diff --git a/fhem/FHEM/00_NetzerI2C.pm b/fhem/FHEM/00_NetzerI2C.pm new file mode 100644 index 000000000..7b66a8c52 --- /dev/null +++ b/fhem/FHEM/00_NetzerI2C.pm @@ -0,0 +1,645 @@ +############################################## +# $Id: 00_Netzer_I2C.pm klausw +package main; + +use strict; +use warnings; +use Time::HiRes qw(gettimeofday); + +sub NetzerI2C_Attr(@); +sub NetzerI2C_HandleQueue($); +sub NetzerI2C_Read($); +#sub NetzerI2C_Ready($); +sub NetzerI2C_Write($$); + +#my $clientsI2C = ":I2C_PCF8574:I2C_PCA9532:I2C_BMP180:FHT.*:"; + +#my %matchListI2C = ( +# "1:I2C_PCF8574"=> ".*", +# "2:FHT" => "^81..(04|09|0d)..(0909a001|83098301|c409c401)..", +#); + +my @clients = qw( +I2C_LCD +I2C_DS1307 +I2C_PC.* +I2C_MCP23017 +I2C_BMP180 +I2C_SHT21 +); + +sub NetzerI2C_Initialize($) { + my ($hash) = @_; + + require "$attr{global}{modpath}/FHEM/DevIo.pm"; + +# Provider + $hash->{Clients} = join (':',@clients); + $hash->{ReadFn} = "NetzerI2C_Read"; #wird von der globalen loop aufgerufen (ueber $hash->{FD} gefunden), wenn Daten verfuegbar sind + $hash->{WriteFn} = "NetzerI2C_Write"; #wird vom client per IOWrite($@) aufgerufen + $hash->{ReadyFn} = "NetzerI2C_Ready"; + $hash->{I2CWrtFn} = "NetzerI2C_Write"; #zum testen als alternative fuer IOWrite + +# Normal devices + $hash->{DefFn} = "NetzerI2C_Define"; + $hash->{UndefFn} = "NetzerI2C_Undef"; + $hash->{GetFn} = "NetzerI2C_Get"; + $hash->{SetFn} = "NetzerI2C_Set"; + $hash->{NotifyFn}= "NetzerI2C_Notify"; + $hash->{AttrFn} = "NetzerI2C_Attr"; + $hash->{AttrList}= "do_not_notify:1,0 dummy:1,0 " . + "timeout socat:1,0"; +} +##################################### +sub NetzerI2C_Define($$) { # + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + unless(@a == 3) { + my $msg = "wrong syntax: define NetzerI2C {none | hostname:port}"; + Log3 undef, 2, $msg; + return $msg; + } + + DevIo_CloseDev($hash); + + my $name = $a[0]; + my $dev = $a[2]; + + #$hash->{Clients} = $clientsI2C; + #$hash->{MatchList} = \%matchListI2C; + + if($dev eq "none") { + Log3 $name, 1, "$name device is none, commands will be echoed only"; + $attr{$name}{dummy} = 1; + return undef; + } + $hash->{DeviceName} = $dev; + #my $ret = DevIo_OpenDev($hash, 0, "CUL_DoInit"); + my $ret = DevIo_OpenDev($hash, 0, ""); + return $ret; +} +##################################### +sub NetzerI2C_Notify { # + my ($hash,$dev) = @_; + my $name = $hash->{NAME}; + my $type = $hash->{TYPE}; + if( grep(m/^(INITIALIZED|REREADCFG)$/, @{$dev->{CHANGED}}) ) { + NetzerI2C_forall_clients($hash,\&NetzerI2C_Init_Client,undef);; + } elsif( grep(m/^SAVE$/, @{$dev->{CHANGED}}) ) { + } +} +##################################### +sub NetzerI2C_forall_clients($$$) { # + my ($hash,$fn,$args) = @_; + foreach my $d ( sort keys %main::defs ) { + if ( defined( $main::defs{$d} ) + && defined( $main::defs{$d}{IODev} ) + && $main::defs{$d}{IODev} == $hash ) { + &$fn($main::defs{$d},$args); + } + } + return undef; +} +##################################### +sub NetzerI2C_Init_Client($@) { # + my ($hash,$args) = @_; + if (!defined $args and defined $hash->{DEF}) { + my @a = split("[ \t][ \t]*", $hash->{DEF}); + $args = \@a; + } + my $name = $hash->{NAME}; + Log3 $name,1,"im init client fuer $name "; + my $ret = CallFn($name,"InitFn",$hash,$args); + if ($ret) { + Log3 $name,2,"error initializing '".$hash->{NAME}."': ".$ret; + } +} +##################################### +sub NetzerI2C_Undef($$) { # + my ($hash, $arg) = @_; + my $name = $hash->{NAME}; + + foreach my $d (sort keys %defs) { + if(defined($defs{$d}) && + defined($defs{$d}{IODev}) && + $defs{$d}{IODev} == $hash) + { + Log3 $name, 3, "deleting port for $d"; + delete $defs{$d}{IODev}; + } + } + DevIo_CloseDev($hash); + return undef; +} +##################################### +sub NetzerI2C_Set($@) { # + my ($hash, @a) = @_; + my $name = shift @a; + my $type = shift @a; + my $arg = join(" ", @a); + my @sets = ('writeByte', 'writeByteReg', 'writeBlock'); + + if($type eq "writeByte") { + return "Usage: set $name i2csend [] [...]" + if(!$arg || $arg !~ /^[0-7][0-9A-F](\W[0-9A-F][0-9A-F]){0,64}$/xi); + foreach (@a) { + $_ = hex; + } + my $i2caddr = shift @a; + my %sendpackage = ( i2caddress => $i2caddr, direction => "i2cwrite", data => join(" ", @a), direct=>1 ); + Log3 $hash, 1, "$sendpackage{data}"; + NetzerI2C_Write($hash, \%sendpackage); + } elsif($type eq "writeByteReg") { + return "Usage: set $name writeByteReg [...]" + if(!$arg || $arg !~ /^[0-7][0-9A-F](\W[0-9A-F][0-9A-F]){0,64}$/xi); + foreach (@a) { + $_ = hex; + } + my $i2caddr = shift @a; + my $reg = shift @a; + my %sendpackage = ( i2caddress => $i2caddr, direction => "i2cwrite", reg => $reg, data => join(" ", @a), direct=>1 ); + NetzerI2C_Write($hash, \%sendpackage); + } elsif($type eq "writeBlock") { + return "Usage: set $name writeBlock [...]" + if(!$arg || $arg !~ /^[0-7][0-9A-F](\W[0-9A-F][0-9A-F]){0,64}$/xi); + foreach (@a) { + $_ = hex; + } + my $i2caddr = shift @a; + my $reg = shift @a; + my %sendpackage = ( i2caddress => $i2caddr, direction => "i2cblockwrite", reg => $reg, data => join(" ", @a), direct=>1 ); + NetzerI2C_Write($hash, \%sendpackage); + } else { + return "Unknown argument $type, choose one of " . join(" ", @sets); + } + return undef; +} +##################################### +sub NetzerI2C_Get($@) { # + my ($hash, @a) = @_; + my $nargs = int(@a); + my $name = $hash->{NAME}; + my @gets = ('read'); + unless ( exists($a[1]) && $a[1] ne "?" && grep {/^$a[1]$/} @gets ) { + return "Unknown argument $a[1], choose one of " . join(" ", @gets); + } + + my ($msg, $err); + return "No $a[1] for dummies" if(IsDummy($name)); + + if ($a[1] eq "read") { + return "use: \"get $name $a[1] [ []]\"" if(@a < 3); + return "$name I2C address must be 2-digit hexvalues" unless ($a[2] =~ /^(0x|)(|[0-7])[0-9A-F]$/xi); # && hex($a[2]) % 2 == 0); + return "$name register address must be a hexvalues" if (defined($a[3]) && $a[3] !~ /^(0x|)[0-9A-F]{1,2}$/xi); + return "$name number of bytes must be decimal value" if (defined($a[4]) && $a[4] !~ /^[0-9]{1,2}$/ && $a[4] < 65); + + my $hmsg = chr( (hex( $a[2] ) << 1) + 1 ); #I2C Adresse (read) in Zeichen wandeln + if ( $a[3] ) { #Registeradresse in Hexwerte wandeln + $hmsg .= chr( hex("5C") ) if ( (hex($a[3])) == "00"); #wenn 0x00 gesendet mit 0x5C escapen + $hmsg .= chr( hex($a[3]) ); + } + if ( $a[4] ) { + for(my $n=1; $n<$a[4]; $n++) { #Fuer jedes zu lesende Byte ein Byte rausschicken + $hmsg .= chr( hex("01") ); + } + } + $hmsg .= chr( hex("00") ); #Endezeichen anhaengen + #nur zum testen mit socat####################### + $hmsg =~ s/(.|\n)/sprintf("%.2X ",ord($1))/eg if ( AttrVal($hash->{NAME}, 'socat', 0) == 1 ); + ################################################ + DevIo_SimpleWrite($hash, $hmsg, undef); + + my $buf = undef; + my $timeout = 10; + return $hash->{NAME} . " disconnected" unless $hash->{FD}; + for(;;) { #Werte direkt lesen (mit Timeout) + my $rin = ""; + vec($rin, $hash->{FD}, 1) = 1; + my $nfound = select($rin, undef, undef, $timeout); + last if($nfound <= 0); + my $r = DevIo_DoSimpleRead($hash); + if(!defined($r) || $r ne "") { + $buf = $r; + last; + } + } + if ($buf) { + if ( AttrVal($hash->{NAME}, 'socat', 0) == 0 ) { + $buf =~ s/(.|\n)/sprintf("%.2X ",ord($1))/eg; #empfangene Zeichen in Hexwerte wandeln (fuer Socat auskommentieren) + } else { + chomp($buf); #weg nach testen mit Socat + $buf = uc($buf); #weg nach testen mit Socat + } + my @abuf = split (/ /,$buf); + for (my $i = 1; $i < (defined($a[3])? 3 : 2 ) ; $i++) { #pruefen, ob jedes gesendete Byte ein positives Ack bekommen hat + return "error, no Ack received for $a[$1]; received: $buf" if $abuf[0] ne "FF"; + shift(@abuf); + } + my $rmsg = undef; + my $nrec = int(@abuf); + for (my $j = 0; $j < $nrec ; $j++) { #escape Zeichen fuer 0x00 entfernen + $rmsg .= " " if (defined($rmsg)); + $rmsg .= $abuf[$j] unless( $abuf[$j] eq "5C" && defined($abuf[$j + 1]) && $abuf[$j + 1] eq "00" ); + } + $buf = $rmsg; + } else { + $buf = "no Message received"; + } + return $buf; + } + #$hash->{READINGS}{$a[1]}{VAL} = $msg; + #$hash->{READINGS}{$a[1]}{TIME} = TimeNow(); + #return "$a[0] $a[1] => $msg"; + return undef; +} +##################################### +sub NetzerI2C_DoInit($) { #ausfuehren beim start von devio evtl. loeschen oder reinit von clienten reinbauen + my $hash = shift; + my $name = $hash->{NAME}; + # Reset the counter + delete($hash->{XMIT_TIME}); + delete($hash->{NR_CMD_LAST_H}); + return undef; +} +##################################### +sub NetzerI2C_Write($$) { #wird vom Client aufgerufen + my ($hash, $clientmsg) = @_; + foreach my $av (keys %{$clientmsg}) { Log3 $hash, 5, "$hash->{NAME} vom Clienten: $av= " . $clientmsg->{$av}; } + if ($clientmsg->{direction} && $clientmsg->{i2caddress}) { + if(!$hash->{QQUEUE} || 0 == scalar(@{$hash->{QQUEUE}})) { + $hash->{QQUEUE} = [ $clientmsg ]; + NetzerI2C_SendFromQueue($hash, $clientmsg); + } else { + push(@{$hash->{QQUEUE}}, $clientmsg); + } + } + return undef; +} +##################################### +sub NetzerI2C_SendFromQueue($$) { # + my ($hash, $clientmsg) = @_; + my $name = $hash->{NAME}; + + my (@msg,@adata) = (); + + @adata = split(/ /,$clientmsg->{data}) if defined($clientmsg->{data}); + + if (defined($clientmsg->{reg}) && ($clientmsg->{direction} eq "i2cwrite" && int(@adata) > 1) + || ($clientmsg->{nbyte} && $clientmsg->{nbyte} > 1)) { #klaeren, ob Register sequentiell geschrieben werden + $clientmsg->{smsg} = ( $clientmsg->{direction} eq "i2cwrite" ? int(@adata) : $clientmsg->{nbyte} ) if !$clientmsg->{smsg}; + $clientmsg->{smsgcnt}++; + push(@msg, $clientmsg->{reg} + $clientmsg->{smsgcnt} - 1 ) if ($clientmsg->{reg}); #Registeradresse hochzaehlen wenn vorhanden + push(@msg, $adata[$clientmsg->{smsgcnt} - 1]) if ($clientmsg->{direction} eq "i2cwrite"); + Log3 $hash, 5, $clientmsg->{direction} . " Nachricht zerteilen: ". ( defined($clientmsg->{data}) ? $clientmsg->{data} : "leer" ) ." Teil Nr: " .$clientmsg->{smsgcnt} ." = ". $clientmsg->{smsg}; + } else { #oder alle auf einmal + Log3 $hash, 5, $clientmsg->{direction} . " Nachricht nicht zerteilen: ". ( defined($clientmsg->{data}) ? $clientmsg->{data} : "leer" ) ." Nbytes: " . int(@adata); + push(@msg, $clientmsg->{reg} ) if defined($clientmsg->{reg}); + push(@msg, @adata); + } + + my $hmsg = chr( ( $clientmsg->{i2caddress} << 1 ) + (($clientmsg->{direction} eq "i2cread")? 1 : 0) ); + if ( int(@msg) > 0 ) { + foreach (@msg) { #Daten in Zeichen wandeln + $hmsg .= chr( hex("5C") ) if ( $_ == hex("00") ); #wenn 0x00 gesendet mit 0x5C escapen + $hmsg .= chr( $_ ); + } + } + $hmsg .= chr( hex("00") ); #Endezeichen anhaengen + +#nur zum Testen######## + $clientmsg->{bytecount} = int(@msg) + 1; #Anzahl Nutzdaten + Adressbyte + (my $smsg = $hmsg) =~ s/(.|\n)/sprintf("%.2X ",ord($1))/eg; + Log3 $hash, 5, "$name SendFromQueue: $clientmsg->{direction}, String: $smsg, Hex: $hmsg, NBytes: $clientmsg->{bytecount}"; +####################### + #DevIo_SimpleWrite($hash, $hmsg, undef); + DevIo_SimpleWrite($hash, AttrVal($hash->{NAME}, 'socat', 0) == 1 ? $smsg : $hmsg, undef); #fuer Socat zum testen + NetzerI2C_InternalTimer("RecvTimeout", gettimeofday() + AttrVal($hash->{NAME}, 'timeout', 10), "NetzerI2C_TransceiveTimeout", $hash, 0); +} +##################################### +sub NetzerI2C_HandleQueue($) { # + my $hash = shift; + my $arr = $hash->{QQUEUE}; + if(defined($arr) && @{$arr} > 0) { + shift(@{$arr}) unless $arr->[0]->{smsg} && $arr->[0]->{smsg} > $arr->[0]->{smsgcnt}; #nur auf naechste Botschaft wechseln wenn alle Byte gesendet wurden + if(@{$arr} == 0) { + delete($hash->{QQUEUE}); + return; + } + my $clientmsg = $arr->[0]; + if(defined($clientmsg) && $clientmsg eq "") { + NetzerI2C_HandleQueue($hash) if defined($hash); + } else { + NetzerI2C_SendFromQueue($hash, $clientmsg); + } + } +} +##################################### +sub NetzerI2C_TransceiveTimeout($) {# + #my $hash = shift; + #Hash finden wenn myinternaltimer genutzt wird# + my ($myHash) = @_; # + my $hash = $myHash->{HASH}; # + ############################################### + my $name = $hash->{NAME}; + Log3 $hash, 1, "$name: Timeout I2C response"; + my $arr = $hash->{QQUEUE}; + delete $arr->[0]->{smsg} if $arr->[0]->{smsg}; + NetzerI2C_HandleQueue($hash); +} +##################################### +sub NetzerI2C_Read($) { # called from the global loop, when the select for hash->{FD} reports data + my ($hash) = @_; + my $buf = DevIo_SimpleRead($hash); + return undef if(!defined($buf)); #Aendern???? + #Log3 $hash, 1, "$hash->{NAME} vom I2C empfangen 1: $buf"; + #hier noch abfangen, wenn $buf leer ist + if ( AttrVal($hash->{NAME}, 'socat', 0) == 1 ) { #weg nach testen mit Socat + chomp($buf); + #$buf = hex($buf); + } else { + $buf =~ s/(.|\n)/sprintf("%.2X ",ord($1))/eg #empfangene Zeichen in Hexwerte wandeln -> in wandlung nach Zahl aendern + } + Log3 $hash, 5, "$hash->{NAME} vom I2C empfangen: $buf"; + my @abuf = split (/ /,$buf); + foreach (@abuf) { #weg wenn Zeichen direkt gewandelt werden + $_ = hex; + #Log3 $hash, 1, "$hash->{NAME} vom I2C: $_"; + } + my $name = $hash->{NAME}; + #Log3 $hash, 1, "$hash->{NAME} vom I2C empfangen: $buf"; + + my $arr = $hash->{QQUEUE}; + if(defined($arr) && @{$arr} > 0) { + my $clientmsg = $arr->[0]; + NetzerI2C_RemoveInternalTimer("RecvTimeout", $hash); + my $status = "Ok"; + for (my $i = 0; $i < $clientmsg->{bytecount} ; $i++) { #pruefen, ob jedes gesendete Byte ein positives Ack (FF) bekommen hat + $status = "error" . ($arr->[0]->{smsg} ? "@ reg: ". sprintf("%.2X ",($clientmsg->{reg} + $clientmsg->{smsgcnt} - 1)) :"") if !defined($abuf[0]) || $abuf[0] != 255; + shift(@abuf); + } + my $rmsg = undef; + my $nrec = int(@abuf); + for (my $i = 0; $i < $nrec ; $i++) { #escape Zeichen (0x5C) fuer 0x00 entfernen + $rmsg .= " " if (defined($rmsg)); + #$rmsg .= $abuf[$i] unless( $abuf[$i] eq "5C" && defined($abuf[$i + 1]) && $abuf[$i + 1] eq "00" ); + $rmsg .= $abuf[$i] unless( $abuf[$i] == 92 && defined($abuf[$i + 1]) && $abuf[$i + 1] == 0 ); + } + + if ( $arr->[0]->{smsg} && defined($rmsg) ) { #wenn Nachricht Teil einer Nachrichtenfolge, dann Daten anhaengen + $clientmsg->{received} .= ( defined($arr->[0]->{smsg}) && $arr->[0]->{smsg} == 1 ? "" : " ") . $rmsg; + } else { + $clientmsg->{received} = $rmsg; + } + unless ( $arr->[0]->{smsg} && $arr->[0]->{smsg} > $arr->[0]->{smsgcnt} && $status eq "Ok" ) { #erst senden, wenn Transfer abgeschlossen oder bei Fehler + delete $arr->[0]->{smsg} if $arr->[0]->{smsg} && $status ne "Ok"; #aktuellen Einzeltransfer durch loeschen der Botschaftszahl abbrechen + #$clientmsg->{received} = $rmsg if defined($rmsg); + $clientmsg->{$name . "_" . "RAWMSG"} = $buf; + $clientmsg->{$name . "_" . "SENDSTAT"} = $status; + if ($clientmsg->{direct}) { #Vorgang wurde von diesem Modul ausgeloest + $hash->{direct_send} = $clientmsg->{data}; + $hash->{direct_answer} = $clientmsg->{$name . "_" . "RAWMSG"}; + $hash->{direct_I2Caddr} = $clientmsg->{i2caddress}; + $hash->{direct_SENDSTAT} = $status; + } + ########################################### neue Variante zum senden an client + foreach my $d ( sort keys %main::defs ) { #zur Botschaft passenden Clienten ermitteln geht auf Client: I2CRecFn + #Log3 $hash, 1, "Clients suchen d: $d". ($main::defs{$d}{IODev}? ", IODev: $main::defs{$d}{IODev}":"") . ($main::defs{$d}{I2C_Address} ? ", I2C: $main::defs{$d}{I2C_Address}":"") . ($clientmsg->{i2caddress} ? " CI2C: $clientmsg->{i2caddress}" : ""); + if ( defined( $main::defs{$d} ) + && defined( $main::defs{$d}{IODev} ) && $main::defs{$d}{IODev} == $hash + && defined( $main::defs{$d}{I2C_Address} ) && defined( $clientmsg->{i2caddress} ) + && $main::defs{$d}{I2C_Address} eq $clientmsg->{i2caddress} ) { + my $chash = $main::defs{$d}; + Log3 $hash, 5, "Client gefunden d: $d". ($main::defs{$d}{IODev}? ", IODev: $main::defs{$d}{IODev}":"") . ($main::defs{$d}{I2C_Address} ? ", I2C: $main::defs{$d}{I2C_Address}":"") . ($clientmsg->{i2caddress} ? " CI2C: $clientmsg->{i2caddress}" : ""); + CallFn($d, "I2CRecFn", $chash, $clientmsg); + } + } + ######################################## alte Variante ueber Dispatch ###################### + # my $dir = $clientmsg->{direction}; # + # my $sid = $clientmsg->{id}; # + # if($dir eq "i2cread" || $dir eq "i2cwrite") { # + # my $dev = $clientmsg->{i2caddress}; # + # my %addvals = (RAWMSG => $buf, SENDSTAT => $status); # + # $rmsg = ( defined($rmsg) ? ($sid . " " . $dev . " " . $rmsg) : ($sid . " " . $dev) ); # + # Log 1, "wird an Client geschickt: $rmsg"; # + # Dispatch($hash, $rmsg, \%addvals); # + # } # + ########################################################################################### + undef $clientmsg; #Hash loeschen nachdem Daten verteilt wurden + } + NetzerI2C_HandleQueue($hash); + } else { + Log3 $hash, 1, "$name: unknown data received: $buf"; + } +} +##################################### +sub NetzerI2C_Ready($) {############# kann geloescht werden? + my ($hash) = @_; + + return DevIo_OpenDev($hash, 1, "") + if($hash->{STATE} eq "disconnected"); + + # This is relevant for windows/USB only + my $po = $hash->{USBDev}; + my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags); + if($po) { + ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status; + } + return ($InBytes && $InBytes>0); +} +##################################### +sub NetzerI2C_Attr(@) { # + my ($cmd,$name,$aName,$aVal) = @_; + my $msg = undef; + if($aName eq "timeout") { + if ( defined($aVal) ) { + unless ( looks_like_number($aVal) && $aVal >= 0.1 && $aVal <= 20 ) { + $msg = "$name: Wrong $aName defined. Value must be a number between 0.1 and 20"; + } + } + } + return $msg; +} +##################################### +sub NetzerI2C_InternalTimer($$$$$) {#(von Dietmar63) + my ($modifier, $tim, $callback, $hash, $waitIfInitNotDone) = @_; + + my $mHash; + if ($modifier eq "") { + $mHash = $hash; + } else { + my $timerName = "$hash->{NAME}_$modifier"; + if (exists ($hash->{TIMER}{$timerName})) { + $mHash = $hash->{TIMER}{$timerName}; + } else { + $mHash = { HASH=>$hash, NAME=>"$hash->{NAME}_$modifier", MODIFIER=>$modifier}; + $hash->{TIMER}{$timerName} = $mHash; + } + } + InternalTimer($tim, $callback, $mHash, $waitIfInitNotDone); +} +##################################### +sub NetzerI2C_RemoveInternalTimer($$) { + my ($modifier, $hash) = @_; + + my $timerName = "$hash->{NAME}_$modifier"; + if ($modifier eq "") { + RemoveInternalTimer($hash); + } else { + my $myHash = $hash->{TIMER}{$timerName}; + if (defined($myHash)) { + delete $hash->{TIMER}{$timerName}; + RemoveInternalTimer($myHash); + } + } +} + +1; + +=pod +=begin html + + +

NetzerI2C

+
    + + Provides access to Netzer's I2C interfaces for some logical modules and also directly.

    + preliminary:
    + Serial Server of Netzer must be activated and configured for I2C .
    +
    + Define +
      + define <name> NetzerI2C <Device-Address:Port>
      + where <Device-Address:Port> Device Address/ IP-Address and Serial Server TCP Port of the Netzer

      +
    + + + Set +
      +
    • + Write one byte (or more bytes sequentially) directly to an I2C device (for devices that have only one register to write):
      + set <name> writeByte <I2C Address> <value>

      +
    • +
    • + Write one byte (or more bytes sequentially) to the specified register of an I2C device:
      + set <name> writeByteReg <I2C Address> <Register Address> <value>

      +
    • +
    • + Write n-bytes to an register range, beginning at the specified register:
      + set <name> writeBlock <I2C Address> <Register Address> <value>

      +
    • + Examples: +
        + Write 0xAA to device with I2C address 0x60
        + set test1 writeByte 60 AA
        + Write 0xAA to register 0x01 of device with I2C address 0x6E
        + set test1 writeByteReg 6E 01 AA
        + Write 0xAA to register 0x01 of device with I2C address 0x6E, after it write 0x55 to register 0x02
        + set test1 writeByteReg 6E 01 AA 55
        + Write 0xA4 to register 0x03, 0x00 to register 0x04 and 0xDA to register 0x05 of device with I2C address 0x60 as block operation
        + set test1 writeBlock 60 03 A4 00 DA
        +

      +
    + + + Get +
      + get <name> read <I2C Address> [<Register Address> [<number of registers>]] +
      + gets value of I2C device's registers

      + Examples: +
        + Reads byte from device with I2C address 0x60
        + get test1 writeByte 60
        + Reads register 0x01 of device with I2C address 0x6E.
        + get test1 read 6E 01 AA 55
        + Reads register 0x03 to 0x06 of device with I2C address 0x60.
        + get test1 read 60 03 4
        +

      +

    + + + Attributes + +
    +
+ +=end html + +=begin html_DE + + +

NetzerI2C

+
    + + Ermöglicht den Zugriff auf die I2C Schnittstelle des Netzer.
    über logische Module. Register von I2C IC's können auch direkt gelesen und geschrieben werden.

    + Vorbereitung:
    + Bevor dieses Modul verwendet werden kann muss der Serielle Server des Netzers und auf I2C gestellt werden. +

    + Define +
      + define <name> NetzerI2C <Device-Address:Port>
      + <Device-Address:Port> ist die Adresse/IP-Adresse und Serial Server TCP-Port des Netzer

      +
    + + + Set +
      +
    • + Schreibe ein Byte (oder auch mehrere nacheinander) direkt auf ein I2C device (manche I2C Module sind so einfach, das es nicht einmal mehrere Register gibt):
      + set <name> writeByte <I2C Address> <value>

      +
    • +
    • + Schreibe ein Byte (oder auch mehrere nacheinander) direkt auf ein Register des adressierten I2C device:
      + set <name> writeByteReg <I2C Address> <Register Address> <value>

      +
    • +
    • + Schreibe n-bytes auf einen Registerbereich, beginnend mit dem angegebenen Register:
      + set <name> writeBlock <I2C Address> <Register Address> <value>

      +
    • + Beispiele: +
        + Schreibe 0xAA zu Modul mit I2C Addresse 0x60
        + set test1 writeByte 60 AA
        + Schreibe 0xAA zu Register 0x01 des Moduls mit der I2C Adresse 0x6E
        + set test1 writeByteReg 6E 01 AA
        + Schreibe 0xAA zu Register 0x01 des Moduls mit der I2C Adresse 0x6E, schreibe danach 0x55 zu Register 0x01
        + set test1 writeByteReg 6E 01 AA 55
        + Schreibe 0xA4 zu Register 0x03, 0x00 zu Register 0x04 und 0xDA zu Register 0x05 des Moduls mit der I2C Adresse 0x60 als Block
        + set test1 writeBlock 60 03 A4 00 DA
        + +

      +
    + + + Get +
      + get <name> read <I2C Address> [<Register Address> [<number of registers>]] +
      + Auslesen der Registerinhalte des I2C Moduls

      + Examples: +
        + Lese Byte vom Modul mit der I2C Adresse 0x60
        + get test1 writeByte 60
        + Lese den Inhalt des Registers 0x01 vom Modul mit der I2C Adresse 0x6E.
        + get test1 read 6E 01 AA 55
        + Lese den Inhalt des Registerbereichs 0x03 bis 0x06 vom Modul mit der I2C Adresse 0x60.
        + get test1 read 60 03 4
        +

      +

    + + + Attribute + +
    +
+ +=end html_DE \ No newline at end of file diff --git a/fhem/FHEM/51_Netzer.pm b/fhem/FHEM/51_Netzer.pm new file mode 100644 index 000000000..1d3016418 --- /dev/null +++ b/fhem/FHEM/51_Netzer.pm @@ -0,0 +1,451 @@ +############################################################################## +# +# 51_Netzer.pm +# +############################################################################## +# Modul for Netzer access +# +# +# +############################################################################## + +package main; +use strict; +use warnings; +use POSIX; +use Scalar::Util qw(looks_like_number); +use IO::File; + +#vorhandene Ports +my @ports = ( "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m" ); + +sub Netzer_Initialize($) { + my ($hash) = @_; + $hash->{DefFn} = "Netzer_Define"; + $hash->{ReadFn} = "Netzer_Read"; + $hash->{GetFn} = "Netzer_Get"; + $hash->{SetFn} = "Netzer_Set"; + $hash->{AttrFn} = "Netzer_Attr"; + $hash->{UndefFn} = "Netzer_Undef"; + $hash->{ExceptFn} = "Netzer_Except"; + $hash->{AttrList} = "poll_interval" . + " Port_a:in,out,cnt" . " Port_b:in,out,cnt" . " Port_c:in,out,cnt" . + " Port_d:in,out,PWM" . + " Port_e:in,out,ADC" . " Port_f:in,out,ADC" . + " Port_g:in,out" . " Port_h:in,out" . " Port_i:in,out" . + " Port_j:in,out,PWM" . + " Port_k:in,out" . " Port_l:in,out" . " Port_m:in,out"; +} +############################################# +sub Netzer_Define($$) { + my ($hash, $def) = @_; + + my @args = split("[ \t]+", $def); + my $menge = int(@args); + if (int(@args) < 2) { + return "Define: to less arguments. Usage:\n" . + "define Netzer :"; + } + #Pruefen, ob GPIO bereits verwendet + foreach my $dev (devspec2array("TYPE=$hash->{TYPE}")) { + if ($args[2] eq InternalVal($dev,"DeviceName","")) { + return "IP-Address $args[2] already used by $dev"; + } + } + my $name = $args[0]; + $hash->{DeviceName} = $args[2]; + Netzer_conn($hash); + # create default attributes + #my $msg = CommandAttr(undef, $name . ' direction input'); + #return $msg if ($msg); + return undef; +} +############################################# +sub Netzer_Get($;$) { + my (@a) = @_; + my $hash = $a[0]; + my $name = $hash->{NAME}; + my ($port,$cnt) = split("_", $a[2]) if ( defined($a[2]) ); + my $function = $attr{$name}{"Port_".$port} if defined($port) && defined($attr{$name}{"Port_".$port}); + my $buf = ""; + if ( !( defined($a[2]) ) ) { + foreach (@ports) { + if(defined($attr{$name}) && defined($attr{$name}{"Port_".$_})) { + my $function = $attr{$name}{"Port_".$_}; + if ($function =~ m/^(PWM|ADC)$/i) { + $buf = $_."=?\r\n"; + Netzer_send($hash, $buf); + } elsif ($function =~ m/^(cnt)$/i) { + $buf = "z".$_."=?\r\n"; + Netzer_send($hash, $buf); + } + } + } + $buf = "x=?\r\n"; + } elsif ( defined($port) && grep( /^$port$/, @ports ) && defined($cnt) && $cnt eq "counter" && defined($function) && $function eq "cnt" ) { + $buf = "z" . $port ."=?\r\n"; + } elsif ( defined($port) && grep( /^$a[2]$/, @ports ) && $port ne "?" ) { + $buf = $a[2]."=?\r\n"; + } else { + my $list = ""; + foreach (@ports) { + #next if (wenn port nicht genutzt werden soll); + $list .= " " unless ($list eq ""); + $list .= $_ . ':noArg'; + $list .= " " . $_ . "_counter:noArg" if( defined($attr{$name}) && defined($attr{$name}{"Port_".$_}) && $attr{$name}{"Port_".$_} eq "cnt" ); + } + return 'Unknown argument ' . $a[2] . ', choose one of ' . $list; + } + Netzer_send($hash, $buf); + return; +} +############################################# +sub Netzer_Set($@) { + my ($hash, @a) = @_; + my $name = $a[0]; + my $port = $a[1] if (defined ($a[1])); + my $val = $a[2] if (defined ($a[2])); + ($port, my $cnt) = split("_", $port) if defined($port) && $port =~ m/(_counter)$/; + my $function = $attr{$name}{"Port_".$port} if defined($port) && defined($attr{$name}{"Port_".$port}); + if ( defined($function) && ( ( $function=~ m/^(PWM|out)$/i && !defined($cnt) ) || ( $function=~ m/^(cnt)$/i && defined($cnt) ) ) ) { + my $msg = "$name: wrong value ". (defined($val)?"$val ":"") . "for Port_$a[1], valid values: 0-"; + my $buf = undef; + if ( $function eq "cnt" && defined($cnt) ) { + return $msg . "32767" if !defined($val) || $val > 32767 || $val < 0; + $buf = "z" . $port; + } else { + return $msg . "1023" if ($function eq "PWM" && (!defined($val) || $val > 1023 || $val < 0)); + return $msg . "1" if ($function eq "out" && (!defined($val) || $val > 1 || $val < 0)); + $buf = $port; + } + $val = sprintf("%x",$val); + $buf .= "=$val\r\n"; + Netzer_send($hash, $buf); + } else { + my $list = ""; + foreach (@ports) { + if(defined($attr{$name}) && defined($attr{$name}{"Port_".$_})) { + my $function = $attr{$name}{"Port_".$_}; + if ($function eq "out") { + $list .= " " unless ($list eq ""); + $list .= $_ . ':0,1'; + } elsif ($function eq "PWM") { + $list .= " " unless ($list eq ""); + $list .= $_ . ':slider,0,1,1023'; + } elsif ($function eq "cnt") { + $list .= " " unless ($list eq ""); + $list .= $_ . "_counter" ; + } + } + } + return 'Unknown argument ' . (defined($port)?$port:"") . (defined($cnt)?"_$cnt":"") . ', choose one of ' . $list; + } + return; +} +############################################# +sub Netzer_Attr(@) { + my (undef, $name, $attr, $val) = @_; + my $hash = $defs{$name}; + my $msg = ''; + #Log3 $name, 1, "Name: $name Attr: $attr Wert: $val"; + + if ($attr eq 'poll_interval') { + if ( defined($val) ) { + if ( looks_like_number($val) && $val > 0) { + RemoveInternalTimer($hash); + InternalTimer(1, 'Netzer_Poll', $hash, 0); + } else { + $msg = "$hash->{NAME}: Wrong poll intervall defined. poll_interval must be a number > 0"; + } + } else { #wird auch aufgerufen wenn $val leer ist, aber der attribut wert wird auf 1 gesetzt + RemoveInternalTimer($hash); + } + } + + if (!$val) { + delete ($hash->{READINGS}{$attr."_counter"}) if defined($hash->{READINGS}{$attr."_counter"}); + } elsif ($val =~ m/^(in|out)$/) { + delete ($hash->{READINGS}{$attr."_counter"}) if defined($hash->{READINGS}{$attr."_counter"}); + } elsif ($attr =~ m/^(Port_[a-c])$/) { + $msg = "$hash->{NAME}: $attr wrong function $val. Use in, out or cnt" if $val !~ m/^(cnt)$/; + delete ($hash->{READINGS}{$attr."_counter"}) if defined($hash->{READINGS}{$attr."_counter"}) && $val !~ m/^(cnt)$/; + } elsif ($attr =~ m/^(Port_[d|j])$/) { + $msg = "$hash->{NAME}: $attr wrong function $val. Use in, out or PWM" if $val !~ m/^(PWM)$/; + delete ($hash->{READINGS}{$attr."_counter"}) if defined($hash->{READINGS}{$attr."_counter"}); + } elsif ($attr =~ m/^(Port_[e|f])$/) { + $msg = "$hash->{NAME}: $attr wrong function $val. Use in, out or ADC" if $val !~ m/^(ADC)$/; + delete ($hash->{READINGS}{$attr."_counter"}) if defined($hash->{READINGS}{$attr."_counter"}); + } + return ($msg) ? $msg : undef; + } +############################################# +sub Netzer_Read($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + my $buf; + my $ret = sysread($hash->{CD}, $buf, 1024*1024); + if(!defined($ret) || $ret <= 0) { + Netzer_conn($hash); + return; + } else { + chomp ($buf); + my @msg = split(" ", $buf); + readingsBeginUpdate($hash); + foreach (@msg) { + #abfangen wenn mehrere botschafen + my ($port, $val) = split("=", $_); + $val =~ s/ //g; + $val = hex($val); + my $sval; + #my ($bufc) = $_ =~ /(\d+)/; + if ($port eq "x") { + for (my $i = 0; $i <= 12; $i++) { + next if defined($attr{$name}{"Port_".$ports[$i]}) && $attr{$name}{"Port_".$ports[$i]} =~ m/^(PWM|ADC)$/; + $sval = hex($val) & (1 << $i); + $sval = $sval == 0 ? "0" :"1"; + readingsBulkUpdate($hash, 'Port_'.$ports[$i] , $sval) if (ReadingsVal($name,'Port_'.$ports[$i],0) ne $sval); + } + } elsif ( grep( /^$port$/, @ports ) ) { + readingsBulkUpdate($hash, 'Port_'.$port , $val); + } elsif ( grep( ($port =~ s/z// ), @ports ) ) { + readingsBulkUpdate($hash, 'Port_'.$port.'_counter' , ($val > 32767?"overflow":$val)); + } + } + readingsBulkUpdate($hash, 'received', $buf); + #readingsBulkUpdate($hash, 'zeichenmenge', $ret); + readingsEndUpdate($hash, 1); + } +} +############################################# +sub Netzer_Poll($) {#Update of all Readings + my ($hash) = @_; + my $name = $hash->{NAME}; + Netzer_Get($hash); + my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0); + if ($pollInterval > 0) { + InternalTimer(gettimeofday() + ($pollInterval * 60), 'Netzer_Poll', $hash, 0); + } + return; +} +############################################# +sub Netzer_Undef($$) { + my ($hash, $arg) = @_; + if ( defined (AttrVal($hash->{NAME}, "poll_interval", undef)) ) { + RemoveInternalTimer($hash); + } + Netzer_disconn($hash,0); + return undef; +} +############################################# +sub Netzer_send($$) { + my ($hash, $buf) = @_; + my $cnt= length ($buf); + if (not defined($hash->{CD})) { + Log3 $hash, 1, "$hash->{NAME}: Verbindung unterbrochen, versuche Verbindungsaufbau"; + Netzer_conn($hash); + } + if ($hash->{CD}) { + syswrite($hash->{CD}, $buf, $cnt) ; + } else { + Log3 $hash, 1, "$hash->{NAME}: Daten konnten nicht gesendet werden"; + } +} +############################################# +sub Netzer_conn($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + + Netzer_disconn($hash,0); + my $timeout = $hash->{TIMEOUT} ? $hash->{TIMEOUT} : 3; + my $conn = IO::Socket::INET->new(PeerAddr=>"$hash->{DeviceName}", Timeout => $timeout); + if($conn) { + $hash->{STATE} = "Connected"; + + $hash->{FD} = $conn->fileno(); + $hash->{CD} = $conn; # sysread / close won't work on fileno + $hash->{CONNECTS}++; + $selectlist{$name} = $hash; + Log(GetLogLevel($name,3), "$name: connected to $hash->{DeviceName}"); + } else { + Netzer_disconn($hash, 1); + } +} +############################################# +sub Netzer_disconn($$) { + my ($hash, $connect) = @_; + my $name = $hash->{NAME}; + return if( !$hash->{CD} ); + close($hash->{CD}) if($hash->{CD}); + delete($hash->{FD}); + delete($hash->{CD}); + delete($selectlist{$name}); + $hash->{STATE} = "Disconnected"; + if($connect) { + Log3 $name, 4, "$name: Connect failed."; + } else { + Log3 $name, 4, "$name: Disconnected"; + } +} + +1; + +=pod +=begin html + + +

Netzer

+
    + The Netzer realizes an Ethernet interface on a PIC-based platform. As a gateway module it enables communication between standard TCP/IP sockets and serial busses like I2C, SPI and UART. + Also up to 13 GPIO pins can be accessed. This Modul provides access to these GPIO pins on a Netzer running IO_base in Version 1.5. + There are two pins usable as ADC channel, two as PMW outputs, three as counter and three can generate an interrupt. + The GPIO pins are configured a input per default. Before a port can be used as output it must be configured via the embedded webpage. + If one of the input ports is configured to send interrupt events on GPIO Server, on every event all port values will be updated. + All ports can be read and controlled individually by the function readingsProxy. +

    + + + Define +
      + define <name> Netzer <host:port> +

      +
    + + + Set +
      + set <name> <port[_counter]> <value> +
      + Where <value> is a character between a and m
      according to the port. If Port attr is cnt an aditional value <port_counter> can be set.
      + Only ports with corresponding attr Port_[a-m] set to PWM or out can be used.
      + If Port attr is:
        +
      • PWM <value> can be a number between 0 and 1023
      • +
      • out <value> can be a number between 0 and 1
      • +
      • cnt <port_counter> <value> can be a number between 0 and 32767
      • +
      +
    + + Get +
      + get <name> [<port[_counter]>] +
      + If no <port> is set, all readings will be updated.
      + <port> is a character between a and m
      according to the port. If Port attr is cnt an aditional reading <port_counter> can be read. +

      +
    + + + Attributes +
      + +
    • poll_interval
      + Set the polling interval in minutes to query the sensor for new measured values. + Default: 5, valid values: decimal number

    • + + +
    • Port_<port>
      +
        + Configuration for Netzer port.
        + <port> is a character between a and m.
        +
      • in: Port is defined as input. Same behavior as no attribute. Set is not avaliable for this port.
        + Can be used for all ports
      • +
      • out: Port is defined as output. Set is avaliable for this port with <value> between 0 and 1.
        + Can be used for all ports
      • +
      • cnt: Port is defined as input. Set is not avaliable for this port.
        + An second reading: Port_<port>_counter is avaiable. + It can be updated with get an changed with set.
        + Port_<port>_counter <value> = 0-32767 or overflow if outside this range.
        + Can be used for ports a,b,c
      • +
      • ADC: Port is defined as analog input. Get <value> is 0-1023 according the voltage on port. Set is not avaliable for this port.
        + Can be used for ports e,f
      • +
      • PWM: Port is defined as PWM output. Set and get <value> is 0-1023 according the duty cycle on the port.
        + Can be used for ports d,j
      • +
      +

    • + + +
    +
    +
+ +=end html + +=begin html_DE + + +

Netzer

+
    + The Netzer realisiert ein Ethernetinterface auf PIC-Basis. Es agiert als Gateway zwischen TCP/IP und verschiedenen seriellen Busses wie I2C, SPI oder UART. Es können bis zu 13 GPIO Pins angesprochen (gelesen oder geschrieben) werden. + This Modul ermöglicht den Zugriff auf diese GPIO Pin's auf einem Netzer mit IO_base in Version 1.5. + Es gibt zwei als ADC nutzbare Pin's channel, 2 als PMW Ausgänge, drei als Zähler sowie drei die einen Interrupt auslösen können. + Die GPIO Pin's sind standardmäßig als Eingänge konfiguriert. Bevor ein Pin anderweitig genutzt werden kann, muss er über die eingebaute Website entsprechend eingestellt werden. + Ist einer der Eingänge als Inerrupteingang eingestellt, werden bei jedem Interrupereignis die Weter sämtlicher Ports aktualisiert. +

    + + + Define +
      + define <name> Netzer <host:port> +

      +
    + + + Set +
      + set <name> <port[_counter]> <value> +
      + Dabei ist <value> ein dem Port entsprechender Buchstabe zwischen a und m. Besitzt der Port das Attribut cnt so kann ein weiterer Wert <port_counter> gesetzt werden.
      + Ausschließlich Port's die über Attribut Port_[a-m] auf PWM oder out gesetzt sind können benutzt werden.
      + Bei Port Attribut:
        +
      • PWM <value> kann ein Wert zwischen 0 und 1023 sein
      • +
      • out <value> kann ein Wert zwischen 0 und 1 sein
      • +
      • cnt <port_counter> <value> kann ein Wert zwischen 0 und 32767 sein
      • +
      +
    + + Get +
      + get <name> [<port[_counter]>] +
      + Ohne <port> werde alle Werte aktualisiert.
      + Wenn <port> ein Buchstabe zwischen a und m
      ist, wird der Portwert aktualisiert und bei Port Attribut cnt kann ein weiterer Zählerwert <port_counter> gelesen werden.
      +
      +
    + + + Attributes +
      + +
    • poll_interval
      + Aktualisierungsintervall aller Werte in Minuten.
      + Standard: 5, gültige Werte: Dezimalzahl +

    • + + +
    • Port_<port>
      +
        + Konfiguration des jeweiligen GPIO.
        + <port> ist ein Buchstabe zwischen a und m.
        +
      • in: Port ist Eingang. Kann auch weggelassen werden, da Standard. Set ist für diesen Port nicht verfügbar.
        + Nutzbar für alle Port's
      • +
      • out: Port ist Ausgang. Set kann <value> zwischen 0 und 1 haben.
        + Nutzbar für alle Port's
      • +
      • cnt: Port ist Eingang. Set ist für diesen Port nicht verfügbar.
        + Ein weiteres Reading: Port_<port>_counter ist verfügbar. + Dieses kann auch mit get gelesen und mit set verändert werden.
        + Port_<port>_counter <value> = 0-32767 oder overflow wenn es ausserhalb dieses Bereichs liegt.
        + Nutzbar für Port's a,b,c
      • +
      • ADC: Port ist Analogeingang. get <value> ist 0-1023 entsprechend der Spannung am Port. Set ist für diesen Port nicht verfügbar.
        + Nutzbar für Port's e,f
      • +
      • PWM: Port ist PWM-Ausgang. set und get <value> ist 0-1023 entsprechend des Dutycycle am Port.
        + Nutzbar für Port's d,j
      • +
      +

    • + + +
    +
    +
+ +=end html_DE + +=cut \ No newline at end of file