From d791459b9d240471f0b3711bc459996e5d9260e1 Mon Sep 17 00:00:00 2001 From: sidey79 Date: Tue, 12 Sep 2023 20:08:07 +0000 Subject: [PATCH] 14_Hideki.pm: refactored code, fixed bug git-svn-id: https://svn.fhem.de/fhem/trunk@27953 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 2 + fhem/FHEM/14_Hideki.pm | 822 ++++++++++++++++++++++------------------- 2 files changed, 441 insertions(+), 383 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index 2e46738cd..009eee2cf 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,7 @@ # 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. + - bugifix: 14_Hideki.pm: winddirection fixed (#1099) + - change: 14_Hideki.pm: Update {Match} regex for Hideki Module (#1071) - feature: SD_ProtocolData.pm: Protocol 122 for Temola TM 40 thermometer Protocol 119 Basic funkbus suppors Protocol 85 new sensor TFA 30.3251.10 diff --git a/fhem/FHEM/14_Hideki.pm b/fhem/FHEM/14_Hideki.pm index c7341e5ab..ebbda802a 100644 --- a/fhem/FHEM/14_Hideki.pm +++ b/fhem/FHEM/14_Hideki.pm @@ -7,34 +7,43 @@ # S. Butzek, HJGode, Ralf9 2015-2017 # S. Butzek 2018-2022 # -# changed the way crc and decrypt is used hjgode 20171129 +# It is part of the SIGNALduinos project. +# https://github.com/RFD-FHEM/RFFHEM | see http://www.fhemwiki.de/wiki/SIGNALduino +# +# The module was modified by a few additions. support Hideki Sensors +# 2015-2017 S. Butzek, hjgode, Ralf9 +# 2018- S. Butzek, elektron-bbs, HomeAutoUser, Ralf9 +# +# 20171129 - hjgode, changed the way crc and decrypt is used package main; #use version 0.77; our $VERSION = version->declare('v3.4.3'); + use strict; use warnings; use POSIX; use FHEM::Meta; + +eval {use Data::Dumper qw(Dumper);1}; #use Data::Dumper; -##################################### -sub -Hideki_Initialize($) -{ - my ($hash) = @_; +##################################### +sub Hideki_Initialize { + my ($hash) = @_; + carp "Hideki_Initialize, hash failed" if (!$hash); + $hash->{Match} = qr/^P12#75[A-F0-9]{14,30}/; # Laenge (Anhahl nibbles nach 0x75 )noch genauer spezifizieren $hash->{DefFn} = \&Hideki_Define; $hash->{UndefFn} = \&Hideki_Undef; $hash->{ParseFn} = \&Hideki_Parse; - $hash->{AttrList} = "do_not_notify:0,1 showtime:0,1" - ." ignore:0,1" - ." windDirCorr windSpeedCorr" - ." $readingFnAttributes"; - + $hash->{AttrList} = 'do_not_notify:0,1 showtime:0,1' + .' ignore:0,1' + .' windDirCorr windSpeedCorr' + ." $readingFnAttributes"; $hash->{AutoCreate}= { "Hideki.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME", GPLOT => "temp4hum4:Temp/Hum,", autocreateThreshold => "2:180"} }; @@ -42,255 +51,280 @@ Hideki_Initialize($) } -##################################### -sub -Hideki_Define($$) -{ - my ($hash, $def) = @_; - my @a = split("[ \t][ \t]*", $def); +my %comfortLevel = ( + 0 => q[Hum. OK. Temp. uncomfortable (>24.9 or <20)], + 1 => q[Wet. More than 69% RHWet. More than 69% RH], + 2 => q[Dry. Less than 40% RH], + 3 => q[Temp. and Hum. comfortable] +); +my @winddir_name=("N","NNE","NE","ENE","E","ESE","SE","SSE","S","SSW","SW","WSW","W","WNW","NW","NNW"); + +my %allSensorTypes; +%allSensorTypes = ( + 30 => { + 'temperature' => \&getTemperature, + 'channel' => \&getChannel, + 'battery' => \&getBattery, + 'humidity' => \&getHumidity, + 'comfort_level' => \&getComfort, + 'package_number' => \&getCount, + '_eval' => { + 'batteryState' => sub { return $_[0]->{battery} }, + 'state' => sub { return qq/T: $_[0]->{temperature} H: $_[0]->{humidity}/ } + } + }, + 31 => { + 'temperature' => \&getTemperature, + 'channel' => \&getChannel, + 'battery' => \&getBattery, + 'package_number' => \&getCount, + '_eval' => { + 'batteryState' => sub { return $_[0]->{battery} }, + 'state' => sub { return qq/T: $_[0]->{temperature}/ } + } + }, + 14 => { + 'rain' => \&getRain, + 'channel' => \&getChannel, + 'battery' => \&getBattery, + 'package_number' => \&getCount, + '_eval' => { + 'batteryState' => sub { return $_[0]->{battery} }, + 'state' => sub { return qq/R: $_[0]->{rain}/ } + } + }, + 12 => { + 'temperature' => \&getTemperature, + 'channel' => \&getChannel, + 'battery' => \&getBattery, + 'package_number' => \&getCount, + 'windChill' => \&getWindchill, + 'windDirection' => \&getWinddir, + 'windDirectionDegree' => \&getWinddirdeg, + 'windDirectionText' => \&getWinddirtext, + 'windGust' => \&getWindgust, + 'windSpeed' => \&getWindspeed, + '_eval' => { + 'batteryState' => sub { return $_[0]->{battery} }, + '_corrWindSpeed' => \&correctWindValues, + 'state' => sub { return qq/T: $_[0]->{temperature} Ws: $_[0]->{windSpeed} Wg: $_[0]->{windGust} Wd: $_[0]->{windDirectionText}/ } + }, + }, + 13 => { + 'temperature' => \&getTemperature, + 'channel' => \&getChannel, + 'battery' => \&getBattery, + 'package_number' => \&getCount, + '_eval' => { + 'batteryState' => sub { return $_[0]->{battery} }, + 'state' => sub { return qq/T: $_[0]->{temperature}/ } + }, + 'debug' => sub { return q[type currently not full supported, please report sensor information] } + } +); + + +##################################### +sub Hideki_Define { + my ($hash, $def) = @_; + carp qq[Hideki_Define, too few arguments ($hash, $def)] if @_ < 2; + (ref $hash ne 'HASH') // return q[no hash provided]; + + my @a = split("[ \t][ \t]*", $def); return "wrong syntax: define Hideki ".int(@a) - if(int(@a) < 3); + if(int(@a) < 3); $hash->{CODE} = $a[2]; - $hash->{lastMSG} = ""; + $hash->{lastMSG} = ''; my $name= $hash->{NAME}; - $modules{Hideki}{defptr}{$a[2]} = $hash; - #$hash->{STATE} = "Defined"; - #AssignIoPort($hash); - return undef; + return; } ##################################### -sub -Hideki_Undef($$) -{ +sub Hideki_Undef { my ($hash, $name) = @_; + carp qq[Hideki_Undef, too few arguments ($hash, $name)] if @_ < 2; + (ref $hash ne 'HASH') // return q[no hash provided]; + delete($modules{Hideki}{defptr}{$hash->{CODE}}) if($hash && $hash->{CODE}); - return undef; + return; } ##################################### -sub -Hideki_Parse($$) -{ - my ($iohash,$msg) = @_; - my (undef ,$rawData) = split("#",$msg); +sub Hideki_Parse { + my ($iohash,$msg) = @_; + carp qq[Hideki_Parse, too few arguments ($iohash, $msg)] if @_ < 2; + (ref $iohash ne 'HASH') // return q[no hash provided]; - my $ioname = $iohash->{NAME}; - my @a = split("", $msg); - Log3 $iohash, 4, "$ioname Hideki_Parse: incomming $msg"; + my (undef ,$rawData) = split(/#/,$msg); + my $ioname = $iohash->{NAME}; + my @a = split(//, $msg); + Log3 $iohash, 4, "$ioname Hideki_Parse: incomming $msg"; - my @decodedData; - my $crc1crc2OK = 0; - ($crc1crc2OK, @decodedData) = decryptAndCheck($iohash, $rawData); # use unencrypted rawdata - - if ($crc1crc2OK == 0) { - return ''; #crc1 or crc2 failed - } + my @decodedData; + my $crc1crc2OK = 0; + ($crc1crc2OK, @decodedData) = decryptAndCheck($iohash, $rawData); # use unencrypted rawdata - # decrypt and decodedBytes are now done with decryptAndCheck - my $decodedString = join '', unpack('H*', pack('C*',@decodedData)); # get hex string - Log3 $iohash, 4, "$ioname Hideki_Parse: raw=$rawData, decoded=$decodedString"; - - if (!@decodedData) { - Log3 $iohash, 4, "$ioname Hideki_Parse: decrypt failed"; - return ''; - } - - Log3 $iohash, 5, "$ioname Hideki_Parse: getSensorType for ".$decodedData[3]; - my $sensorTyp=($decodedData[3] & 0x1F); - Log3 $iohash, 4, "$ioname Hideki_Parse: SensorTyp = $sensorTyp decodedString = $decodedString"; + if ($crc1crc2OK == 0) { + return ''; #crc1 or crc2 failed + } - my $id=substr($decodedString,2,2); # get the random id from the data - my $channel=0; - my $temp=""; - my $hum=0; - my $rain=0; - my $unknown=0; - my $windchill=0; - my $windspeed=0; - my $windgust=0; - my $winddir=0; - my $winddirdeg=0; - my $winddirtext; - my $rc; - my $val; - my $bat; - my $deviceCode; - my $model= "Hideki_$sensorTyp"; - my $count=0; - my $comfort=0; - ## 1. Detect what type of sensor we have, then call specific function to decode - if ($sensorTyp==30){ - ($channel, $temp) = decodeThermo(\@decodedData); # decodeThermoHygro($decodedString); - $hum = 10 * ($decodedData[6] >> 4) + ($decodedData[6] & 0x0f); - $bat = ($decodedData[2] >> 6 == 3) ? 'ok' : 'low'; # decode battery - $count = $decodedData[3] >> 6; # verifiziert, MSG_Counter - $comfort = ($decodedData[7] >> 2 & 0x03); # comfort level + # decrypt and decodedBytes are now done with decryptAndCheck + my $decodedString = join '', unpack('H*', pack('C*',@decodedData)); # get hex string + Log3 $iohash, 4, "$ioname Hideki_Parse: raw=$rawData, decoded=$decodedString"; - if ($comfort == 0) { $comfort = 'Hum. OK. Temp. uncomfortable (>24.9 or <20)' } - elsif ($comfort == 1) { $comfort = 'Wet. More than 69% RH' } - elsif ($comfort == 2) { $comfort = 'Dry. Less than 40% RH' } - elsif ($comfort == 3) { $comfort = 'Temp. and Hum. comfortable' } - $val = "T: $temp H: $hum"; - Log3 $iohash, 4, "$ioname decoded Hideki protocol model=$model, sensor id=$id, channel=$channel, cnt=$count, bat=$bat, temp=$temp, humidity=$hum, comfort=$comfort"; - }elsif($sensorTyp==31){ - ($channel, $temp) = decodeThermo(\@decodedData); - $bat = ($decodedData[2] >> 6 == 3) ? 'ok' : 'low'; # decode battery - $count = $decodedData[3] >> 6; # verifiziert, MSG_Counter - $val = "T: $temp"; - Log3 $iohash, 4, "$ioname decoded Hideki protocol model=$model, sensor id=$id, channel=$channel, cnt=$count, bat=$bat, temp=$temp"; - }elsif($sensorTyp==14){ - ($channel, $rain) = decodeRain(\@decodedData); # decodeThermoHygro($decodedString); - $bat = ($decodedData[2] >> 6 == 3) ? 'ok' : 'low'; # decode battery - $count = $decodedData[3] >> 6; # UNVERIFIZIERT, MSG_Counter - $val = "R: $rain"; - Log3 $iohash, 4, "$ioname decoded Hideki protocol model=$model, sensor id=$id, channel=$channel, cnt=$count, bat=$bat, rain=$rain, unknown=$unknown"; - }elsif($sensorTyp==12){ - ($channel, $temp) = decodeThermo(\@decodedData); # decodeThermoHygro($decodedString); - #($windchill,$windspeed,$windgust,$winddir,$winddirdeg,$winddirtext) = wind(\@decodedData); ## nach unten verschoben - $bat = ($decodedData[2] >> 6 == 3) ? 'ok' : 'low'; # decode battery - $count = $decodedData[3] >> 6; # UNVERIFIZIERT, MSG_Counter - #$val = "T: $temp Ws: $windspeed Wg: $windgust Wd: $winddirtext"; ## nach unten verschoben - Log3 $iohash, 4, "$ioname decoded Hideki protocol model=$model, sensor id=$id, channel=$channel, cnt=$count, bat=$bat, temp=$temp"; - }elsif($sensorTyp==13){ - ($channel, $temp) = decodeThermo(\@decodedData); # decodeThermoHygro($decodedString); - $bat = ($decodedData[2] >> 6 == 3) ? 'ok' : 'low'; # decode battery - $count = $decodedData[3] >> 6; # UNVERIFIZIERT, MSG_Counter - $val = "T: $temp"; - Log3 $iohash, 4, "$ioname decoded Hideki protocol model=$model, sensor id=$id, channel=$channel, cnt=$count, bat=$bat, temp=$temp"; - Log3 $iohash, 4, "$ioname Sensor Typ $sensorTyp currently not full supported, please report sensor information!"; - } - else{ - Log3 $iohash, 4, "$ioname Sensor Typ $sensorTyp not supported, please report sensor information!"; - return ""; - } - my $longids = AttrVal($iohash->{NAME},'longids',0); - if ( ($longids ne "0") && ($longids eq "1" || $longids eq "ALL" || (",$longids," =~ m/,$model,/))) - { - $deviceCode=$model . "_" . $id . "." . $channel; - Log3 $iohash,4, "$ioname Hideki_Parse: using longid: $longids model: $model"; - } else { - $deviceCode = $model . "_" . $channel; - } + if (!@decodedData) { + Log3 $iohash, 4, "$ioname Hideki_Parse: decrypt failed"; + return ''; + } - Log3 $iohash, 5, "$ioname Hideki_Parse deviceCode: $deviceCode"; + Log3 $iohash, 5, "$ioname Hideki_Parse: getSensorType for ".$decodedData[3]; + my $sensorTyp=getSensorType(\@decodedData); + Log3 $iohash, 4, "$ioname Hideki_Parse: SensorTyp = $sensorTyp decodedString = $decodedString"; - my $def = $modules{Hideki}{defptr}{$iohash->{NAME} . "." . $deviceCode}; - $def = $modules{Hideki}{defptr}{$deviceCode} if(!$def); + my $id=substr($decodedString,2,2); # get the random id from the data + my $deviceCode; + my $model= qq[Hideki_$sensorTyp]; - if(!$def) { - Log3 $iohash, 1, "$ioname Hideki: UNDEFINED sensor $deviceCode detected, code $msg"; - return "UNDEFINED $deviceCode Hideki $deviceCode"; - } + ## 1. Detect what type of sensor we have, then call specific function to decode + if ( !exists $allSensorTypes{$sensorTyp} ) { + Log3 $iohash, 4, qq[$ioname Sensor type $sensorTyp not supported, please report sensor information!]; + #return q[]; + }; - my $hash = $def; - my $name = $hash->{NAME}; - return "" if(IsIgnored($name)); + # Build sensordecoder based on type + my $sensorDecoder = $allSensorTypes{$sensorTyp}; - #Log3 $name, 4, "Hideki: $name ($msg)"; - - if ($sensorTyp == 12) { # Wind - ($windchill,$windspeed,$windgust,$winddir,$winddirdeg,$winddirtext) = wind($name, \@decodedData); - $val = "T: $temp Ws: $windspeed Wg: $windgust Wd: $winddirtext"; - Log3 $name, 4, "$ioname $name Parse: model=12(wind), T: $temp, Wc=$windchill, Ws=$windspeed, Wg=$windgust, Wd=$winddir, WdDeg=$winddirdeg, Wdtxt=$winddirtext"; - } - - if (!defined(AttrVal($name,"event-min-interval",undef))) - { - my $minsecs = AttrVal($ioname,'minsecs',0); - if($hash->{lastReceive} && (time() - $hash->{lastReceive} < $minsecs)) { - Log3 $name, 4, "$name Hideki_Parse: $deviceCode Dropped ($decodedString) due to short time. minsecs=$minsecs"; - return ""; - } - } - $hash->{lastReceive} = time(); + # Get values from decoder + my %sensorData; + foreach my $key ( keys %{ $sensorDecoder } ) + { + next if (ref $sensorDecoder->{$key} ne q[CODE]); + $sensorData{$key} = $sensorDecoder->{$key}->(\@decodedData); + } - $def->{lastMSG} = $decodedString; + # Log received values + my $logstr = q{}; + while( my ($key, $value) = each(%sensorData) ) { + next if ($key =~ /^_/x ); + $logstr .= qq[, $key=$value]; + } + Log3 $iohash, 4, qq[$ioname decoder Hideki protocol model=$model, sensor id=$id].$logstr; - #Log3 $name, 4, "Hideki update $name:". $name; + # Get devicecode + my $longids = AttrVal($iohash->{NAME},'longids',0); + if ( ($longids ne "0") && ($longids eq "1" || $longids eq "ALL" || (",$longids," =~ m/,$model,/x))) + { + $deviceCode=$model . "_" . $id . "." . $sensorData{channel}; + Log3 $iohash,4, "$ioname Hideki_Parse: using longid: $longids model: $model"; + } else { + $deviceCode = $model . "_" . $sensorData{channel}; + } - readingsBeginUpdate($hash); - readingsBulkUpdate($hash, "state", $val); - readingsBulkUpdate($hash, "battery", $bat) if ($bat ne ""); - readingsBulkUpdate($hash, "batteryState", $bat) if ($bat ne ""); - readingsBulkUpdate($hash, "channel", $channel) if ($channel ne ""); - readingsBulkUpdate($hash, "temperature", $temp) if ($temp ne ""); - readingsBulkUpdate($hash, "package_number", $count) if ($count ne ""); - if ($sensorTyp == 30) { # temperature, humidity - readingsBulkUpdate($hash, "humidity", $hum) if ($hum ne ""); - readingsBulkUpdate($hash, "comfort_level", $comfort) if ($comfort ne ""); - } - elsif ($sensorTyp == 14) { # rain - readingsBulkUpdate($hash, "rain", $rain); - } - elsif ($sensorTyp == 12) { # wind - readingsBulkUpdate($hash, "windChill", $windchill); - readingsBulkUpdate($hash, "windGust", $windgust); - readingsBulkUpdate($hash, "windSpeed", $windspeed); - readingsBulkUpdate($hash, "windDirection", $winddir); - readingsBulkUpdate($hash, "windDirectionDegree", $winddirdeg); - readingsBulkUpdate($hash, "windDirectionText", $winddirtext); - } + Log3 $iohash, 5, "$ioname Hideki_Parse deviceCode: $deviceCode"; - readingsEndUpdate($hash, 1); # Notify is done by Dispatch + # Check if device is defined + my $def = $modules{Hideki}{defptr}{$iohash->{NAME} . "." . $deviceCode}; + $def = $modules{Hideki}{defptr}{$deviceCode} if(!$def); + if(!$def) { + Log3 $iohash, 1, "$ioname Hideki: UNDEFINED sensor $deviceCode detected, code $msg"; + return "UNDEFINED $deviceCode Hideki $deviceCode"; + } - return $name; + # Check if device will receive update + my $hash = $def; + my $name = $hash->{NAME}; + return "" if(IsIgnored($name)); + $sensorData{_NAME} = $hash->{NAME}; + + if (!defined(AttrVal($name,"event-min-interval",undef))) + { + my $minsecs = AttrVal($ioname,'minsecs',0); + if($hash->{lastReceive} && (time() - $hash->{lastReceive} < $minsecs)) { + Log3 $name, 4, "$name Hideki_Parse: $deviceCode Dropped ($decodedString) due to short time. minsecs=$minsecs"; + return ''; + } + } + # Update existing device + $hash->{lastReceive} = time(); + $def->{lastMSG} = $decodedString; + + + # Do some late evaluations bevore update readings + foreach my $key (sort keys %{ $sensorDecoder->{_eval} }) { + $sensorData{$key} = $sensorDecoder->{_eval}{$key}->(\%sensorData); + } + + readingsBeginUpdate($hash); + while ( my ($key, $value) = each(%sensorData) ) { + next if ($key =~ /^[_\.]/x ); + readingsBulkUpdate($hash,$key,$value); + } + readingsEndUpdate($hash, 1); # Notify is done by Dispatch + + return $name; } +##################################### # decryptAndCheck # input is raw data (array of bytes) # output is true if check1 and check2 OK # data will then hold the decrypted data sub decryptAndCheck { - my $iohash = shift; - my $rawData = shift; - my $name = $iohash->{NAME}; - my $cs1=0; #will be zero for xor over all (bytes[2]>>1)&0x1F except first byte (always 0x75) - my $cs2=0; - my $i; - my @data; - @data=map { hex($_) } ($rawData =~ /(..)/g); #byte array from raw hex data string - - #/* Decrypt raw received data byte */ BYTE DecryptByte(BYTE b) { return b ^ (b << 1); } - my $count=( ($data[2] ^ ($data[2]<<1)) >>1 ) & 0x1f; - my $L = scalar @data; - if ($L <= $count+2) { - Log3 $iohash, 4, "$name Hideki_crc: rawdata=$rawData to short, count=$count data length=$L"; - return (0,@data); - } - - if($data[0] != 0x75) { - Log3 $iohash, 4, "$name Hideki_Parse: rawData=$rawData is no Hideki"; - return (0,@data); - } - - #iterate over data only, first byte is 0x75 always - # read bytes 1 to n-2 , just before checksum - for ($i=1; $i<($count+2); $i++) { - $cs1 ^= $data[$i]; # calc first chksum - $cs2 = Hideki_SecondCheck($data[$i] ^ $cs2); - $data[$i] ^= (($data[$i] << 1) & 0xFF); # decrypt byte at $i without overflow - } - - $count += 2; - if ($cs1 != 0 || $cs2 != $data[$count]) { - Log3 $iohash, 4, "$name Hideki crcCheck FAILED: cs1 / cs2/checksum2 $cs1 / $cs2/$data[$count], rawData=$rawData, count+2=$count, length=$L"; - return (0, @data); - } else { - Log3 $iohash, 4, "$name Hideki crcCheck ok: cs1/cs2 $cs1/$cs2, rawData=$rawData, count+2=$count, length=$L"; - } - return (1, @data); + carp qq[decryptAndCheck, too few arguments (iohash, rawData)] if @_ < 2; + + my $iohash = shift; + my $rawData = shift; + + my $name = $iohash->{NAME}; + my $cs1=0; #will be zero for xor over all (bytes[2]>>1)&0x1F except first byte (always 0x75) + my $cs2=0; + my $i; + my @data; + @data=map { hex($_) } ($rawData =~ /(..)/gx); #byte array from raw hex data string + + #/* Decrypt raw received data byte */ BYTE DecryptByte(BYTE b) { return b ^ (b << 1); } + my $count=( ($data[2] ^ ($data[2]<<1)) >>1 ) & 0x1f; + my $L = scalar @data; + if ($L <= $count+2) { + Log3 $iohash, 4, "$name Hideki_crc: rawdata=$rawData to short, count=$count data length=$L"; + return (0,@data); + } + + if($data[0] != 0x75) { + Log3 $iohash, 4, "$name Hideki_Parse: rawData=$rawData is not Hideki"; + return (0,@data); + } + + #iterate over data only, first byte is 0x75 always + # read bytes 1 to n-2 , just before checksum + for my $i (1..$count+1) { + $cs1 ^= $data[$i]; # calc first chksum + $cs2 = Hideki_SecondCheck($data[$i] ^ $cs2); + $data[$i] ^= (($data[$i] << 1) & 0xFF); # decrypt byte at $i without overflow + } + + $count += 2; + if ($cs1 != 0 || $cs2 != $data[$count]) { + Log3 $iohash, 4, "$name Hideki crcCheck FAILED: cs1 / cs2/checksum2 $cs1 / $cs2/$data[$count], rawData=$rawData, count+2=$count, length=$L"; + return (0, @data); + } else { + Log3 $iohash, 4, "$name Hideki crcCheck ok: cs1/cs2 $cs1/$cs2, rawData=$rawData, count+2=$count, length=$L"; + } + return (1, @data); } +##################################### # /* The second checksum. Input is OldChecksum^NewByte */ -sub Hideki_SecondCheck{ +sub Hideki_SecondCheck { + carp qq[Hideki_SecondCheck, too few arguments] if @_ < 1; my $b = shift; + my $c = 0; if (($b & 0x80) == 0x80){ $b^=0x95; @@ -306,139 +340,160 @@ sub Hideki_SecondCheck{ } +##################################### # return decoded sensor type # in: one byte # out: one byte # Der Typ eines Sensors steckt in Byte 3: # Byte3 & 0x1F Device -# 0x0C Anemometer -# 0x0D UV sensor -# 0x0E Rain level meter -# 0x1E Thermo/hygro-sensor -# 0x1F Thermo sensor -sub getSensorType{ - return ($_[0] & 0x1F); +# 0x0C Anemometer +# 0x0D UV sensor +# 0x0E Rain level meter +# 0x1E Thermo/hygro-sensor +# 0x1F Thermo sensor +sub getSensorType { + my $decodedData= shift // carp q[no bytes specified]; + + return $decodedData->[3] & 0x1F; } -# decode byte array and return channel, temperature -# input: decrypted byte array starting with 0x75, passed by reference as in mysub(\@array); -# output , , -# was unable to get this working with an array ref as input, so switched to hex string input -sub decodeThermo { - my @Hidekibytes = @{$_[0]}; +##################################### +# getters for serval values from the decrypted hexdata +# input:hashref of hex value of received message +# output specific value - #my $Hidekihex = shift; - #my @Hidekibytes=(); - #for (my $i=0; $i<(length($Hidekihex))/2; $i++){ - # my $hex=hex(substr($Hidekihex, $i*2, 2)); ## Mit split und map geht es auch ... $str =~ /(..?)/g; - # push (@Hidekibytes, $hex); - #} - my $channel=0; - my $temp=0; +sub getTemperature { + my $decodedData= shift // carp q[no bytes specified]; + my $temp = 100 * ($decodedData->[5] & 0x0f) + 10 * ($decodedData->[4] >> 4) + ($decodedData->[4] & 0x0f); + ## // temp is negative? + if (!($decodedData->[5] & 0x80)) { $temp = -$temp; } - $channel = $Hidekibytes[1] >> 5; - # //Internally channel 4 is used for the other sensor types (rain, uv, anemo). - # //Therefore, if channel is decoded 5 or 6, the real value set on the device itself is 4 resp 5. - if ($channel >= 5) { - $channel--; - } - my $sensorId = $Hidekibytes[1] & 0x1f; # Extract random id from sensor - #my $devicetype = $Hidekibytes[3]&0x1f; - $temp = 100 * ($Hidekibytes[5] & 0x0f) + 10 * ($Hidekibytes[4] >> 4) + ($Hidekibytes[4] & 0x0f); - ## // temp is negative? - if (!($Hidekibytes[5] & 0x80)) { - $temp = -$temp; - } + return $temp = $temp / 10; +} - $temp = $temp / 10; - return ($channel, $temp); +sub getChannel { + my $decodedData = shift // carp q[no bytes specified]; + my $channel = $decodedData->[1] >> 5; + + if ( $channel >= 5 ) { $channel--; } + + return $channel +} + +sub getHumidity { + my $decodedData = shift // carp q[no bytes specified]; + + return 10 * ($decodedData->[6] >> 4) + ($decodedData->[6] & 0x0f); +} + +sub getBattery { + my $decodedData = shift // carp q[no bytes specified]; + + return ($decodedData->[2] >> 6 == 3) ? 'ok' : 'low'; # decode battery +} + +sub getCount { + my $decodedData = shift // carp q[no bytes specified]; + + return $decodedData->[3] >> 6; # verifiziert, MSG_Counter +} + +sub getComfort { + my $decodedData = shift // carp q[no bytes specified]; + my $comfortVal = ($decodedData->[7] >> 2 & 0x03); # comfort level + + if ( !exists $comfortLevel{$comfortVal} ) { return $comfortVal; }; + + return $comfortLevel{$comfortVal}; +} + +sub getRain { + my $decodedData = shift // carp q[no bytes specified]; + + return ($decodedData->[4] + $decodedData->[5]*0xff)*0.7; +} + +sub getWindchill { + my $decodedData = shift // carp q[no bytes specified]; + + my $windchill = 100 * ($decodedData->[7] & 0x0f) + 10 * ($decodedData->[6] >> 4) + ($decodedData->[6] & 0x0f); + ## windchill is negative? + if (!($decodedData->[7] & 0x80)) { + $windchill = -$windchill; + } + + return $windchill / 10; +} + +sub getWindspeed { + my $decodedData = shift // carp q[no bytes specified]; + + my $windspeed = ($decodedData->[9] & 0x0f ) * 100 + ($decodedData->[8] >> 4) * 10 + ($decodedData->[8] & 0x0f); + + return sprintf("%.2f", $windspeed); +} + +sub getWindgust { + my $decodedData = shift // carp q[no bytes specified]; + + my $windgust = ($decodedData->[10] >> 4) * 100 + ($decodedData->[10] & 0x0f) * 10 + ($decodedData->[9] >> 4); + + return sprintf("%.2f", $windgust); } -# decode byte array and return channel and total rain in mm -# input: decrypted byte array starting with 0x75, passed by reference as in mysub(\@array); -# output , , -# was unable to get this working with an array ref as input, so switched to hex string input -sub decodeRain { - my @Hidekibytes = @{$_[0]}; +sub getWinddir { + my $decodedData = shift // carp q[no bytes specified]; + my @wd=(0, 15, 13, 14, 9, 10, 12, 11, 1, 2, 4, 3, 8, 7, 5, 6); - #my $Hidekihex = shift; - #my @Hidekibytes=(); - #for (my $i=0; $i<(length($Hidekihex))/2; $i++){ - # my $hex=hex(substr($Hidekihex, $i*2, 2)); ## Mit split und map geht es auch ... $str =~ /(..?)/g; - # push (@Hidekibytes, $hex); - #} - my $channel=0; - my $rain=0; - my $unknown; - - #my $tests=0; - #additional checks? - #if($Hidekibytes[2]==0xCC){ - # $tests+=1; - #} - #if($Hidekibytes[6]==0x66){ - # $tests+=1; - #} - # possibly test if $tests==2 for sanity check - #printf("SANITY CHECK tests=%i\n", $tests); - - $unknown = $Hidekibytes[6]; - $channel = $Hidekibytes[1] >> 5; - # //Internally channel 4 is used for the other sensor types (rain, uv, anemo). - # //Therefore, if channel is decoded 5 or 6, the real value set on the device itself is 4 resp 5. - if ($channel >= 5) { - $channel--; - } - my $sensorId = $Hidekibytes[1] & 0x1f; # Extract random id from sensor - - $rain = ($Hidekibytes[4] + $Hidekibytes[5]*0xff)*0.7; - - return ($channel, $rain, $unknown); + return $wd[$decodedData->[11] >> 4]; } -# P12#758BB244074007400F00001C6E7A01 -sub wind { - my $name = shift; - my @Hidekibytes = @{$_[0]}; - my @wd=(0, 15, 13, 14, 9, 10, 12, 11, 1, 2, 4, 3, 8, 7, 5, 6); - my @winddir_name=("N","NNE","NE","ENE","E","ESE","SE","SSE","S","SSW","SW","WSW","W","WNW","NW","NNW"); - my $windspeed; - my $windchill; - my $windgust; - my $winddir; - my $winddirdeg; - my $winddirtext; - - $windchill = 100 * ($Hidekibytes[7] & 0x0f) + 10 * ($Hidekibytes[6] >> 4) + ($Hidekibytes[6] & 0x0f); - ## windchill is negative? - if (!($Hidekibytes[7] & 0x80)) { - $windchill = -$windchill; - } - $windchill = $windchill / 10; - $windspeed = ($Hidekibytes[9] & 0x0f ) * 100 + ($Hidekibytes[8] >> 4) * 10 + ($Hidekibytes[8] & 0x0f); - $windgust = ($Hidekibytes[10] >> 4) * 100 + ($Hidekibytes[10] & 0x0f) * 10 + ($Hidekibytes[9] >> 4); - my $windSpeedCorr = AttrVal($name,'windSpeedCorr',1); ### -> hierher verschoben - if ($windSpeedCorr > 0) { - $windspeed = sprintf("%.2f", $windspeed * $windSpeedCorr); - $windgust = sprintf("%.2f", $windgust * $windSpeedCorr); - Log3 $name, 5, "$name Hideki_Parse: WindSpeedCorr factor=$windSpeedCorr"; - } - $winddir = $wd[$Hidekibytes[11] >> 4]; - my $windDirCorr = AttrVal($name,'windDirCorr',0); - if ($windDirCorr > 0) { - $winddir += $windDirCorr; - $winddir &= 15; - Log3 $name, 5, "$name Hideki_Parse: windDirCorr=$windDirCorr"; - } - $winddirtext = $winddir_name[$winddir]; - $winddirdeg = $winddir * 22.5; - - return ($windchill,$windspeed,$windgust,$winddir,$winddirdeg,$winddirtext); +sub getWinddirtext { + my $decodedData = shift // carp q[no bytes specified]; + + return $winddir_name[getWinddir($decodedData)]; } +sub getWinddirdeg { + my $decodedData = shift // carp q[no bytes specified]; + return getWinddir($decodedData) * 22.5; +} + +##################################### +# correct wind values if correction attributes are set +# input: hashref with prepared values from sensors +# output undef + +sub correctWindValues { + my $sensorValues = shift // carp q[no values from sensor specified]; + + if (! IsDevice($sensorValues->{_NAME}) ) { carp q[no sensorname provided]; } + my $windSpeedCorr = AttrVal($sensorValues->{_NAME},'windSpeedCorr',1); + my $windDirCorr = AttrVal($sensorValues->{_NAME},'windDirCorr',0); + + if ($windSpeedCorr > 0) { + $sensorValues->{windSpeed} = sprintf q[%.2f], $sensorValues->{windSpeed} * $windSpeedCorr ; + $sensorValues->{windGust} = sprintf q[%.2f], $sensorValues->{windGust} * $windSpeedCorr ; + Log3 $sensorValues->{_NAME}, 5, qq[$sensorValues->{_NAME} correctWindValues: WindSpeedCorr factor=$windSpeedCorr]; + } + + if ($windDirCorr > 0) { + $sensorValues->{windDirection} += $windDirCorr; + $sensorValues->{windDirection} &= 15; + $sensorValues->{windDirectionText} = $winddir_name[$sensorValues->{windDirection}]; + $sensorValues->{windDirectionDegree} = $sensorValues->{windDirection} * 22.5; + + Log3 $sensorValues->{_NAME}, 5, qq[$sensorValues->{_NAME} correctWindValues: windDirCorr=$windDirCorr]; + } + + return; +} + + + 1; =pod @@ -455,13 +510,13 @@ sub wind { Supported Brands
    -
  • Arduinos with remote Sensor lib from Randy Simons
  • -
  • Bresser
  • -
  • Cresta
  • -
  • Hama
  • -
  • Hideki (Anemometer | UV sensor | Rain level meter | Thermo/hygro-sensor)
  • -
  • TFA Dostman
  • -
  • all other devices, which use the Hideki protocol
  • +
  • Arduinos with remote Sensor lib from Randy Simons
  • +
  • Bresser
  • +
  • Cresta
  • +
  • Hama
  • +
  • Hideki (Anemometer | UV sensor | Rain level meter | Thermo/hygro-sensor)
  • +
  • TFA Dostman
  • +
  • all other devices, which use the Hideki protocol
Please note, currently temp/hum devices are implemented. Please report data for other sensortypes.

@@ -472,26 +527,26 @@ sub wind {
  • <code> is the address of the sensor device and - is build by the sensor type and the channelnumber (1 to 5) or if the attribute longid is specfied an autogenerated address build when inserting - the battery (this adress will change every time changing the battery).

  • - -
  • If autocreate is enabled, the device will be defined via autocreate. This is also the preferred mode of defining such a device.


  • + is build by the sensor type and the channelnumber (1 to 5) or if the attribute longid is specfied an autogenerated address build when inserting + the battery (this adress will change every time changing the battery).
    + +
  • If autocreate is enabled, the device will be defined via autocreate. This is also the preferred mode of defining such a device.


  • Generated readings
      -
    • battery & batteryState (ok or low)
    • -
    • channel (The Channelnumber (number if)
    • -
    • humidity (0-100)
    • -
    • state (T:x.xx H:y B:z)
    • -
    • temperature (°C)
    • -
      - Hideki only - -
    • comfort_level (Status: Humidity OK... , Wet. More than 69% RH, Dry. Less than 40% RH, Temperature and humidity comfortable)
    • -
    • package_number (reflect the package number in the stream starting at 1)

    • +
    • battery & batteryState (ok or low)
    • +
    • channel (The Channelnumber (number if)
    • +
    • humidity (0-100)
    • +
    • state (T:x.xx H:y B:z)
    • +
    • temperature (°C)
    • +
      - Hideki only - +
    • comfort_level (Status: Humidity OK... , Wet. More than 69% RH, Dry. Less than 40% RH, Temperature and humidity comfortable)
    • +
    • package_number (reflect the package number in the stream starting at 1)

    - - + + Set
      N/A

    @@ -506,16 +561,16 @@ sub wind {
  • ignore
  • readingFnAttributes
  • showtime
  • -
  • windDirCorr
    - correction value of your displayed wind direction deztimal degree value. The correction value is added to the measured direction in dgrees.
    - Example value: 5
    - Default value: 0
    -
  • -
  • windSpeedCorr
    - correction value of your displayed wind speed as floatingpoint value. The measured speed is multiplied with the specified value. The value 0 disables the feature.
    - Example value: 1.25
    - Default value: 1
    -
  • +
  • windDirCorr
    + correction value of your displayed wind direction deztimal degree value. The correction value is added to the measured direction in dgrees.
    + Example value: 5
    + Default value: 0
    +
  • +
  • windSpeedCorr
    + correction value of your displayed wind speed as floatingpoint value. The measured speed is multiplied with the specified value. The value 0 disables the feature.
    + Example value: 1.25
    + Default value: 1
    +

  • @@ -529,46 +584,46 @@ sub wind {
      Das Hideki module dekodiert empfangene Nachrichten von Wettersensoren, welche das Hideki Protokoll verwenden.

      - + Unterstützte Hersteller
        -
      • Arduinos with remote Sensor lib from Randy Simons
      • -
      • Bresser
      • -
      • Cresta
      • -
      • Hama
      • -
      • Hideki (Anemometer | UV sensor | Rain level meter | Thermo/hygro-sensor)
      • -
      • TFA Dostman
      • -
      • Alle anderen, welche das Hideki Protokoll verwenden
      • +
      • Arduinos with remote Sensor lib from Randy Simons
      • +
      • Bresser
      • +
      • Cresta
      • +
      • Hama
      • +
      • Hideki (Anemometer | UV sensor | Rain level meter | Thermo/hygro-sensor)
      • +
      • TFA Dostman
      • +
      • Alle anderen, welche das Hideki Protokoll verwenden
      Hinweis, Aktuell sind nur temp/feuchte Sensoren implementiert. Bitte sendet uns Daten zu anderen Sensoren.

      - + Define
        - define <name> Hideki <code> + define <name> Hideki <code>

        -
      • +
      • <code> besteht aus dem Sensortyp und der Kanalnummer (1..5) oder wenn das Attribut longid im IO Device gesetzt ist aus einer Zufallsadresse, die durch den Sensor beim einlegen der - Batterie generiert wird (Die Adresse ändert sich bei jedem Batteriewechsel).
        + Batterie generiert wird (Die Adresse ändert sich bei jedem Batteriewechsel).
      • Wenn autocreate aktiv ist, dann wird der Sensor automatisch in FHEM angelegt. Das ist der empfohlene Weg, neue Sensoren hinzuzufügen.
      • - +

      Generierte Readings
        -
      • battery & batteryState (ok oder low)
      • -
      • channel (Der Sensor Kanal)
      • -
      • humidity (0-100)
      • -
      • state (T:x.xx H:y B:z)
      • -
      • temperature (°C)
      • +
      • battery & batteryState (ok oder low)
      • +
      • channel (Der Sensor Kanal)
      • +
      • humidity (0-100)
      • +
      • state (T:x.xx H:y B:z)
      • +
      • temperature (°C)
      • -
        - Hideki spezifisch - -
      • comfort_level (Status: Humidity OK... , Wet größer 69% RH, Dry weniger als 40% RH, Temperature and humidity comfortable)
      • -
      • package_number (Paketnummer in der letzten Signalfolge, startet bei 1)

      • +
        - Hideki spezifisch - +
      • comfort_level (Status: Humidity OK... , Wet größer 69% RH, Dry weniger als 40% RH, Temperature and humidity comfortable)
      • +
      • package_number (Paketnummer in der letzten Signalfolge, startet bei 1)

      Set
        N/A

      @@ -641,7 +696,8 @@ sub wind { }, "develop": { "requires": { - "POSIX": "0" + "POSIX": "0", + "Data::Dumper": 0 } } },