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

14_Hideki.pm: refactored code, fixed bug

git-svn-id: https://svn.fhem.de/fhem/trunk@27953 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
sidey79 2023-09-12 20:08:07 +00:00
parent 3f44ee0fab
commit d791459b9d
2 changed files with 441 additions and 383 deletions

View File

@ -1,5 +1,7 @@
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
# Do not insert empty lines here, update check depends on it. # Do not insert empty lines here, update check depends on it.
- 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 - feature: SD_ProtocolData.pm: Protocol 122 for Temola TM 40 thermometer
Protocol 119 Basic funkbus suppors Protocol 119 Basic funkbus suppors
Protocol 85 new sensor TFA 30.3251.10 Protocol 85 new sensor TFA 30.3251.10

View File

@ -7,34 +7,43 @@
# S. Butzek, HJGode, Ralf9 2015-2017 # S. Butzek, HJGode, Ralf9 2015-2017
# S. Butzek 2018-2022 # 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; package main;
#use version 0.77; our $VERSION = version->declare('v3.4.3'); #use version 0.77; our $VERSION = version->declare('v3.4.3');
use strict; use strict;
use warnings; use warnings;
use POSIX; use POSIX;
use FHEM::Meta; use FHEM::Meta;
eval {use Data::Dumper qw(Dumper);1};
#use Data::Dumper; #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->{Match} = qr/^P12#75[A-F0-9]{14,30}/; # Laenge (Anhahl nibbles nach 0x75 )noch genauer spezifizieren
$hash->{DefFn} = \&Hideki_Define; $hash->{DefFn} = \&Hideki_Define;
$hash->{UndefFn} = \&Hideki_Undef; $hash->{UndefFn} = \&Hideki_Undef;
$hash->{ParseFn} = \&Hideki_Parse; $hash->{ParseFn} = \&Hideki_Parse;
$hash->{AttrList} = "do_not_notify:0,1 showtime:0,1" $hash->{AttrList} = 'do_not_notify:0,1 showtime:0,1'
." ignore:0,1" .' ignore:0,1'
." windDirCorr windSpeedCorr" .' windDirCorr windSpeedCorr'
." $readingFnAttributes"; ." $readingFnAttributes";
$hash->{AutoCreate}= $hash->{AutoCreate}=
{ "Hideki.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME", GPLOT => "temp4hum4:Temp/Hum,", autocreateThreshold => "2:180"} }; { "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($)
} }
##################################### my %comfortLevel = (
sub 0 => q[Hum. OK. Temp. uncomfortable (>24.9 or <20)],
Hideki_Define($$) 1 => q[Wet. More than 69% RHWet. More than 69% RH],
{ 2 => q[Dry. Less than 40% RH],
my ($hash, $def) = @_; 3 => q[Temp. and Hum. comfortable]
my @a = split("[ \t][ \t]*", $def); );
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 <name> Hideki <code>".int(@a) return "wrong syntax: define <name> Hideki <code>".int(@a)
if(int(@a) < 3); if(int(@a) < 3);
$hash->{CODE} = $a[2]; $hash->{CODE} = $a[2];
$hash->{lastMSG} = ""; $hash->{lastMSG} = '';
my $name= $hash->{NAME}; my $name= $hash->{NAME};
$modules{Hideki}{defptr}{$a[2]} = $hash; $modules{Hideki}{defptr}{$a[2]} = $hash;
#$hash->{STATE} = "Defined";
#AssignIoPort($hash); return;
return undef;
} }
##################################### #####################################
sub sub Hideki_Undef {
Hideki_Undef($$)
{
my ($hash, $name) = @_; 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}); delete($modules{Hideki}{defptr}{$hash->{CODE}}) if($hash && $hash->{CODE});
return undef; return;
} }
##################################### #####################################
sub sub Hideki_Parse {
Hideki_Parse($$) my ($iohash,$msg) = @_;
{ carp qq[Hideki_Parse, too few arguments ($iohash, $msg)] if @_ < 2;
my ($iohash,$msg) = @_; (ref $iohash ne 'HASH') // return q[no hash provided];
my (undef ,$rawData) = split("#",$msg);
my $ioname = $iohash->{NAME}; my (undef ,$rawData) = split(/#/,$msg);
my @a = split("", $msg); my $ioname = $iohash->{NAME};
Log3 $iohash, 4, "$ioname Hideki_Parse: incomming $msg"; my @a = split(//, $msg);
Log3 $iohash, 4, "$ioname Hideki_Parse: incomming $msg";
my @decodedData; my @decodedData;
my $crc1crc2OK = 0; my $crc1crc2OK = 0;
($crc1crc2OK, @decodedData) = decryptAndCheck($iohash, $rawData); # use unencrypted rawdata ($crc1crc2OK, @decodedData) = decryptAndCheck($iohash, $rawData); # use unencrypted rawdata
if ($crc1crc2OK == 0) {
return ''; #crc1 or crc2 failed
}
# decrypt and decodedBytes are now done with decryptAndCheck if ($crc1crc2OK == 0) {
my $decodedString = join '', unpack('H*', pack('C*',@decodedData)); # get hex string return ''; #crc1 or crc2 failed
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";
my $id=substr($decodedString,2,2); # get the random id from the data # decrypt and decodedBytes are now done with decryptAndCheck
my $channel=0; my $decodedString = join '', unpack('H*', pack('C*',@decodedData)); # get hex string
my $temp=""; Log3 $iohash, 4, "$ioname Hideki_Parse: raw=$rawData, decoded=$decodedString";
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
if ($comfort == 0) { $comfort = 'Hum. OK. Temp. uncomfortable (>24.9 or <20)' } if (!@decodedData) {
elsif ($comfort == 1) { $comfort = 'Wet. More than 69% RH' } Log3 $iohash, 4, "$ioname Hideki_Parse: decrypt failed";
elsif ($comfort == 2) { $comfort = 'Dry. Less than 40% RH' } return '';
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;
}
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}; my $id=substr($decodedString,2,2); # get the random id from the data
$def = $modules{Hideki}{defptr}{$deviceCode} if(!$def); my $deviceCode;
my $model= qq[Hideki_$sensorTyp];
if(!$def) { ## 1. Detect what type of sensor we have, then call specific function to decode
Log3 $iohash, 1, "$ioname Hideki: UNDEFINED sensor $deviceCode detected, code $msg"; if ( !exists $allSensorTypes{$sensorTyp} ) {
return "UNDEFINED $deviceCode Hideki $deviceCode"; Log3 $iohash, 4, qq[$ioname Sensor type $sensorTyp not supported, please report sensor information!];
} #return q[];
};
my $hash = $def; # Build sensordecoder based on type
my $name = $hash->{NAME}; my $sensorDecoder = $allSensorTypes{$sensorTyp};
return "" if(IsIgnored($name));
#Log3 $name, 4, "Hideki: $name ($msg)"; # Get values from decoder
my %sensorData;
if ($sensorTyp == 12) { # Wind foreach my $key ( keys %{ $sensorDecoder } )
($windchill,$windspeed,$windgust,$winddir,$winddirdeg,$winddirtext) = wind($name, \@decodedData); {
$val = "T: $temp Ws: $windspeed Wg: $windgust Wd: $winddirtext"; next if (ref $sensorDecoder->{$key} ne q[CODE]);
Log3 $name, 4, "$ioname $name Parse: model=12(wind), T: $temp, Wc=$windchill, Ws=$windspeed, Wg=$windgust, Wd=$winddir, WdDeg=$winddirdeg, Wdtxt=$winddirtext"; $sensorData{$key} = $sensorDecoder->{$key}->(\@decodedData);
} }
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();
$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); Log3 $iohash, 5, "$ioname Hideki_Parse deviceCode: $deviceCode";
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);
}
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 # decryptAndCheck
# input is raw data (array of bytes) # input is raw data (array of bytes)
# output is true if check1 and check2 OK # output is true if check1 and check2 OK
# data will then hold the decrypted data # data will then hold the decrypted data
sub decryptAndCheck { sub decryptAndCheck {
my $iohash = shift; carp qq[decryptAndCheck, too few arguments (iohash, rawData)] if @_ < 2;
my $rawData = shift;
my $name = $iohash->{NAME}; my $iohash = shift;
my $cs1=0; #will be zero for xor over all (bytes[2]>>1)&0x1F except first byte (always 0x75) my $rawData = shift;
my $cs2=0;
my $i; my $name = $iohash->{NAME};
my @data; my $cs1=0; #will be zero for xor over all (bytes[2]>>1)&0x1F except first byte (always 0x75)
@data=map { hex($_) } ($rawData =~ /(..)/g); #byte array from raw hex data string my $cs2=0;
my $i;
#/* Decrypt raw received data byte */ BYTE DecryptByte(BYTE b) { return b ^ (b << 1); } my @data;
my $count=( ($data[2] ^ ($data[2]<<1)) >>1 ) & 0x1f; @data=map { hex($_) } ($rawData =~ /(..)/gx); #byte array from raw hex data string
my $L = scalar @data;
if ($L <= $count+2) { #/* Decrypt raw received data byte */ BYTE DecryptByte(BYTE b) { return b ^ (b << 1); }
Log3 $iohash, 4, "$name Hideki_crc: rawdata=$rawData to short, count=$count data length=$L"; my $count=( ($data[2] ^ ($data[2]<<1)) >>1 ) & 0x1f;
return (0,@data); my $L = scalar @data;
} if ($L <= $count+2) {
Log3 $iohash, 4, "$name Hideki_crc: rawdata=$rawData to short, count=$count data length=$L";
if($data[0] != 0x75) { return (0,@data);
Log3 $iohash, 4, "$name Hideki_Parse: rawData=$rawData is no Hideki"; }
return (0,@data);
} if($data[0] != 0x75) {
Log3 $iohash, 4, "$name Hideki_Parse: rawData=$rawData is not Hideki";
#iterate over data only, first byte is 0x75 always return (0,@data);
# read bytes 1 to n-2 , just before checksum }
for ($i=1; $i<($count+2); $i++) {
$cs1 ^= $data[$i]; # calc first chksum #iterate over data only, first byte is 0x75 always
$cs2 = Hideki_SecondCheck($data[$i] ^ $cs2); # read bytes 1 to n-2 , just before checksum
$data[$i] ^= (($data[$i] << 1) & 0xFF); # decrypt byte at $i without overflow for my $i (1..$count+1) {
} $cs1 ^= $data[$i]; # calc first chksum
$cs2 = Hideki_SecondCheck($data[$i] ^ $cs2);
$count += 2; $data[$i] ^= (($data[$i] << 1) & 0xFF); # decrypt byte at $i without overflow
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); $count += 2;
} else { if ($cs1 != 0 || $cs2 != $data[$count]) {
Log3 $iohash, 4, "$name Hideki crcCheck ok: cs1/cs2 $cs1/$cs2, rawData=$rawData, count+2=$count, length=$L"; Log3 $iohash, 4, "$name Hideki crcCheck FAILED: cs1 / cs2/checksum2 $cs1 / $cs2/$data[$count], rawData=$rawData, count+2=$count, length=$L";
} return (0, @data);
return (1, @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 */ # /* 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 $b = shift;
my $c = 0; my $c = 0;
if (($b & 0x80) == 0x80){ if (($b & 0x80) == 0x80){
$b^=0x95; $b^=0x95;
@ -306,139 +340,160 @@ sub Hideki_SecondCheck{
} }
#####################################
# return decoded sensor type # return decoded sensor type
# in: one byte # in: one byte
# out: one byte # out: one byte
# Der Typ eines Sensors steckt in Byte 3: # Der Typ eines Sensors steckt in Byte 3:
# Byte3 & 0x1F Device # Byte3 & 0x1F Device
# 0x0C Anemometer # 0x0C Anemometer
# 0x0D UV sensor # 0x0D UV sensor
# 0x0E Rain level meter # 0x0E Rain level meter
# 0x1E Thermo/hygro-sensor # 0x1E Thermo/hygro-sensor
# 0x1F Thermo sensor # 0x1F Thermo sensor
sub getSensorType{ sub getSensorType {
return ($_[0] & 0x1F); 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); # getters for serval values from the decrypted hexdata
# output <return code>, <channel>, <temperature> # input:hashref of hex value of received message
# was unable to get this working with an array ref as input, so switched to hex string input # output specific value
sub decodeThermo {
my @Hidekibytes = @{$_[0]};
#my $Hidekihex = shift; sub getTemperature {
#my @Hidekibytes=(); my $decodedData= shift // carp q[no bytes specified];
#for (my $i=0; $i<(length($Hidekihex))/2; $i++){ my $temp = 100 * ($decodedData->[5] & 0x0f) + 10 * ($decodedData->[4] >> 4) + ($decodedData->[4] & 0x0f);
# my $hex=hex(substr($Hidekihex, $i*2, 2)); ## Mit split und map geht es auch ... $str =~ /(..?)/g; ## // temp is negative?
# push (@Hidekibytes, $hex); if (!($decodedData->[5] & 0x80)) { $temp = -$temp; }
#}
my $channel=0;
my $temp=0;
$channel = $Hidekibytes[1] >> 5; return $temp = $temp / 10;
# //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;
}
$temp = $temp / 10; sub getChannel {
return ($channel, $temp); 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 sub getWinddir {
# input: decrypted byte array starting with 0x75, passed by reference as in mysub(\@array); my $decodedData = shift // carp q[no bytes specified];
# output <return code>, <channel>, <totalrain> my @wd=(0, 15, 13, 14, 9, 10, 12, 11, 1, 2, 4, 3, 8, 7, 5, 6);
# was unable to get this working with an array ref as input, so switched to hex string input
sub decodeRain {
my @Hidekibytes = @{$_[0]};
#my $Hidekihex = shift; return $wd[$decodedData->[11] >> 4];
#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);
} }
# P12#758BB244074007400F00001C6E7A01 sub getWinddirtext {
sub wind { my $decodedData = shift // carp q[no bytes specified];
my $name = shift;
my @Hidekibytes = @{$_[0]}; return $winddir_name[getWinddir($decodedData)];
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 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; 1;
=pod =pod
@ -455,13 +510,13 @@ sub wind {
<a name="Hideki_define"></a> <a name="Hideki_define"></a>
<b>Supported Brands</b> <b>Supported Brands</b>
<ul> <ul>
<li>Arduinos with remote Sensor lib from Randy Simons</li> <li>Arduinos with remote Sensor lib from Randy Simons</li>
<li>Bresser</li> <li>Bresser</li>
<li>Cresta</li> <li>Cresta</li>
<li>Hama</li> <li>Hama</li>
<li>Hideki (Anemometer | UV sensor | Rain level meter | Thermo/hygro-sensor)</li> <li>Hideki (Anemometer | UV sensor | Rain level meter | Thermo/hygro-sensor)</li>
<li>TFA Dostman</li> <li>TFA Dostman</li>
<li>all other devices, which use the Hideki protocol</li> <li>all other devices, which use the Hideki protocol</li>
</ul> </ul>
Please note, currently temp/hum devices are implemented. Please report data for other sensortypes.<br><br> Please note, currently temp/hum devices are implemented. Please report data for other sensortypes.<br><br>
@ -472,26 +527,26 @@ sub wind {
<br> <br>
<li>&lt;code&gt; is the address of the sensor device and <li>&lt;code&gt; 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 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).</li><br> the battery (this adress will change every time changing the battery).</li><br>
<li>If autocreate is enabled, the device will be defined via autocreate. This is also the preferred mode of defining such a device.</li><br><br> <li>If autocreate is enabled, the device will be defined via autocreate. This is also the preferred mode of defining such a device.</li><br><br>
</ul> </ul>
<a name="Hideki_readings"></a> <a name="Hideki_readings"></a>
<b>Generated readings</b> <b>Generated readings</b>
<ul> <ul>
<li>battery & batteryState (ok or low)</li> <li>battery & batteryState (ok or low)</li>
<li>channel (The Channelnumber (number if)</li> <li>channel (The Channelnumber (number if)</li>
<li>humidity (0-100)</li> <li>humidity (0-100)</li>
<li>state (T:x.xx H:y B:z)</li> <li>state (T:x.xx H:y B:z)</li>
<li>temperature (&deg;C)</li> <li>temperature (&deg;C)</li>
<br><i>- Hideki only -</i> <br><i>- Hideki only -</i>
<li>comfort_level (Status: Humidity OK... , Wet. More than 69% RH, Dry. Less than 40% RH, Temperature and humidity comfortable)</li> <li>comfort_level (Status: Humidity OK... , Wet. More than 69% RH, Dry. Less than 40% RH, Temperature and humidity comfortable)</li>
<li>package_number (reflect the package number in the stream starting at 1)</li><br> <li>package_number (reflect the package number in the stream starting at 1)</li><br>
</ul> </ul>
<a name="Hideki_unset"></a> <a name="Hideki_unset"></a>
<b>Set</b> <ul>N/A</ul><br> <b>Set</b> <ul>N/A</ul><br>
@ -506,16 +561,16 @@ sub wind {
<li><a href="#ignore">ignore</a></li> <li><a href="#ignore">ignore</a></li>
<li><a href="#readingFnAttributes">readingFnAttributes</a></li> <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
<li><a href="#showtime">showtime</a></li> <li><a href="#showtime">showtime</a></li>
<li><a name="windDirCorr"></a>windDirCorr<br> <li><a name="windDirCorr"></a>windDirCorr<br>
correction value of your displayed wind direction deztimal degree value. The correction value is added to the measured direction in dgrees.<br> correction value of your displayed wind direction deztimal degree value. The correction value is added to the measured direction in dgrees.<br>
Example value: 5<br> Example value: 5<br>
Default value: 0<br> Default value: 0<br>
</li> </li>
<li><a name="windSpeedCorr"></a>windSpeedCorr<br> <li><a name="windSpeedCorr"></a>windSpeedCorr<br>
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.<br> 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.<br>
Example value: 1.25<br> Example value: 1.25<br>
Default value: 1<br> Default value: 1<br>
</li> </li>
</ul> </ul>
<br> <br>
</ul> </ul>
@ -529,46 +584,46 @@ sub wind {
<ul> <ul>
Das Hideki module dekodiert empfangene Nachrichten von Wettersensoren, welche das Hideki Protokoll verwenden. Das Hideki module dekodiert empfangene Nachrichten von Wettersensoren, welche das Hideki Protokoll verwenden.
<br><br> <br><br>
<a name="Hideki_define"></a> <a name="Hideki_define"></a>
<b>Unterst&uuml;tzte Hersteller</b> <b>Unterst&uuml;tzte Hersteller</b>
<ul> <ul>
<li>Arduinos with remote Sensor lib from Randy Simons</li> <li>Arduinos with remote Sensor lib from Randy Simons</li>
<li>Bresser</li> <li>Bresser</li>
<li>Cresta</li> <li>Cresta</li>
<li>Hama</li> <li>Hama</li>
<li>Hideki (Anemometer | UV sensor | Rain level meter | Thermo/hygro-sensor)</li> <li>Hideki (Anemometer | UV sensor | Rain level meter | Thermo/hygro-sensor)</li>
<li>TFA Dostman</li> <li>TFA Dostman</li>
<li>Alle anderen, welche das Hideki Protokoll verwenden</li> <li>Alle anderen, welche das Hideki Protokoll verwenden</li>
</ul> </ul>
Hinweis, Aktuell sind nur temp/feuchte Sensoren implementiert. Bitte sendet uns Daten zu anderen Sensoren.<br><br> Hinweis, Aktuell sind nur temp/feuchte Sensoren implementiert. Bitte sendet uns Daten zu anderen Sensoren.<br><br>
<a name="Hideki_define"></a> <a name="Hideki_define"></a>
<b>Define</b> <b>Define</b>
<ul> <ul>
<code>define &lt;name&gt; Hideki &lt;code&gt; </code> <code>define &lt;name&gt; Hideki &lt;code&gt; </code>
<br><br> <br><br>
<li> <li>
&lt;code&gt; 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 &lt;code&gt; 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 &auml;ndert sich bei jedem Batteriewechsel).<br> Batterie generiert wird (Die Adresse &auml;ndert sich bei jedem Batteriewechsel).<br>
</li> </li>
<li>Wenn autocreate aktiv ist, dann wird der Sensor automatisch in FHEM angelegt. Das ist der empfohlene Weg, neue Sensoren hinzuzuf&uumlgen.</li> <li>Wenn autocreate aktiv ist, dann wird der Sensor automatisch in FHEM angelegt. Das ist der empfohlene Weg, neue Sensoren hinzuzuf&uumlgen.</li>
</ul> </ul>
<br> <br>
<a name="Hideki_readings"></a> <a name="Hideki_readings"></a>
<b>Generierte Readings</b> <b>Generierte Readings</b>
<ul> <ul>
<li>battery & batteryState (ok oder low)</li> <li>battery & batteryState (ok oder low)</li>
<li>channel (Der Sensor Kanal)</li> <li>channel (Der Sensor Kanal)</li>
<li>humidity (0-100)</li> <li>humidity (0-100)</li>
<li>state (T:x.xx H:y B:z)</li> <li>state (T:x.xx H:y B:z)</li>
<li>temperature (&deg;C)</li> <li>temperature (&deg;C)</li>
<br><i>- Hideki spezifisch -</i> <br><i>- Hideki spezifisch -</i>
<li>comfort_level (Status: Humidity OK... , Wet gr&ouml;&szlig;er 69% RH, Dry weniger als 40% RH, Temperature and humidity comfortable)</li> <li>comfort_level (Status: Humidity OK... , Wet gr&ouml;&szlig;er 69% RH, Dry weniger als 40% RH, Temperature and humidity comfortable)</li>
<li>package_number (Paketnummer in der letzten Signalfolge, startet bei 1)</li><br> <li>package_number (Paketnummer in der letzten Signalfolge, startet bei 1)</li><br>
</ul> </ul>
<a name="Hideki_unset"></a> <a name="Hideki_unset"></a>
<b>Set</b> <ul>N/A</ul><br> <b>Set</b> <ul>N/A</ul><br>
@ -641,7 +696,8 @@ sub wind {
}, },
"develop": { "develop": {
"requires": { "requires": {
"POSIX": "0" "POSIX": "0",
"Data::Dumper": 0
} }
} }
}, },