##############################################
# $Id: 14_SD_WS09.pm 27954 2023-09-12 20:10:30Z Sidey $
# 
# 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
# 20230121  use round from package FHEM::Core::Utils::Math

package main;

use strict;
use warnings;
use FHEM::Meta;
use FHEM::Core::Utils::Math;

# 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"} };

	return FHEM::Meta::InitMod( __FILE__, $hash );		
}

#############################
sub SD_WS09_Define {
	my ($hash, $def) = @_;
	my @a = split("[ \t][ \t]*", $def);

	return "wrong syntax: define <name> SD_WS09 <code> ".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 =  FHEM::Core::Utils::Math::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 = FHEM::Core::Utils::Math::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 =  FHEM::Core::Utils::Math::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 = FHEM::Core::Utils::Math::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 = FHEM::Core::Utils::Math::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 = FHEM::Core::Utils::Math::round ($windguest * $wfaktor,01);
	$windSpeed_kmh = FHEM::Core::Utils::Math::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 = FHEM::Core::Utils::Math::round ($windguest * $wfaktor,01);
	$windSpeed_fts = FHEM::Core::Utils::Math::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 = FHEM::Core::Utils::Math::round ($windguest * $wfaktor,01);
	$windSpeed_mph = FHEM::Core::Utils::Math::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 = FHEM::Core::Utils::Math::round ($windguest * $wfaktor,01);
	$windSpeed_kn = FHEM::Core::Utils::Math::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 = FHEM::Core::Utils::Math::round(sqrt( 9 + (6 * $windguest)) - 3,0) ;
	$windSpeed_bft = FHEM::Core::Utils::Math::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=".FHEM::Core::Utils::Math::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=".FHEM::Core::Utils::Math::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=".FHEM::Core::Utils::Math::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=".FHEM::Core::Utils::Math::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=".FHEM::Core::Utils::Math::round($ws,2)." Dir=".FHEM::Core::Utils::Math::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=".FHEM::Core::Utils::Math::round($ws,2)." Dir=".FHEM::Core::Utils::Math::round($wd,2)." Time=".substr($time,11)." vec=".FHEM::Core::Utils::Math::round($sumSin,2)."/".FHEM::Core::Utils::Math::round($sumCos,2)." age=".$age." ".FHEM::Core::Utils::Math::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=".FHEM::Core::Utils::Math::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

<a name="SD_WS09"></a>
<h3>Wether Sensors protocol #9</h3>
<ul>
  The SD_WS09 module interprets temperature sensor messages received by a Device like CUL, CUN, SIGNALduino etc.<br><br>
  Requires Perl-Modul Digest::CRC / Math::Trig <br>
	<ul>
	<li>cpan install Digest::CRC or             <br>
			sudo apt-get install libdigest-crc-perl </li>
	<li>cpan install Math::Trig        </li></ul><br>
  <br>
  <b>Known models:</b>
  <ul>
    <li>WS-0101 --> Model: WH1080</li>
    <li>TFA 30.3189 / WH1080 --> Model: WH1080</li>
    <li>1073 (WS1080) --> Model: WH1080</li>
    <li>WH2315 --> Model: WH1080</li>
    <li>WH3080 --> Model: WH1080</li>
    <li>CTW600 --> Model: CTW600</li>
  </ul>
  <br>
  New received device are add in fhem with autocreate.
  <br><br><br>

  <a name="SD_WS09_Define"></a>
  <b>Define</b> 
  <ul>The received devices created automatically.<br>
  The ID of the defice is the model or, if the longid attribute is specified, it is a combination of model and some random generated bits at powering the sensor.<br>
  If you want to use more sensors, you can use the longid option to differentiate them.
  </ul>
  <br>
  
  <a name="SD_WS09_Set"></a>
  <b>Set</b> <ul>N/A</ul><br>
  
  <a name="SD_WS09_Get"></a>
  <b>Get</b> <ul>N/A</ul><br><br>
  
  
  <b>Attributes</b>
  <ul>
    <li><a href="#do_not_notify">do_not_notify</a></li>
    <li><a href="#ignore">ignore</a></li>
    <li><a href="#showtime">showtime</a></li>
    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
    
	<a name="Model"></a>
	<li>Model: WH1080,CTW600
    </li>
	
	<a name="windKorrektur"></a>
    <li>windKorrektur: -3,-2,-1,0,1,2,3   
    </li>
    
	<a name="Unit_of_Wind"></a>
	<li>Unit_of_Wind<br>
    Unit of windSpeed and windGuest. State-Format: Value + Unit.
       <br>m/s,km/h,ft/s,mph,bft,knot 
    </li><br>

	<a name="WindDirAverageTime"></a>
    <li>WindDirAverageTime<br>
    default is 600s, time span to be considered for the calculation
    </li><br>

	<a name="WindDirAverageMinSpeed"></a>
    <li>WindDirAverageMinSpeed<br>
    since the wind direction is usually not clear at very low wind speeds,
    minspeed can be used to specify a threshold value. 
    <br>The (weighted) mean velocity < minspeed is returned undef
    </li><br>

	<a name="WindDirAverageDecay"></a>
    <li>WindDirAverageDecay<br>
       1 -> all values ​​are weighted equally  <br>
       0 -> only the current value is used.   <br>
       in practice, you will take values ​​around 0.75 
    </li><br>
    
	<a name="WS09_CRCAUS"></a>
    <li>WS09_CRCAUS (set in Signalduino-Modul 00_SIGNALduino.pm)
       <br>0: CRC-Check WH1080 CRC-Summe = 0  on, default   
       <br>2: CRC-Summe = 49 (x031) WH1080, set OK
    </li>
   </ul> <br><br>

  <a name="SD_WS09 Events"></a>
  <b>Generated readings:</b>
  <br>Some devices may not support all readings, so they will not be presented<br>
  <ul>
   <li>State (T: H: Ws: Wg: Wd: R: )  temperature, humidity, windSpeed, windGuest, windDirection, Rain</li>
     <li>Temperature (&deg;C)</li>
     <li>Humidity: (The humidity (1-100 if available)</li>
     <li>Battery: (low or ok)</li>
     <li>ID: (The ID-Number (number if)</li>
     <li>windSpeed/windGuest (Unit_of_Wind)) and windDirection (N-O-S-W)</li>
     <li>Rain (mm)</li>
     <li>windDirectionAverage<br>
     As a result, the wind direction is returned, which are calculated from the current and past values
     via a kind of exponential mean value. 
     The respective wind speed is additionally taken into account (higher speed means higher weighting)</li><br>
     <b>WH2315 / WH3080:</b>
     <li>UV Index</li>
     <li>Lux</li>
    
  </ul>
</ul>

=end html
=begin html_DE

<a name="SD_WS09"></a>
<h3>SD_WS09</h3>
<ul>
  Das SD_WS09 Module verarbeitet von einem IO Gerät (CUL, CUN, SIGNALDuino, etc.) empfangene Nachrichten von Temperatur-Sensoren.<br>
  <br>
  Perl-Module Digest::CRC / Math::Trig erforderlich. <br>
	<ul>
	<li>cpan install Digest::CRC oder auch             <br>
			sudo apt-get install libdigest-crc-perl        </li>
	<li>cpan install Math::Trig        </li></ul><br>
  <br>
  <b>Unterst&uuml;tze Modelle:</b>
  <ul>
    <li>WS-0101 --> Model: WH1080</li>
    <li>TFA 30.3189 / WH1080 --> Model: WH1080</li>
    <li>1073 (WS1080) --> Model: WH1080</li>
    <li>WH2315 --> Model: WH1080</li>
    <li>WH3080 --> Model: WH1080</li>
    <li>CTW600 --> Model: CTW600</li>
  </ul>
  <br>
  Neu empfangene Sensoren werden in FHEM per autocreate angelegt.
  <br><br><br>

  <a name="SD_WS09_Define"></a>
  <b>Define</b> 
  <ul>Die empfangenen Sensoren werden automatisch angelegt.<br>
  Die ID der angelegten Sensoren wird nach jedem Batteriewechsel ge&aumlndert, welche der Sensor beim Einschalten zuf&aumlllig vergibt.<br>
  CRC Checksumme wird zur Zeit noch nicht &uuml;berpr&uumlft, deshalb werden Sensoren bei denen die Luftfeuchte < 0 oder > 100 ist, nicht angelegt.<br>
  </ul>
  <br>
  
  <a name="SD_WS09_Set"></a>
  <b>Set</b> <ul>N/A</ul><br>
  
  <a name="SD_WS09_Get"></a>
  <b>Get</b> <ul>N/A</ul><br><br>
  
  <b>Attribute</b>
  <ul>
    <li><a href="#do_not_notify">do_not_notify</a></li>
    <li><a href="#ignore">ignore</a></li>
    <li><a href="#showtime">showtime</a></li>
    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
	
	<a name="Model"></a>
	<li>Model<br>
        WH1080, CTW600
    </li><br>
    
	<a name="windKorrektur"></a>
	<li>windKorrektur<br>
    Korrigiert die Nord-Ausrichtung des Windrichtungsmessers, wenn dieser nicht richtig nach Norden ausgerichtet ist. 
      -3,-2,-1,0,1,2,3    
    </li><br>
    
	<a name="Unit_of_Wind"></a>
	<li>Unit_of_Wind<br>
    Hiermit wird der Einheit eingestellt und im State die entsprechenden Werte + Einheit angezeigt.
       <br>m/s,km/h,ft/s,mph,bft,knot 
    </li><br>

    <a name="WindDirAverageTime"></a>
    <li>WindDirAverageTime<br>
     default ist 600s, Zeitspanne die f&uuml;r die Berechung ber&uuml;cksichtig werden soll
    </li><br>

    <a name="WindDirAverageMinSpeed"></a>
    <li>WindDirAverageMinSpeed<br>
    da bei sehr geringer Windgeschwindigkeit die Windrichtung &uuml;blicherweise nicht
    eindeutig ist, kann mit minspeed ein Schwellwert angegeben werden
    Ist die (gewichtetete) mittlere Geschwindigkeit < minspeed wird undef zur&uuml;ck geliefert
    </li><br>

    <a name="WindDirAverageDecay"></a>
    <li>WindDirAverageDecay<br>
    1 -> alle Werte werden gleich gewichtet <br>
    0 -> nur der aktuelle Wert wird verwendet.<br>
    in der Praxis wird man Werte so um 0.75 nehmen
    </li><br>
   
    <a name="WS09_CRCAUS"></a>
    <li>WS09_CRCAUS<br>
    Wird im Signalduino-Modul (00_SIGNALduino.pm) gesetzt 
       <br>0: CRC-Pr&uuml;fung bei WH1080 CRC-Summe = 0  
       <br>2: CRC-Summe = 49 (x031) bei WH1080 wird als OK verarbeitet
    </li><br>
   </ul><br>

  <a name="SD_WS09 Events"></a>
  <b>Generierte Readings:</b>
  <ul>
     <li>State (T: H: Ws: Wg: Wd: R: )  temperature, humidity, windSpeed, windGuest, Einheit, windDirection, Rain</li>
     <li>Temperature (&deg;C)</li>
     <li>Humidity: (1-100 wenn verf&uuml;gbar)</li>
     <li>Battery: (low or ok)</li>
     <li>ID: (ID-Nummer wenn verf&uuml;gbar)</li>
     <li>windSpeed/windgust (Einheit siehe Unit_of_Wind) und windDirection (N-O-S-W)</li>
     <li>Rain (mm)</li>
     <li>windDirectionAverage
      Als Ergebnis wird die Windrichtung zur&uuml;ck geliefert, die aus dem aktuellen und
    vergangenen Werten &uuml;ber eine Art exponentiellen Mittelwert berechnet werden.
    Dabei wird zusätzlich die jeweilige Windgeschwindigkeit mit ber&uuml;cksichtigt (h&ouml;here Geschwindigkeit
    bedeutet h&ouml;here Gewichtung).</li><br>
     <b>WH2315 / WH3080:</b>
     <li>UV Index</li>
     <li>Lux</li>
     
  </ul>   
   
</ul>

=end html_DE
=for :application/json;q=META.json 14_SD_WS09.pm
{
  "abstract": "Supports weather sensors (WH1080/3080/CTW-600) protocl 9 from SIGNALduino",
  "author": [
    "Sidey <>",
    "ralf9 <>",
	"pejonp",
	"homeautouser"
  ],
  "x_fhem_maintainer": [
    "Sidey",
	"pejonp"
  ],
  "x_fhem_maintainer_github": [
    "Sidey79",
	"HomeAutoUser",
	"elektron-bbs"
  ],
  "description": "The SD_WS09 module interprets temperature sensor messages received by a Device like CUL, CUN, SIGNALduino etc",
  "dynamic_config": 1,
  "keywords": [
    "fhem-sonstige-systeme",
    "fhem-hausautomations-systeme",
    "fhem-mod",
    "signalduino",
    "weather",
    "station",
    "sensor"
  ],
  "license": [
    "GPL_2"
  ],
  "meta-spec": {
    "url": "https://metacpan.org/pod/CPAN::Meta::Spec",
    "version": 2
  },
  "name": "FHEM::SD_WS",
  "prereqs": {
    "runtime": {
      "requires": {
		"Math::Trig" : "0",
		"Digest::CRC" : "0"
      }
    },
    "develop": {
      "requires": {
      	"Math::Trig" : "0",
		"Digest::CRC" : "0"
	  }
    }
  },
  "release_status": "stable",
  "resources": {
    "bugtracker": {
      "web": "https://github.com/RFD-FHEM/RFFHEM/issues/"
    },
    "x_testData": [
      {
        "url": "https://raw.githubusercontent.com/RFD-FHEM/RFFHEM/master/t/FHEM/14_SD_WS09/testData.json",
        "testname": "Testdata with SD_WS09 sensors"
      }
    ],
    "repository": {
      "x_master": {
        "type": "git",
        "url": "https://github.com/RFD-FHEM/RFFHEM.git",
        "web": "https://github.com/RFD-FHEM/RFFHEM/tree/master"
      },
      "type": "svn",
      "url": "https://svn.fhem.de/fhem",
      "web": "https://svn.fhem.de/trac/browser/trunk/fhem/FHEM/14_SD_WS09.pm",
      "x_branch": "trunk",
      "x_filepath": "fhem/FHEM/",
      "x_raw": "https://svn.fhem.de/trac/export/latest/trunk/fhem/FHEM/14_SD_WS09.pm"
    },
    "x_support_community": {
      "board": "Sonstige Systeme",
      "boardId": "29",
      "cat": "FHEM - Hausautomations-Systeme",
      "description": "Sonstige Hausautomations-Systeme",
      "forum": "FHEM Forum",
      "rss": "https://forum.fhem.de/index.php?action=.xml;type=rss;board=29",
      "title": "FHEM Forum: Sonstige Systeme",
      "web": "https://forum.fhem.de/index.php/board,29.0.html"
    },
    "x_wiki": {
      "web": "https://wiki.fhem.de/wiki/SIGNALduino"
    }
  }
}
=end :application/json;q=META.json
=cut

