package main; ########################### # 87_ws2000.pm # Modul for FHEM # # contributed by thomas dressler 2008 # $Id$ # corr. negativ temps / peterp ########################### use strict; use Switch; use warnings; #prototypes to make komodo happy use vars qw{%attr %defs}; sub Log($$); our $FH; #################################### # WS2000_Initialize # Implements Initialize function # sub WS2000_Initialize($) { my ($hash) = @_; # Provider #$hash->{WriteFn} = "ws2000_Write"; # $hash->{Clients} = ":WS2000Rain:WS2000Wind:WS2000Indoor:WS2000Lux:WS2000Pyro:WS2000Temp:WS2000TempHum"; # Consumer $hash->{DefFn} = "WS2000_Define"; $hash->{UndefFn} = "WS2000_Undef"; $hash->{GetFn} = "WS2000_Get"; $hash->{SetFn} = "WS2000_Set"; $hash->{ReadyFn} = "WS2000_Ready"; $hash->{ReadFn} ="WS2000_Read"; $hash->{ListFn} ="WS2000_List"; $hash->{AttrList}= "model:WS2000 rain altitude loglevel:0,1,2,3,4,5"; } ##################################### # WS2000_Define # Implements DefFn function # sub WS2000_Define($$) { my ($hash, $def) = @_; my @a = split("[ \t][ \t]*", $def); delete $hash->{po}; delete $hash->{socket}; delete $hash->{FD}; my $ws2000_cfg='ws2000.cfg'; my $quiet=1; my $name=$hash->{NAME}; my $PortName = $a[2]; my $PortObj; if($PortName eq "none") { Log 1, "WS2000 device is none, commands will be echoed only"; return undef; } Log 4, "WS2000 opening device $PortName"; #switch serial/socket device if ($PortName=~/^\/dev|^COM/) { #normal devices (/dev), on windows COMx my $OS=$^O; if ($OS eq 'MSWin32') { eval ("use Win32::SerialPort;"); if ($@) { $hash->{STATE} = "error using Modul Win32::SerialPort"; Log 1,"Error using Device::SerialPort"; return "Can't use Win32::SerialPort $@\n"; } $PortObj = new Win32::SerialPort ($PortName, $quiet); if (!$PortObj) { $hash->{STATE} = "error opening device"; Log 1,"Error opening Serial Device $PortName"; return "Can't open Device $PortName: $^E\n"; } #$hash->{FD}=$PortObj->{_HANDLE}; $readyfnlist{"$a[0].$a[2]"} = $hash; } else { eval ("use Device::SerialPort;"); if ($@) { $hash->{STATE} = "error using Modul Device::SerialPort"; Log 1,"Error using Device::SerialPort"; return "Can't Device::SerialPort $@\n"; } $PortObj = new Device::SerialPort ($PortName, $quiet); if (!$PortObj) { $hash->{STATE} = "error opening device"; Log 1,"Error opening Serial Device $PortName"; return "Can't open Device $PortName: $^E\n"; } $hash->{FD}=$PortObj->FILENO; $selectlist{"$a[0].$a[2]"} = $hash; } #Parameter 19200,8,2,Odd,None $PortObj->baudrate(19200); $PortObj->databits(8); $PortObj->parity("odd"); $PortObj->stopbits(2); $PortObj->handshake("none"); if (! $PortObj->write_settings) { undef $PortObj; return "Serial write Settings failed!\n"; } $hash->{po}=$PortObj; $hash->{socket}=0; }elsif($PortName=~/([\w.]+):(\d{1,5})/){ #Sockets(hostname:port) my $host=$1; my $port=$2; my $xport=IO::Socket::INET->new(PeerAddr=>$host, PeerPort=>$port, timeout=>1, blocking=>0 ); if (!$xport) { $hash->{STATE} = "error opening device"; Log 1,"Error opening Connection to $PortName"; return "Can't Connect to $PortName -> $@ ( $!)\n"; } $xport->autoflush(1); $hash->{FD}=$xport->fileno; $selectlist{"$a[0].$a[2]"} = $hash; $hash->{socket}=$xport; }else{ $hash->{STATE} = "$PortName is no device and not implemented"; Log 1,"$PortName is no device and not implemented"; return "$PortName is no device and not implemented\n"; } Log 4, "$name connected to device $PortName"; $hash->{STATE} = "open"; $hash->{DeviceName}=$PortName; return undef; } ##################################### # WS2000_Undef # implements UnDef-Function # sub WS2000_Undef($$) { my ($hash, $arg) = @_; my $name = $hash->{NAME}; delete $hash->{FD}; $hash->{STATE}='close'; if ($hash->{socket}) { $hash->{socket}->shutdown(2); $hash->{socket}->close(); }elsif ($hash->{po}) { $hash->{po}->close(); } Log 5, "$name shutdown complete"; return undef; } ##################################### # WS2000_Set # implement SetFn # currently nothing to set # sub WS2000_Ready($$) { my ($hash, $dev) = @_; my $po=$hash->{po}; return undef if !$po; my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags)=$po->status; return ($InBytes>0); } ##################################### # WS2000_Set # implement SetFn # currently nothing to set # sub WS2000_Set($@) { my ($hash, @a) = @_; my $msg; my $name=$a[0]; my $reading= $a[1]; $msg="$name => No Set function ($reading) implemented"; Log 1,$msg; return $msg; } ##################################### # WS2000_Get # implement GetFn # sub WS2000_Get($@) { my ($hash, @a) = @_; my $u1 = "Usage: get [TH0..TH7, T0..T7, I0..I7, R0..R7, W0..W7, L0..L7, P0..P7, LAST, RAW]\n" . "get list\n"; return $u1 if(int(@a) != 2); my $name= $a[0]; my $reading= $a[1]; my $msg; my $retval; my $time; my $sensor=$hash->{READINGS}; if ($reading =~/list/i) { $msg=''; foreach my $s (keys %$sensor) { next if !$s; $msg.="ID:$s, Last Update ".$sensor->{$s}{TIME}."\n"; } }else { if(!defined($sensor->{$reading})) { $msg="Sensor ($reading)not defined, try 'get list'"; }else { $retval=$sensor->{$reading}{VAL}; $time=$sensor->{$reading}{TIME}; $retval=unpack("H*",$retval) if ($reading eq 'RAW'); $msg= "$name $reading ($time) => $retval"; } } return $msg; } ##################################### # WS2000_Write # currently dummy # sub WS2000_Write($$) { my ($hash,$msg) = @_; } ##################################### # WS2000_Read # Implements ReadFn, called from global select # sub WS2000_Read($$) { my ($hash) = @_; my $STX=2; my $ETX=3; my $retval=''; my $out=undef; my $byte; my $name=$hash->{NAME}; my $xport=$hash->{socket}; my $PortObj=$hash->{po}; my $message=$hash->{PARTIAL}||''; my $status=$hash->{STEP}; #read data(1 byte), because fhem select flagged data available if ($xport) { $xport->read($out,1); }elsif($PortObj) { $out = $PortObj->read(1); } return if(!defined($out) || length($out) == 0) ; Log 5, "$name => WS2000/RAW: " . unpack('H*',$out); #check for frame: STX TYP W1 W2 W3 W4 W5 ETX $byte=ord($out); if($byte eq $STX) { #Log 4, "M232: return value \'" . $retval . "\'"; $status= "STX"; $message=$out; Log 5, "WS2000 STX received"; } elsif($byte eq $ETX) { $status= "ETX"; $message .=$out; Log 5, "WS2000 ETX received"; } elsif ($status eq "STX"){ $message .=$out; } $hash->{STEP}=$status; $hash->{PARTIAL}=$message; if($status eq "ETX") { WS2000_Parse($hash,$message); } } ##################################### # WS2000_Parse # decodes complete frame # called directly from WS2000_Read sub WS2000_Parse($$) { my ($hash,$msg) = @_; my ($stx,$typ,$w1,$w2,$w3,$w4,$w5,$etx)=map {$_ & 0x7F} unpack("U*",$msg); my $tm=TimeNow(); my $name=$hash->{NAME}; my $factor=$attr{$name}{rain}||366; my $altitude=$attr{$name}{altitude}||0; if ($etx != 3) { Log 4, "$name:Frame Error!"; return undef; } my ($sensor,$daten1,$einheit1,$daten2,$einheit2,$daten3,$einheit3,$result,$shortname,$val, $unit); my $group = ($typ & 0x70)/16 ;#/slash for komodo syntax checker! my $snr = $typ % 16; #duplicate check (repeater?) my $prevmsg=$hash->{READINGS}{RAW}{VAL}||''; my $prevtime=$hash->{READINGS}{RAW}{TIME}||0; if (($prevmsg eq $msg) && ((time() - $prevtime) <10)) { Log 4,"$name check: Duplicate detected"; return undef; } my $rawtext="Typ:$typ,W1:$w1,W2:$w2,W3:$w3,W4:$w4,W5:$w5"; Log 4, "$name parsing: $rawtext"; #break into sensor specs switch ( $group ){ case 7 { $sensor = "Fernbedienung"; $shortname='FB'; $einheit1='(CODE)'; $daten1 = $w1 * 10000 + $w2 * 1000 + $w3 * 100 + $w4 * 10 + $w5; $result = $shortname . " => D=" . $daten1 . $einheit1; } case 0 { if ($snr < 8) { $sensor = "Temperatursensor V1.1(" . $snr . ")"; }else{ $snr -= 8; $sensor = "Temperatursensor V1.2(" .$snr. ")"; } $daten1 = (($w1 * 128 + $w2) ); if ($daten1 >= 16085) { $daten1 = $daten1 - 16384; } $daten1 = $daten1 / 10; $shortname='TX'.$snr; $einheit1 = " C"; $result = $shortname . " => T:" . $daten1 . $einheit1; } case 1 { if ($snr <8) { $sensor = "Temperatursensor mit Feuchte V1.1(" . $snr . ")"; }else{ $snr -= 8; $sensor = "Temperatursensor mit Feuchte V1.2(" . $snr . ")"; } $daten1 = (($w1 * 128 + $w2) ); if ($daten1 >= 16085) { $daten1 = $daten1 - 16384; } $daten1 = $daten1 / 10; $shortname='TH'.$snr; $einheit1 = " C"; $daten2 = $w3; $daten3 = 0; $einheit2 = " %"; $result = $shortname . " => T:" . $daten1 . $einheit1 . ", H:" . $daten2 .$einheit2; } case 2 { if ( $snr < 8 ) { $sensor = "Regensensor V1.1(" . $snr . ")"; }else{ $snr -= 8; $sensor = "Regensensor V1.2(" . $snr . ")" } $shortname='R'.$snr; $daten1 = ($w1 * 128 + $w2); $einheit1= ' Imp'; my $prev=$hash->{READINGS}{$shortname}{VAL}; if ($prev && $prev=~/C=(\d+)/i) { $prev=$1; }else { $prev=0; } my $diff=$daten1-$prev; $daten2= $diff * $factor/1000; $einheit2 = " l/m2"; $result = $shortname . " => M:".$daten2. $einheit2."(". $diff . $einheit1 ." x Faktor $factor)" . ", C:$daten1, P:$prev" ; } case 3 { if ($snr < 8) { $sensor = "Windsensor V1.1(" . $snr . ")"; }else{ $snr -= 8; $sensor = "Windsensor V1.2(" . $snr . ")"; } switch( $w3) { case 0 { $daten3 = 0;} case 1 { $daten3 = 22.5;} case 2 { $daten3 = 45;} case 3 { $daten3 = 67.5;} } $einheit3 = " +/-"; $daten1 = ($w1 * 128 + $w2) / 10; $daten2 = $w4 * 128 + $w5; $einheit1 = " km/h"; $einheit2 = " Grad"; $shortname='W'.$snr; my @wr=("N","NNO","NO","ONO","O","OSO","SO","SSO","S","SSW","SW","WSW","W","WNW","NW","NNW"); my @bf=(0,0.7,5.4,11.9,19.4,38.7,49.8,61.7,74.6,88.9,102.4,117.4); my @bfn=("Windstille","leiser Zug","leichte Brise","schwache Brise","maessige Brise","frische Brise", "starker Wind","steifer Wind","stuermischer Wind","Sturm","schwerer Sturm","orkanartiger Sturm","Orkan"); my $i=1; foreach (1..$#bf) { if ($daten1<$bf[$i]) { last; } $i++; } $i--; #windrichtung my $w=int($daten2/22.5+0.5); if ($w ==16) {$w=0;} $result = $shortname . " => S:" . $daten1 . $einheit1 . ", BF:$i($bfn[$i])" . " ,R:" . $daten2 . $einheit2 . "($wr[$w])".$einheit3. $daten3; } case 4 { if ($snr < 8) { $sensor = "Innensensor V1.1(" . $snr . ")"; }else{ $snr -= 8; $sensor = "Innensensor V1.2(" . $snr . ")"; } $daten1 = (($w1 * 128 + $w2) ); if ($daten1 >= 16085) { $daten1 = $daten1 - 16384; } $daten1 = $daten1 / 10; $shortname='I'.$snr; $daten2 = $w3; $daten3 = $w4 * 128 + $w5; $einheit1 = " C"; $einheit2 = " %"; $einheit3 = " hPa"; $result = $shortname . " => T:" . $daten1 . $einheit1 . ", H:" . $daten2 . $einheit2 . ", D:" . $daten3 . $einheit3; } case 5 { $snr -= 8 if $snr>7;; #only V1.2 sensors exists $sensor = "Helligkeitssensor V1.2(" . $snr . ")"; $shortname='L'.$snr; switch ($w3) { case 0 {$daten1 = 1;} case 1 {$daten1 = 10;} case 2 {$daten1 = 100;} case 3 {$daten1 = 1000;} } $daten1 = $daten1 * ($w1 * 128 + $w2); $einheit1 = "Lux"; $result = $shortname . " => L:" . $daten1 . $einheit1; } case 6 { #Sensor has been never produced, but maybe there are personal implementations $snr -= 8 if $snr>7; $sensor = "Pyranometer V1.2(" . $snr . ")"; $shortname='P'.$snr; switch ($w3) { case 0 {$daten1 = 1;} case 1 {$daten1 = 10;} case 2 {$daten1 = 100;} case 3 {$daten1 = 1000;} } $daten1 = $daten1 * ($w1 * 128 + $w2); $einheit1 = " W/m2"; $result = $shortname . " => P:" . $daten1 . $einheit1; } else { $shortname="U"; $sensor = "unknown"; $daten1 = $typ; $result = "(Group:" . $group . "/Typ:" . $typ . ")"; Log 1, "$name => Unknown sensor detected". $result }#switch else }#switch #store result Log 4, $name." result:".$result; $rawtext='RAW => '.$rawtext; $hash->{READINGS}{LAST}{VAL}=$result; $hash->{READINGS}{LAST}{TIME}=$tm; $hash->{READINGS}{RAW}{TIME}=time(); $hash->{READINGS}{RAW}{VAL}=$msg; $hash->{READINGS}{$shortname}{VAL}=$result; $hash->{READINGS}{$shortname}{TIME}=$tm; $hash->{STATE}=$result; $hash->{CHANGED}[0] = $result; $hash->{CHANGETIME}[0]=$tm; $hash->{CHANGED}[1] = $rawtext; $hash->{CHANGETIME}[1]=$tm; #notify system DoTrigger($name, undef); return $result; } ##################################### sub WS2000_List($$) { my ($hash,$msg) = @_; $msg=WS2000_Get($hash,$hash->{NAME},'list'); return $msg; } 1;