diff --git a/fhem/FHEM/00_RPII2C.pm b/fhem/FHEM/00_RPII2C.pm new file mode 100644 index 000000000..119ef8e17 --- /dev/null +++ b/fhem/FHEM/00_RPII2C.pm @@ -0,0 +1,530 @@ +############################################## +# $Id: 00_RPII2C.pm klausw +package main; + +use strict; +use warnings; +use Time::HiRes qw(gettimeofday); +use Device::SMBus; + +#my $clientsI2C = ":I2C_PC.*:I2C_SHT21:I2C_MCP23017:I2C_BMP180:"; + +my @clients = qw( +I2C_LCD +I2C_DS1307 +I2C_PC.* +I2C_MCP23017 +I2C_BMP180 +I2C_SHT21 +); + +my $gpioprg = "/usr/local/bin/gpio"; #WiringPi GPIO utility + + +#my %matchListI2C = ( #kann noch weg? +# "1:I2C_PCF8574"=> ".*", +# "2:FHT" => "^81..(04|09|0d)..(0909a001|83098301|c409c401)..", +#); + +sub RPII2C_Initialize($) { + my ($hash) = @_; + +# Provider + $hash->{Clients} = join (':',@clients); + #$hash->{WriteFn} = "RPII2C_Write"; #wird vom client per IOWrite($@) aufgerufen + $hash->{I2CWrtFn} = "RPII2C_Write"; #zum testen als alternative für IOWrite + +# Normal devices + $hash->{DefFn} = "RPII2C_Define"; + $hash->{UndefFn} = "RPII2C_Undef"; + $hash->{GetFn} = "RPII2C_Get"; + $hash->{SetFn} = "RPII2C_Set"; + #$hash->{AttrFn} = "RPII2C_Attr"; + $hash->{NotifyFn} = "RPII2C_Notify"; + $hash->{AttrList}= "do_not_notify:1,0 ignore:1,0 showtime:1,0 " . + "$readingFnAttributes"; +} +##################################### +sub RPII2C_Define($$) { # + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + unless(@a == 3) { + my $msg = "wrong syntax: define RPII2C <0|1>"; + Log3 undef, 2, $msg; + return $msg; + } + if(-e $gpioprg) { #I2C Devices für FHEM User lesbar machen + if(-x $gpioprg) { + if(-u $gpioprg) { + my $exp = $gpioprg.' load i2c'; + $exp = `$exp`; + } else { + Log3 $hash, 1, "file $gpioprg is not setuid"; + } + } else { + Log3 $hash, 1, "file $gpioprg is not executable"; + } + } else { + Log3 $hash, 1, "file $gpioprg doesnt exist"; + } #system "/usr/local/bin/gpio load i2c"; + + my $name = $a[0]; + my $dev = $a[2]; + + $hash->{NOTIFYDEV} = "global"; + + #$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/i2c-".$dev; + $hash->{STATE} = "initialized"; + return undef; +} + +##################################### +sub RPII2C_Notify { # + my ($hash,$dev) = @_; + my $name = $hash->{NAME}; + my $type = $hash->{TYPE}; + if( grep(m/^(INITIALIZED|REREADCFG)$/, @{$dev->{CHANGED}}) ) { + RPII2C_forall_clients($hash,\&RPII2C_Init_Client,undef);; + } elsif( grep(m/^SAVE$/, @{$dev->{CHANGED}}) ) { + } +} +##################################### +sub RPII2C_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 RPII2C_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,5,"im init client fuer $name "; + my $ret = CallFn($name,"InitFn",$hash,$args); + if ($ret) { + Log3 $name,2,"error initializing '".$hash->{NAME}."': ".$ret; + } +} +##################################### +sub RPII2C_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}; + } + } + return undef; +} +##################################### +sub RPII2C_Set($@) { #writeBlock noch nicht fertig + my ($hash, @a) = @_; + my $name = shift @a; + my $type = shift @a; + my @sets = ('writeByte', 'writeByteReg', 'writeBlock'); #, 'writeNBlock'); + return "Unknown argument $type, choose one of " . join(" ", @sets) if @a < 2; + + foreach (@a) { #Hexwerte prüfen und in Dezimalwerte wandeln + return "$name: $_ is no 1byte hexadecimal value" if $_ !~ /^(0x|)[0-9A-F]{1,2}$/xi ; + $_ = hex; + } + my $i2ca = shift @a; + return "$name: I2C Address not valid" unless ($i2ca > 3 && $i2ca < 128); #prüfe auf Hexzahl zwischen 4 und 7F + + my $i2chash = { i2caddress => $i2ca, direction => "i2cwrite" }; + my ($reg, $nbyte, $data) = undef; + if ($type eq "writeByte") { + $data = join(" ", @a); + } elsif ($type eq "writeByteReg") { + $reg = shift @a; + $data = join(" ", @a); + } elsif ($type eq "writeBlock") { + $reg = shift @a; + $nbyte = int(@a); + return "$name maximal blocksize (32byte) exeeded" if $nbyte > 32; + $data = join(" ", @a); + $i2chash->{direction} = "i2cblockwrite"; +#####kommt weg da sinnlos??!!! Achtung $nbyte stimmt derzeit nicht +# } elsif ($type eq "writeNBlock") { +# $reg = shift @a; +# return "$name register address must be a hexvalue" if (!defined($reg) || $reg !~ /^(0x|)[0-9A-F]{1,4}$/xi); +# $nbyte = shift @a; +# return "$name number of bytes must be decimal value" if (!defined($nbyte) || $nbyte !~ /^[0-9]{1,2}$/); +# return "$name data values must be n times number of bytes" if (int(@a) % $nbyte != 0); +# $data = join(" ", @a); +######################################################################### + }else { + return "Unknown argument $type, choose one of " . join(" ", @sets); + } + + $i2chash->{reg} = $reg if defined($reg); #startadresse zum lesen + $i2chash->{nbyte} = $nbyte if defined($nbyte); + $i2chash->{data} = $data if defined($data); + RPII2C_HWACCESS($hash, $i2chash); + undef $i2chash; #Hash löschen + return undef; +} +##################################### fertig? +sub RPII2C_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); + } + if ($a[1] eq "read") { + return "use: \"get $name $a[1] [ []]\"" if(@a < 3); + return "$name: I2C Address not valid" unless ( $a[2] =~ /^(0x|)([0-7]|)[0-9A-F]$/xi); + return "$name register address must be a hexvalue" if (defined($a[3]) && $a[3] !~ /^(0x|)[0-9A-F]{1,4}$/xi); + return "$name number of bytes must be decimal value" if (defined($a[4]) && $a[4] !~ /^[0-9]{1,2}$/); + my $i2chash = { i2caddress => hex($a[2]), direction => "i2cread" }; + $i2chash->{reg} = hex($a[3]) if defined($a[3]); #startadresse zum lesen + $i2chash->{nbyte} = $a[4] if defined($a[4]); + Log3 $hash, 1, "Reg: ". $i2chash->{reg}; + my $status = RPII2C_HWACCESS($hash, $i2chash); + #my $received = join(" ", @{$i2chash->{received}}); #als Array + my $received = $i2chash->{received}; #als Scalar + undef $i2chash; #Hash löschen + return "$received transmission: $status"; + } + return undef; +} +##################################### +sub RPII2C_Write($$) { #wird vom Client aufgerufen + my ($hash, $clientmsg) = @_; + my $name = $hash->{NAME}; + my $ankommen = "$name: vom client empfangen"; + foreach my $av (keys %{$clientmsg}) { $ankommen .= "|" . $av . ": " . $clientmsg->{$av}; } + Log3 $hash, 5, $ankommen; + + if ( $clientmsg->{direction} && $clientmsg->{i2caddress} ) { + $clientmsg->{$name . "_" . "SENDSTAT"} = RPII2C_HWACCESS($hash, $clientmsg); + } + + foreach my $d ( sort keys %main::defs ) { #zur Botschaft passenden Clienten ermitteln geht auf Client: I2CRecFn + #Log3 $hash, 1, "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, "$name ->Client gefunden: $d". ($main::defs{$d}{I2C_Address} ? ", I2Caddress: $main::defs{$d}{I2C_Address}":"") . ($clientmsg->{data} ? " Data: $clientmsg->{data}" : ""); + CallFn($d, "I2CRecFn", $chash, $clientmsg); + undef $clientmsg #Hash löschen nachdem Daten verteilt wurden + } + } + return undef; +} +##################################### +sub RPII2C_HWACCESS($$) { + my ($hash, $clientmsg) = @_; + my $status = "error"; + my $inh = undef; + Log3 $hash, 5, "$hash->{NAME}: HWaccess I2CAddr: " . sprintf("%.2X", $clientmsg->{i2caddress}); + my $dev = Device::SMBus->new( + I2CBusDevicePath => $hash->{DeviceName}, + I2CDeviceAddress => hex( sprintf("%.2X", $clientmsg->{i2caddress}) ), + ); + if (defined($clientmsg->{nbyte}) && defined($clientmsg->{reg}) && defined($clientmsg->{data}) && $clientmsg->{direction} eq "i2cblockwrite") { #Registerblock beschreiben + my @data = split(" ", $clientmsg->{data}); + my $dataref = \@data; + $inh = $dev->writeBlockData( $clientmsg->{reg} , $dataref ); + my $wr = join(" ", @{$dataref}); + Log3 $hash, 5, "$hash->{NAME}: Block schreiben Register: " . sprintf("%.2X", $clientmsg->{reg}) . " Inhalt: " . $wr . " N: ". int(@data) ." Returnvar.: $inh"; + $status = "Ok" if $inh == 0; +#kommt wieder weg################# + } elsif (defined($clientmsg->{nbyte}) && defined($clientmsg->{reg}) && defined($clientmsg->{data}) && $clientmsg->{direction} eq "i2cwrite") { #Registerbereich (mehrfach) beschreiben + my @data = split(" ", $clientmsg->{data}); + foreach (0..$#data) { + my $i = $_ -( int($_ / $clientmsg->{nbyte}) * $clientmsg->{nbyte} ); + $inh = $dev->writeByteData( ($clientmsg->{reg} + $i ) ,$data[$_]); + Log3 $hash, 5, "$hash->{NAME} NReg schreiben; Reg: " . ($clientmsg->{reg} + $i) . " Inh: " . $data[$_] . " Returnvar.: $inh"; + last if $inh != 0; + $status = "Ok" if $inh == 0; + } +#hier Mehrfachbeschreibung eines Registers noch entfernen und dafür Bereich mit Registeroperationen beschreiben + } elsif (defined($clientmsg->{reg}) && defined($clientmsg->{data}) && $clientmsg->{direction} eq "i2cwrite") { #Register beschreiben + my @data = split(" ", $clientmsg->{data}); + foreach (@data) { + $inh = $dev->writeByteData($clientmsg->{reg},$_); + Log3 $hash, 5, "$hash->{NAME}; Register ".sprintf("%.2X", $clientmsg->{reg})." schreiben - Inhalt: " .sprintf("%.2X",$_) . " Returnvar.: $inh"; + last if $inh != 0; + $status = "Ok" if $inh == 0; + } + } elsif (defined($clientmsg->{data}) && $clientmsg->{direction} eq "i2cwrite") { #Byte(s) schreiben + my @data = split(" ", $clientmsg->{data}); + foreach (@data) { + $inh = $dev->writeByte($_); + Log3 $hash, 5, "$hash->{NAME} Byte schreiben; Inh: " . $_ . " Returnvar.: $inh"; + last if $inh != 0; + $status = "Ok" if $inh == 0; + } + } elsif (defined($clientmsg->{reg}) && $clientmsg->{direction} eq "i2cread") { #Register lesen + my $nbyte = defined($clientmsg->{nbyte}) ? $clientmsg->{nbyte} : 1; + my $rmsg = ""; + for (my $n = 0; $n < $nbyte; $n++) { + $inh = $dev->readByteData($clientmsg->{reg} + $n ); + Log3 $hash, 5, "$hash->{NAME}; Register ".sprintf("%.2X", $clientmsg->{reg} + $n )." lesen - Inhalt: ".sprintf("%.2X",$inh); + last if ($inh < 0); + #$rmsg .= sprintf("%.2X",$inh); + $rmsg .= $inh; + $rmsg .= " " if $n <= $nbyte; + $status = "Ok" if ($n + 1) == $nbyte; + } + #@{$clientmsg->{received}} = split(" ", $rmsg) if($rmsg); #Daten als Array übertragen + $clientmsg->{received} = $rmsg if($rmsg); #Daten als Scalar übertragen + } elsif ($clientmsg->{direction} eq "i2cread") { #Byte lesen + my $nbyte = defined($clientmsg->{nbyte}) ? $clientmsg->{nbyte} : 1; + my $rmsg = ""; + for (my $n = 0; $n < $nbyte; $n++) { + $inh = $dev->readByte(); + Log3 $hash, 5, "$hash->{NAME} Byte lesen; Returnvar.: $inh"; + last if ($inh < 0); + $rmsg .= $inh; + $rmsg .= " " if $n <= $nbyte; + $status = "Ok" if ($n + 1) == $nbyte; + } + #@{$clientmsg->{received}} = split(" ", $rmsg) if($rmsg); #Daten als Array übertragen + $clientmsg->{received} = $rmsg if($rmsg); #Daten als Scalar übertragen + } + $hash->{STATE} = $status; + $hash->{ERRORCNT} = defined($hash->{ERRORCNT}) ? $hash->{ERRORCNT} += 1 : 1 if $status ne "Ok"; + $clientmsg->{$hash->{NAME} . "_" . "RAWMSG"} = $inh; + + return $status; +} + +=pod +=begin html + + +

RPII2C

+
    + + Provides access to Raspberry Pi's I2C interfaces for some logical modules and also directly.
    + This modul will basically work on every linux system that provides /dev/i2c-x.

    + + preliminary:
    +
      +
    • + This module uses gpio utility from WiringPi library change access rights of I2C-Interface
      + WiringPi installation is described here: RPI_GPIO
      + Alternatively for other systems (BeagleBone, etc.) you can manually change access rights for /dev/i2c-x. You will need write-/read access for user that runs FHEM. This can be doen e.g. in etc/init.d/fhem
      + +
    • +
    • + installation of i2c dependencies:
      + sudo apt-get install libi2c-dev i2c-tools build-essential
      +
    • +
    • + load I2C kernel modules:
      + open /etc/modules
      + sudo nano /etc/modules
      + add theese lines
      + + i2c-dev
      + i2c-bcm2708
      +
      +
    • +
    • + To access the I2C-Bus the Device::SMBus module is necessary:
      + sudo cpan Device::SMBus
      +
    • +
    +
    + Define +
      + define <name> RPII2C <I2C Bus Number>
      + where <I2C Bus Number> is the number of the I2C bus that should be used (0 or 1)

      +
    + + + 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>

      +
    • +
    • + Same as writeBlock but writes register range sequentially. The numbers of byte to write must be a multipe of the number of register. + set <name> writeNBlock <I2C Address> <Register Address> <number of registers> <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 0x02 as two separate commands
        + 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 an block command
        + 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 + + +

RPII2C

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

    + Dieses Modul funktioniert grunsätzlich auf allen Linux Systemen, die /dev/i2c-x bereitstellen.

    + + Vorbereitung:
    +
      +
    • + Dieses Modul nutzt das gpio Utility der WiringPi Bibliothek um FHEM Schreibrechte auf die I2C Schnittstelle zu geben.
      + WiringPi Installation ist hier beschrieben: RPI_GPIO
      + Für andere Systeme (BeagleBone, etc.) oder auch für das Raspberry kann auf WiringPi verzichtet werden. In diesem Fall müssen die Dateien /dev/i2c-x Schreib-/Leserechte, für den User unter dem FHEM läuft, gesetzt bekommen. (z.B. in der etc/init.d/fhem)
      +
    • +
    • + Installation der I2C Abhängigkeiten:
      + sudo apt-get install libi2c-dev i2c-tools build-essential
      +
    • +
    • + I2C Kernelmodule laden:
      + modules Datei öffnen
      + sudo nano /etc/modules
      + folgendes einfügen
      + + i2c-dev
      + i2c-bcm2708
      +
      +
    • +
    • + Desweiteren ist das Perl Modul Device::SMBus für den Zugrff auf den I2C Bus notwendig:
      + sudo cpan Device::SMBus
      +
    • +
    +
    + Define +
      + define <name> RPII2C <I2C Bus Number>
      + Die <I2C Bus Number> ist die Nummer des I2C Bus an den die I2C IC's angeschlossen werden (0 oder 1)

      +
    + + + 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>

      +
    • +
    • + Identisch zu writeBlock nur kann der Registerbereich sequentiell beschrieben werden. Die Anzahl der Byte muss ein vielfaches der <number of registers> sein. + set <name> writeNBlock <I2C Address> <Register Address> <number of registers> <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 in das Register 0x02 als einzelne Befehle
        + 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 zusammen als ein Blockbefehl
        + 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 + +1; diff --git a/fhem/FHEM/52_I2C_PCA9532.pm b/fhem/FHEM/52_I2C_PCA9532.pm new file mode 100644 index 000000000..974a2372b --- /dev/null +++ b/fhem/FHEM/52_I2C_PCA9532.pm @@ -0,0 +1,488 @@ +############################################################################## +# +# 52_I2C_PCA9532.pm +# +############################################################################## +# Modul for I2C PWM Driver PCA9532 +# +# define I2C_PCA9532 +# set +# +# contributed by Klaus Wittstock (2014) email: klauswittstock bei gmail punkt com +# +############################################################################## +#zu tun: +#bei Set sollten Input Values nicht aktualisiert werden +#$sendpackage{data} als Array ? +#$clientmsg->{received} als Array ? + +#Inhalte des Hashes: +#i2caddress 00-7F I2C-Adresse +#direction i2cread|i2cwrite Richtung +#reg 00-FF|"" Registeradresse (kann weggelassen werden für IC's ohne Registeradressierung) +#nbyte Zahl Anzahl Register, die bearbeitet werden sollen (im mom 0-99) +#data 00-FF ... Daten die an I2C geschickt werden sollen (müssen, wenn nbyte benutzt wird immer ein Vielfaches Desselben sein) +#received 00-FF ... Daten die vom I2C empfangen wurden, durch Leerzeichen getrennt (bleibt leer wenn Daten geschrieben werden) +#pname_SENDSTAT Ok|error zeigt Übertragungserfolg an + +package main; +use strict; +use warnings; +use SetExtensions; +#use POSIX; +use Scalar::Util qw(looks_like_number); +#use Error qw(:try); + +my $setdim = ":slider,0,1,255 "; + +my %setsP = ( +'off' => 0, +'on' => 1, +'PWM0' => 2, +'PWM1' => 3, +); +############################################################################### +sub I2C_PCA9532_Initialize($) { + my ($hash) = @_; + $hash->{DefFn} = "I2C_PCA9532_Define"; + $hash->{InitFn} = 'I2C_PCA9532_Init'; + $hash->{UndefFn} = "I2C_PCA9532_Undefine"; + $hash->{AttrFn} = "I2C_PCA9532_Attr"; + #$hash->{StateFn} = "I2C_PCA9532_SetState"; + $hash->{SetFn} = "I2C_PCA9532_Set"; + $hash->{GetFn} = "I2C_PCA9532_Get"; + $hash->{I2CRecFn} = "I2C_PCA9532_I2CRec"; + $hash->{AttrList} = "IODev do_not_notify:1,0 ignore:1,0 showtime:1,0". + "poll_interval T0:slider,0,1,255 T1:slider,0,1,255 InputPorts ". + "$readingFnAttributes"; +} +############################################################################### +sub I2C_PCA9532_SetState($$$$) { + my ($hash, $tim, $vt, $val) = @_; + + $val = $1 if($val =~ m/^(.*) \d+$/); + #return "Undefined value $val" if(!defined($it_c2b{$val})); + return undef; +} +############################################################################### +sub I2C_PCA9532_Define($$) { + my ($hash, $def) = @_; + my @a = split("[ \t]+", $def); + $hash->{STATE} = 'defined'; + if ($main::init_done) { + eval { I2C_PCA9532_Init( $hash, [ @a[ 2 .. scalar(@a) - 1 ] ] ); }; + return I2C_PCA9532_Catch($@) if $@; + } + return undef; +} +############################################################################### +sub I2C_PCA9532_Init($$) { + my ( $hash, $args ) = @_; + #my @a = split("[ \t]+", $args); + my $name = $hash->{NAME}; + if (defined $args && int(@$args) != 1) { + return "Define: Wrong syntax. Usage:\n" . + "define I2C_PCA9532 "; + } + #return "$name I2C Address not valid" unless ($a[0] =~ /^(0x|)([0-7]|)[0-9A-F]$/xi); + + if (defined (my $address = shift @$args)) { + $hash->{I2C_Address} = $address =~ /^0.*$/ ? oct($address) : $address; + } else { + return "$name I2C Address not valid"; + } + + #$hash->{I2C_Address} = hex($a[0]); + AssignIoPort($hash); + $hash->{STATE} = 'Initialized'; + return; +} +############################################################################### +sub I2C_PCA9532_Catch($) { + my $exception = shift; + if ($exception) { + $exception =~ /^(.*)( at.*FHEM.*)$/; + return $1; + } + return undef; +} +############################################################################### +sub I2C_PCA9532_Undefine($$) { + my ($hash, $arg) = @_; + if ( defined (AttrVal($hash->{NAME}, "poll_interval", undef)) ) { + RemoveInternalTimer($hash); + } +} +############################################################################### +sub I2C_PCA9532_Attr(@) { + my (undef, $name, $attr, $val) = @_; + my $hash = $defs{$name}; + my $msg = ''; + if ($attr && $attr eq 'poll_interval') { + #my $pollInterval = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0; + if (!defined($val) ) { + RemoveInternalTimer($hash); + } elsif ($val > 0) { + RemoveInternalTimer($hash); + InternalTimer(1, 'I2C_PCA9532_Poll', $hash, 0); + } else { + $msg = 'Wrong poll intervall defined. poll_interval must be a number > 0'; + } + } elsif ($attr && $attr =~ m/^T[0-1]$/i) { + return "wrong value: $val for \"set $name $attr\" use 0-255" + unless(looks_like_number($val) && $val >= 0 && $val < 256); + substr($attr,0,1,""); + my $regaddr = ($attr == 0 ? 2 : 4); + my %sendpackage = ( i2caddress => $hash->{I2C_Address}, direction => "i2cwrite" ); + #$sendpackage{data} = sprintf("%.2X", $val); + #$sendpackage{reg} = sprintf("%.2X", $regaddr); + $sendpackage{data} = $val; + $sendpackage{reg} = $regaddr; + return "$name: no IO device defined" unless ($hash->{IODev}); + my $phash = $hash->{IODev}; + my $pname = $phash->{NAME}; + CallFn($pname, "I2CWrtFn", $phash, \%sendpackage); + } elsif ($attr && $attr eq "InputPorts") { + my @inp = split(" ", $val); + foreach (@inp) { + return "wrong value: $_ for \"set $name $attr\" use space separated numbers 0-15" unless ($_ >= 0 && $_ < 16); + } + } + return ($msg) ? $msg : undef; +} +############################################################################### +sub I2C_PCA9532_Poll($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + # Read values + I2C_PCA9532_Get($hash, $name); + my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0); + if ($pollInterval > 0) { + InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_PCA9532_Poll', $hash, 0); + } +} +############################################################################### +sub I2C_PCA9532_Set($@) { + my ($hash, @a) = @_; + my $name =$a[0]; + my $cmd = $a[1]; + my $val = $a[2]; + my @inports = sort(split( " ",AttrVal($name, "InputPorts", ""))); + unless (@a == 3) { + + } + my %sendpackage = ( i2caddress => $hash->{I2C_Address}, direction => "i2cwrite" ); + if ( $cmd && $cmd =~ m/^Port((0|)[0-9]|1[0-5])$/i) { + return "wrong value: $val for \"set $name $cmd\" use one of: " . + join(',', (sort { $setsP{ $a } <=> $setsP{ $b } } keys %setsP) ) + unless(exists($setsP{$val})); + substr($cmd,0,4,""); + return "$name error: Port$cmd is defined as input" if ( $cmd ~~ @inports ); #Prüfen ob entsprechender Port Input ist + my $LSreg = int($cmd / 4); #Nummer des entspechenden LS Registers + my $regaddr = $LSreg + 6; #Adresse für Controlregister berechnen (LS0 = 0x06) + my $n = $LSreg * 4; #Erster Port in LSx + my $sbyte = 0; + foreach (reverse 0..3) { #ensprechendes Controlregister füllen + my $portn = $_ + $n; +#hier noch alle inputs auf rezessiv setzen + if (( $portn) == $cmd ) { #->wenn aktueller Port dann neuer Wert + $sbyte += $setsP{$val} << (2 * $_); + next; + } + $sbyte += $setsP{ReadingsVal($name,'Port'.$portn,"off")} << (2 * $_); #->sonst aus dem Reading holen + } + #$sendpackage{data} = sprintf("%.2X",$sbyte); + $sendpackage{data} = $sbyte; + #$sendpackage{reg} = sprintf("%.2X", $regaddr); + $sendpackage{reg} = $regaddr; + + } elsif ($cmd && $cmd =~ m/^PWM[0-1]$/i) { + return "wrong value: $val for \"set $name $cmd\" use 0-255" + unless(looks_like_number($val) && $val >= 0 && $val < 256); + substr($cmd,0,3,""); + my $regaddr = ($cmd == 0 ? 3 : 5); + #$sendpackage{data} = sprintf("%.2X", $val); + $sendpackage{data} = $val; + $sendpackage{reg} = sprintf("%.2X", $regaddr); + + } else { + my $list = undef; + foreach (0..15) { + next if ( $_ ~~ @inports ); #Inputs überspringen + #$list .= "Port" . $_ . ":" . join(',', sort keys %setsP) . " "; + $list .= "Port" . $_ . ":" . join(',', (sort { $setsP{ $a } <=> $setsP{ $b } } keys %setsP) ) . " "; + } + $list .= join($setdim, ("PWM0", "PWM1")) . $setdim; + return "Unknown argument $a[1], choose one of " . $list; + } + return "$name: no IO device defined" unless ($hash->{IODev}); + my $phash = $hash->{IODev}; + my $pname = $phash->{NAME}; + CallFn($pname, "I2CWrtFn", $phash, \%sendpackage); +} +############################################################################### +sub I2C_PCA9532_Get($@) { + my ($hash, @a) = @_; + my $name =$a[0]; + + my %sendpackage = ( i2caddress => $hash->{I2C_Address}, direction => "i2cread" ); + $sendpackage{reg} = 0; #startadresse zum lesen + $sendpackage{nbyte} = 10; + return "$name: no IO device defined" unless ($hash->{IODev}); + my $phash = $hash->{IODev}; + my $pname = $phash->{NAME}; + CallFn($pname, "I2CWrtFn", $phash, \%sendpackage); + +} +############################################################################### +sub I2C_PCA9532_I2CRec($@) { # vom physical aufgerufen + my ($hash, $clientmsg) = @_; + my $name = $hash->{NAME}; + my $phash = $hash->{IODev}; + my $pname = $phash->{NAME}; + while ( my ( $k, $v ) = each %$clientmsg ) { #erzeugen von Internals für alle Keys in $clientmsg die mit dem physical Namen beginnen + $hash->{$k} = $v if $k =~ /^$pname/ ; + } + #hier noch überprüfen, ob Register und Daten ok + if ($clientmsg->{direction} && defined($clientmsg->{reg}) && $clientmsg->{$pname . "_SENDSTAT"} && $clientmsg->{$pname . "_SENDSTAT"} eq "Ok") { + if ( $clientmsg->{direction} eq "i2cread" && defined($clientmsg->{received}) ) { # =~ m/^[a-f0-9]{2}$/i) { + #my @rec = @{$clientmsg->{received}}; #bei übergabe im hash als array + my @rec = split(" ",$clientmsg->{received}); #bei übergabe im als skalar + Log3 $hash, 3, "$name: wrong amount of registers transmitted from $pname" unless (@rec == $clientmsg->{nbyte}); + foreach (reverse 0..$#rec) { #reverse, damit Inputs (Register 0 und 1 als letztes geschrieben werden) + I2C_PCA9532_UpdReadings($hash, $_ + $clientmsg->{reg} , $rec[$_]); + } + readingsSingleUpdate($hash,"state", "Ok", 1); + } elsif ( $clientmsg->{direction} eq "i2cwrite" && defined($clientmsg->{data}) ) { # =~ m/^[a-f0-9]{2}$/i) {#readings aktualisieren wenn Übertragung ok + I2C_PCA9532_UpdReadings($hash, $clientmsg->{reg} , $clientmsg->{data}); + readingsSingleUpdate($hash,"state", "Ok", 1); + + } else { + readingsSingleUpdate($hash,"state", "transmission error", 1); + Log3 $hash, 3, "$name: failure in message from $pname"; + Log3 $hash, 3,(defined($clientmsg->{direction}) ? "Direction: $clientmsg->{direction} " : "Direction: undef "). + (defined($clientmsg->{i2caddress}) ? "I2Caddress: $clientmsg->{i2caddress} " : "I2Caddress: undef "). + (defined($clientmsg->{reg}) ? "Register: $clientmsg->{reg} " : "Register: undef "). + (defined($clientmsg->{data}) ? "Data: $clientmsg->{data} " : "Data: undef "). + (defined($clientmsg->{received}) ? "received: $clientmsg->{received} " : "received: undef "); + } + } else { + readingsSingleUpdate($hash,"state", "transmission error", 1); + Log3 $hash, 3, "$name: failure in message from $pname"; + Log3 $hash, 3,(defined($clientmsg->{direction}) ? "Direction: $clientmsg->{direction} " : "Direction: undef "). + (defined($clientmsg->{i2caddress}) ? "I2Caddress: $clientmsg->{i2caddress} " : "I2Caddress: undef "). + (defined($clientmsg->{reg}) ? "Register: $clientmsg->{reg} " : "Register: undef "). + (defined($clientmsg->{data}) ? "Data: $clientmsg->{data} " : "Data: undef "). + (defined($clientmsg->{received}) ? "received: $clientmsg->{received} " : "received: undef "); + #my $cmsg = undef; + #foreach my $av (keys %{$clientmsg}) { $cmsg .= "|" . $av . ": " . $clientmsg->{$av}; } + #Log3 $hash, 3, $cmsg; + } + #undef $clientmsg; +} +############################################################################### +sub I2C_PCA9532_UpdReadings($$$) { + my ($hash, $reg, $inh) = @_; + my $name = $hash->{NAME}; + #$inh = hex($inh); + Log3 $hash, 5, "$name UpdReadings Register: $reg, Inhalt: $inh"; + readingsBeginUpdate($hash); + if ( $reg < 10 && $reg > 5) { #Wenn PortRegister + my %rsetsP = reverse %setsP; + my $LSreg = $reg - 6; #Nummer des entspechenden LS Registers + my $n = $LSreg * 4; #Erster Port in LSx + foreach (reverse 0..3) { #Ports aus Controlregister abarbeiten + my $pval = 3 & ( $inh >> ($_ * 2) ); + my $port = $_ + $n; + readingsBulkUpdate($hash, 'Port'.$port , $rsetsP{$pval}) + if (ReadingsVal($name, 'Port'.$port,"nix") ne $rsetsP{$pval}); #nur wenn Wert geändert + } + } elsif ( $reg == 3) { #wenn PWM0 Register + readingsBulkUpdate($hash, 'PWM0' , $inh); + } elsif ( $reg == 5) { #wenn PWM1 Register + readingsBulkUpdate($hash, 'PWM1' , $inh); + } elsif ( $reg == 2) { #wenn Frequenz0 Register + $hash->{Frequency_0} = sprintf( "%.3f", ( 152 / ($inh + 1) ) ) . " Hz"; + } elsif ( $reg == 4) { #wenn Frequenz0 Register + $hash->{Frequency_1} = sprintf( "%.3f", ( 152 / ($inh + 1) ) ) . " Hz"; + } elsif ( $reg >= 0 && $reg < 2 ) { #Input Register + my $j = 8 * $reg; + Log3 $hash, 5, "Register $reg Inh: $inh"; + my @inports = sort(split( " ",AttrVal($name, "InputPorts", ""))); + for (my $i = 0; $i < 8; $i++) { + Log3 $hash, 5, "Register $reg Forschleife i: $i"; + if ( ($i + $j) ~~ @inports ) { #nur als Input definierte Ports aktualisieren + my $sval = $inh & (1 << $i); + $sval = $sval == 0 ? "0" :"1"; + readingsBulkUpdate($hash, 'Port'.($i + $j) , $sval) if (ReadingsVal($name,'Port'.($i + $j),2) ne $sval); + Log3 $hash, 5, "Register $reg wert: $sval"; + } + } + } + readingsEndUpdate($hash, 1); + return; +} +############################################################################### + +1; + +=pod +=begin html + + +

I2C_PCA9532

+
    + + Provides an interface to the PCA9532 I2C 16 channel PWM IC. + The PCA9532 has 2 independent PWM stages and every channel can be attached to on of these stages or directly turned on or off. + The I2C messages are send through an I2C interface module like RPII2C, FRM + or NetzerI2C so this device must be defined first.
    + attribute IODev must be set
    +
    + Define +
      + define <name> I2C_PCA9532 <I2C Address>
      + where <I2C Address> is an 2 digit hexadecimal value
      +
    + + + Set +
      + set <name> <port> <value>

      +
        +
      • if <port> is one of Port0 to Port15, then <value> will be one of:
        +
          + + off
          + on
          + PWM0 (output is switched with PWM0 frequency and duty cycle)
          + PWM1 (output is switched with PWM1 frequency and duty cycle)
          +
          +
        +
      • +
      • + if <port> is PWM0 or PWM1, then <value> is an value between 0 and 255 and stands for the duty cycle of the PWM stage. +
      • +
      +
      + Examples: +
        + set mod1 Port4 PWM1
        + set mod1 PWM1 128
        +

      +
    + + + Get +
      + get <name> +

      + refreshes all readings +

    + + + Attributes +
      +
    • poll_interval
      + Set the polling interval in minutes to query the GPIO's level
      + Default: -, valid values: decimal number

      +
    • +
    • InputPorts
      + Space separated list of Portnumers that are used as Inputs
      + Ports in this list can't be written
      + Default: no, valid values: 0 1 2 .. 15

      +
    • +
    • T0/T1
      + Sets PWM0/PWM1 to another Frequency. The Formula is: Fx = 152/(Tx + 1) The corresponding frequency value is shown under internals.
      + Default: 0 (152Hz), valid values: 0-255

      +
    • +
    • IODev
    • +
    • ignore
    • +
    • do_not_notify
    • +
    • showtime
    • +
    +
    +
+ +=end html + +=begin html_DE + + +

I2C_PCA9532

+
    + + Ermöglicht die Verwendung eines PCA9532 I2C 16 Kanal PWM IC. + Das PCA9532 hat 2 unabhängige PWM Stufen. Jeder Kanal kanne einer der Stufen zugeordnet werden oder direkt auf off/on gesetzt werden. + I2C-Botschaften werden über ein I2C Interface Modul wie beispielsweise das RPII2C + oder NetzerI2C gesendet. Daher muss dieses vorher definiert werden.
    + Das Attribut IODev muss definiert sein.
    +
    + Define +
      + define <name> I2C_PCA9532 <I2C Address>
      + Der Wert <I2C Address> ist ein zweistelliger Hex-Wert
      +
    + + + Set +
      + set <name> <port> <value>

      +
        +
      • wenn als <port> Port0 bis Port15 verwendet wird, dann ist <value> einer dieser Werte:
        +
          + + off
          + on
          + PWM0 (Port wird auf PWM0 Frequenz- und Pulsweiteneinstellung gesetzt)
          + PWM1 (Port wird auf PWM1 Frequenz- und Pulsweiteneinstellung gesetzt)
          +
          +
        +
      • +
      • + wenn als <port> PWM0 oder PWM1 verwendet wird, ist <value> ein Wert zwischen 0 und 255 ensprechend der Pulsweite der PWM Stufe. +
      • +
      +
      + Beispiele: +
        + set mod1 Port4 PWM1
        + set mod1 PWM1 128
        +

      +
    + + + Get +
      + get <name> +

      + Aktualisierung aller Werte +

    + + + Attribute +
      +
    • poll_interval
      + Aktualisierungsintervall aller Werte in Minuten.
      + Standard: -, gültige Werte: Dezimalzahl

      +
    • +
    • InputPorts
      + Durch Leerzeichen getrennte Portnummern die als Inputs genutzt werden.
      + Ports in dieser Liste können nicht geschrieben werden.
      + Standard: no, gültige Werte: 0 1 2 .. 15

      +
    • +
    • T0/T1
      + Änderung der Frequenzwerte von PWM0/PWM1 nach der Formel: Fx = 152/(Tx + 1). Der entsprechende Frequenzwert wird unter Internals angezeigt.
      + Standard: 0 (152Hz), gültige Werte: 0-255

      +
    • +
    • IODev
    • +
    • ignore
    • +
    • do_not_notify
    • +
    • showtime
    • +
    +
    +
+ +=end html_DE + +=cut \ No newline at end of file diff --git a/fhem/FHEM/52_I2C_PCF8574.pm b/fhem/FHEM/52_I2C_PCF8574.pm new file mode 100644 index 000000000..6568207b0 --- /dev/null +++ b/fhem/FHEM/52_I2C_PCF8574.pm @@ -0,0 +1,416 @@ +############################################## +# $Id: 52_I2C_PCF8574.pm 3764 2014-01-22 07:09:38Z klausw $ +package main; + +use strict; +use warnings; +use SetExtensions; +use Scalar::Util qw(looks_like_number); + +my %setsP = ( +'off' => 0, +'on' => 1, +); + +sub I2C_PCF8574_Initialize($) { + my ($hash) = @_; + + #$hash->{Match} = ".*"; + $hash->{DefFn} = "I2C_PCF8574_Define"; + $hash->{InitFn} = 'I2C_PCF8574_Init'; + $hash->{AttrFn} = "I2C_PCF8574_Attr"; + $hash->{SetFn} = "I2C_PCF8574_Set"; + $hash->{GetFn} = "I2C_PCF8574_Get"; + $hash->{UndefFn} = "I2C_PCF8574_Undef"; + $hash->{ParseFn} = "I2C_PCF8574_Parse"; + $hash->{I2CRecFn} = "I2C_PCF8574_I2CRec"; + $hash->{AttrList} = "IODev do_not_notify:1,0 ignore:1,0 showtime:1,0 ". + "poll_interval InputPorts ". + "$readingFnAttributes"; +} +################################### +sub I2C_PCF8574_Set($@) { # + my ($hash, @a) = @_; + my $name =$a[0]; + my $cmd = $a[1]; + my $val = $a[2]; + my @inports = sort(split( " ",AttrVal($name, "InputPorts", ""))); + my %sendpackage = ( i2caddress => $hash->{I2C_Address}, direction => "i2cwrite" ); + if ( $cmd && $cmd =~ m/^Port((0|)[0-7])$/i) { + return "wrong value: $val for \"set $name $cmd\" use one of: off, on" + unless(exists($setsP{$val})); + substr($cmd,0,4,""); #Nummer aus String extrahieren + return "$name error: Port$cmd is defined as input" if ( $cmd ~~ @inports ); #Prüfen ob entsprechender Port Input ist + my $sbyte = 0; + foreach (0..7) { + if ($_ == $cmd) { #Port der geändert werden soll + $sbyte += $setsP{$val} << (1 * $_); + } elsif ($_ ~~ @inports) {#Port der als Input konfiguriert ist wird auf 1 gesetzt + $sbyte += 1 << (1 * $_); + } else { #alle anderen Portwerte werden den Readings entnommen + $sbyte += $setsP{ReadingsVal($name,'Port'.$_,"off")} << (1 * $_); #->sonst aus dem Reading holen + } + } + #$sendpackage{data} = sprintf("%.2X",$sbyte); + $sendpackage{data} = $sbyte; + } else { + my $list = undef; + foreach (0..7) { + next if ( $_ ~~ @inports ); #Inputs überspringen + $list .= "Port" . $_ . ":" . join(',', (sort { $setsP{ $a } <=> $setsP{ $b } } keys %setsP) ) . " "; + } + return "Unknown argument $a[1], choose one of " . $list; + } + return "$name: no IO device defined" unless ($hash->{IODev}); + my $phash = $hash->{IODev}; + my $pname = $phash->{NAME}; + CallFn($pname, "I2CWrtFn", $phash, \%sendpackage); + ########################################################## + # IOWrite($hash, \%sendpackage); + ########################################################## + + ########################################################## + # Look for all devices with the same code, and set state, timestamp + #my $code = "$hash->{I2C_Address} $hash->{BTN}"; + #my $code = "$hash->{NAME} $hash->{I2C_Address}"; + #my $tn = TimeNow(); + #my $defptr = $modules{I2C_PCF8574}{defptr}{$code}; + #foreach my $n (keys %{ $defptr }) { + # readingsSingleUpdate($defptr->{$n}, "state", $v, 1); + # } + ########################################################## + return undef; +} +################################### +sub I2C_PCF8574_Get($@) { + my ($hash, @a) = @_; + my $name =$a[0]; + my %sendpackage = (); + #%sendpackage = ( direction => "i2cread", id => (defined( $hash->{ID} )? $hash->{ID} : "00"), i2caddress => $hash->{I2C_Address}); + %sendpackage = ( i2caddress => $hash->{I2C_Address}, direction => "i2cread"); + return "$name: no IO device defined" unless ($hash->{IODev}); + #neu: über CallFn auf eigene Funktion + my $phash = $hash->{IODev}; + my $pname = $phash->{NAME}; + CallFn($pname, "I2CWrtFn", $phash, \%sendpackage); + #alt: für IOWrite + #IOWrite($hash, \%sendpackage); +} +################################### +sub I2C_PCF8574_Attr(@) { # + my (undef, $name, $attr, $val) = @_; + my $hash = $defs{$name}; + my $msg = ''; + if ($attr eq 'poll_interval') { + if ( defined($val) ) { + if ( looks_like_number($val) && $val > 0) { + RemoveInternalTimer($hash); + InternalTimer(1, 'I2C_PCF8574_Poll', $hash, 0); + } else { + $msg = "$hash->{NAME}: Wrong poll intervall defined. poll_interval must be a number > 0"; + } + } else { + RemoveInternalTimer($hash); + } + } elsif ($attr && $attr eq "InputPorts") { + if ( defined($val) ) { + my @inp = split(" ", $val); + foreach (@inp) { + $msg = "wrong value: $_ for \"set $name $attr\" use space separated numbers 0-7" unless ($_ >= 0 && $_ < 8); + } + } + } + return $msg +} +################################### +sub I2C_PCF8574_Define($$) { # + my ($hash, $def) = @_; + my @a = split("[ \t]+", $def); + $hash->{STATE} = 'defined'; + if ($main::init_done) { + eval { I2C_PCF8574_Init( $hash, [ @a[ 2 .. scalar(@a) - 1 ] ] ); }; + return I2C_PCF8574_Catch($@) if $@; + } + return undef; +} +################################### +sub I2C_PCF8574_Init($$) { # + my ( $hash, $args ) = @_; + #my @a = split("[ \t]+", $args); + my $name = $hash->{NAME}; + if (defined $args && int(@$args) != 1) { + return "Define: Wrong syntax. Usage:\n" . + "define I2C_PCA9532 "; + } + + if (defined (my $address = shift @$args)) { + $hash->{I2C_Address} = $address =~ /^0.*$/ ? oct($address) : $address; + } else { + return "$name I2C Address not valid"; + } + + #für die Nutzung von IOWrite + #my $code = ( defined( $hash->{ID} )? $hash->{ID} : "00" ) . " " . $hash->{I2C_Address}; + #my $ncode = 1; + #my $name = $a[0]; + #$hash->{CODE}{$ncode++} = $code; + #$modules{I2C_PCF8574}{defptr}{$code}{$name} = $hash; + + AssignIoPort($hash); + $hash->{STATE} = 'Initialized'; + return; +} +################################### +sub I2C_PCF8574_Catch($) { + my $exception = shift; + if ($exception) { + $exception =~ /^(.*)( at.*FHEM.*)$/; + return $1; + } + return undef; +} +################################### +sub I2C_PCF8574_Undef($$) { # + my ($hash, $name) = @_; + if ( defined (AttrVal($hash->{NAME}, "poll_interval", undef)) ) { + RemoveInternalTimer($hash); + } + #foreach my $c (keys %{ $hash->{CODE} } ) { + # $c = $hash->{CODE}{$c}; + # my $c = ( defined( $hash->{ID} )? $hash->{ID} : "00" ) . " " . $hash->{I2C_Address}; + # As after a rename the $name my be different from the $defptr{$c}{$n} + # we look for the hash. + # foreach my $dname (keys %{ $modules{I2C_PCF8574}{defptr}{$c} }) { + # delete($modules{I2C_PCF8574}{defptr}{$c}{$dname}) + # if($modules{I2C_PCF8574}{defptr}{$c}{$dname} == $hash); + # } + # } + return undef; +} +################################### +sub I2C_PCF8574_Poll($) { #for attr poll_intervall -> readout pin values + my ($hash) = @_; + my $name = $hash->{NAME}; + # Read values + I2C_PCF8574_Get($hash, $name); + my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0); + if ($pollInterval > 0) { + InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_PCF8574_Poll', $hash, 0); + } +} +################################### +sub I2C_PCF8574_I2CRec($@) { # über CallFn vom physical aufgerufen + my ($hash, $clientmsg) = @_; + my $name = $hash->{NAME}; + my $phash = $hash->{IODev}; + my $pname = $phash->{NAME}; + while ( my ( $k, $v ) = each %$clientmsg ) { #erzeugen von Internals für alle Keys in $clientmsg die mit dem physical Namen beginnen + $hash->{$k} = $v if $k =~ /^$pname/ ; + } + my $sval; + if ($clientmsg->{direction} && $clientmsg->{$pname . "_SENDSTAT"} && $clientmsg->{$pname . "_SENDSTAT"} eq "Ok") { + readingsBeginUpdate($hash); + if ($clientmsg->{direction} eq "i2cread" && defined($clientmsg->{received})) { # =~ m/^(0x|)[0-9A-F]{1,2}$/xi) { + foreach (0..7) { + #$sval = hex($clientmsg->{received}) & (1 << $_); + $sval = $clientmsg->{received} & (1 << $_); + $sval = $sval == 0 ? "off" :"on"; + readingsBulkUpdate($hash, 'Port'.$_ , $sval) if (ReadingsVal($name,'Port'.$_,"") ne $sval); + } + readingsBulkUpdate($hash, 'state', $clientmsg->{received}); + } elsif ($clientmsg->{direction} eq "i2cwrite" && defined($clientmsg->{data})) { # =~ m/^(0x|)[0-9A-F]{1,2}$/xi) { + my @inports = sort(split( " ",AttrVal($name, "InputPorts", ""))); + foreach (0..7) { + #$sval = hex($clientmsg->{data}) & (1 << $_); + $sval = $clientmsg->{data} & (1 << $_); + $sval = $sval == 0 ? "off" :"on"; + readingsBulkUpdate($hash, 'Port'.$_ , $sval) unless (ReadingsVal($name,'Port'.$_,"") eq $sval || $_ ~~ @inports ); + } + readingsBulkUpdate($hash, 'state', $clientmsg->{data}); + } + #readingsBulkUpdate($hash, 'state', join(" ", $clientmsg->{received})); + readingsEndUpdate($hash, 1); + } +} +################################### +sub I2C_PCF8574_Parse($$) { #wird über dispatch vom physical device aufgerufen (dispatch wird im mom nicht verwendet) + my ($hash, $msg) = @_; + my($sid, $addr, @msg) = split(/ /,$msg); + #Log3 $hash, 4, "Vom Netzerparse $hash->{NAME}: sid: $sid, Msg: @msg"; + + my $def = $modules{I2C_PCF8574}{defptr}{"$sid $addr"}; + if($def) { + my @list; + foreach my $n (keys %{ $def }) { + my $lh = $def->{$n}; # Hash bekommen + $n = $lh->{NAME}; # It may be renamed + return "" if(IsIgnored($n)); # Little strange. + ################################################ + my $cde = join(" ",@msg); + my $sval; + readingsBeginUpdate($lh); + if ( int(@msg) == 1) { + for (my $i = 0; $i < 8; $i++) { + #$sval = hex($cde) & (1 << $i); + $sval = $cde & (1 << $i); + $sval = $sval == 0 ? "0" :"1"; + readingsBulkUpdate($lh, 'P'.$i , $sval) if (ReadingsVal($n,'P'.$i,2) ne $sval); + } + } + readingsBulkUpdate($lh, 'state', join(" ", @msg)); + readingsEndUpdate($lh, 1); + ################################################ + Log3 $n, 4, "I2C_PCF8574 $n $cde"; + + push(@list, $n); + } + return @list; + + } else { + Log3 $hash, 3, "I2C_PCF8574 Unknown device $addr Id $sid"; + #return "UNDEFINED I2C_PCF8574_$addr$sid I2C_PCF8574 $addr $sid"; + } + +} +################################### +1; + +=pod +=begin html + + +

I2C_PCF8574

+
    + + Provides an interface to the PCA9532 8 channel port extender IC. On Raspberry Pi the Interrupt Pin can be connected to an GPIO and RPI_GPIO can be used to get the port values if an interrupt occurs.
    + The I2C messages are send through an I2C interface module like RPII2C, FRM + or NetzerI2C so this device must be defined first.
    + attribute IODev must be set
    +
    + Define +
      + define <name> I2C_PCF8574 <I2C Address>
      + where <I2C Address> is an 2 digit hexadecimal value
      +
    + + + Set +
      + set <name> <port> <value>

      +
        +
      • <port> is one of Port0 to Port7 and <value> is one of:
        +
          + + off
          + on
          +
          +
        +
      • +
      +
      + Example: +
        + set mod1 Port4 on
        +

      +
    + + + Get +
      + get <name> +

      + refreshes all readings +

    + + + Attributes +
      +
    • poll_interval
      + Set the polling interval in minutes to query the GPIO's level
      + Default: -, valid values: decimal number

      +
    • +
    • InputPorts
      + Space separated list of Portnumers that are used as Inputs
      + Ports in this list can't be written
      + Default: no, valid values: 0 1 2 .. 7

      +
    • +
    • IODev
    • +
    • ignore
    • +
    • do_not_notify
    • +
    • showtime
    • +
    +
    +
+ +=end html + +=begin html_DE + + +

I2C_PCF8574

+
    + + Ermöglicht die Verwendung eines PCF8574 I2C 8 Bit Portexenders. + Auf einem Raspberry Pi kann der Interrupt Pin des PCF8574 mit einem GPIO verbunden werden und ¨ber die Interrupt Funktionen von RPI_GPIO l&aml;sst sich dann ein get für den PCF8574 bei Pegel&aml;nderung ausl&oml;sen.
    + I2C-Botschaften werden über ein I2C Interface Modul wie beispielsweise das RPII2C + oder NetzerI2C gesendet. Daher muss dieses vorher definiert werden.
    + Das Attribut IODev muss definiert sein.
    +
    + Define +
      + define <name> I2C_PCF8574 <I2C Address>
      + Der Wert <I2C Address> ist ein zweistelliger Hex-Wert
      +
    + + + Set +
      + set <name> <port> <value>

      +
        +
      • <port> kann Port0 bis Port7 annehmen und <value> folgende Werte:
        +
          + + off
          + on
          +
          +
        +
      • +
      +
      + Beispiel: +
        + set mod1 Port4 on
        +

      +
    + + + Get +
      + get <name> +

      + Aktualisierung aller Werte +

    + + + Attribute +
      +
    • poll_interval
      + Aktualisierungsintervall aller Werte in Minuten.
      + Standard: -, gültige Werte: Dezimalzahl

      +
    • +
    • InputPorts
      + Durch Leerzeichen getrennte Portnummern die als Inputs genutzt werden.
      + Ports in dieser Liste können nicht geschrieben werden.
      + Standard: no, gültige Werte: 0 1 2 .. 15

      +
    • +
    • IODev
    • +
    • ignore
    • +
    • do_not_notify
    • +
    • showtime
    • +
    +
    +
+ +=end html_DE + +=cut \ No newline at end of file diff --git a/fhem/FHEM/52_I2C_SHT21.pm b/fhem/FHEM/52_I2C_SHT21.pm new file mode 100644 index 000000000..d872a738b --- /dev/null +++ b/fhem/FHEM/52_I2C_SHT21.pm @@ -0,0 +1,358 @@ +############################################## +# $Id: 52_I2C_SHT21.pm 3764 2014-01-22 07:09:38Z klausw $ + +package main; + +use strict; +use warnings; + +use Time::HiRes qw(usleep); +use Scalar::Util qw(looks_like_number); +#use Error qw(:try); + +use constant { + SHT21_I2C_ADDRESS => '0x40', +}; + +################################################## +# Forward declarations +# +sub I2C_SHT21_Initialize($); +sub I2C_SHT21_Define($$); +sub I2C_SHT21_Attr(@); +sub I2C_SHT21_Poll($); +sub I2C_SHT21_Set($@); +sub I2C_SHT21_Undef($$); + + +my %sets = ( + 'readValues' => 1, +); + +sub I2C_SHT21_Initialize($) { + my ($hash) = @_; + + $hash->{DefFn} = 'I2C_SHT21_Define'; + $hash->{InitFn} = 'I2C_SHT21_Init'; + $hash->{AttrFn} = 'I2C_SHT21_Attr'; + $hash->{SetFn} = 'I2C_SHT21_Set'; + $hash->{UndefFn} = 'I2C_SHT21_Undef'; + $hash->{I2CRecFn} = 'I2C_SHT21_I2CRec'; + + $hash->{AttrList} = 'IODev do_not_notify:0,1 showtime:0,1 poll_interval:1,2,5,10,20,30 ' . + 'roundHumidityDecimal:0,1,2 roundTemperatureDecimal:0,1,2 ' . + $readingFnAttributes; +} + +sub I2C_SHT21_Define($$) { + my ($hash, $def) = @_; + my @a = split('[ \t][ \t]*', $def); + + $hash->{STATE} = "defined"; + + if ($main::init_done) { + eval { I2C_SHT21_Init( $hash, [ @a[ 2 .. scalar(@a) - 1 ] ] ); }; + return I2C_SHT21_Catch($@) if $@; + } + return undef; +} + +sub I2C_SHT21_Init($$) { + my ( $hash, $args ) = @_; + + my $name = $hash->{NAME}; + + if (defined $args && int(@$args) > 1) + { + return "Define: Wrong syntax. Usage:\n" . + "define I2C_SHT21 []"; + } + + if (defined (my $address = shift @$args)) { + $hash->{I2C_Address} = $address =~ /^0.*$/ ? oct($address) : $address; + return "$name I2C Address not valid" unless ($address < 128 && $address > 3); + } else { + $hash->{I2C_Address} = hex(SHT21_I2C_ADDRESS); + } + + + my $msg = ''; + # create default attributes + $msg = CommandAttr(undef, $name . ' poll_interval 5'); + if ($msg) { + Log3 ($hash, 1, $msg); + return $msg; + } + AssignIoPort($hash); + $hash->{STATE} = 'Initialized'; + +# my %sendpackage = ( i2caddress => $hash->{I2C_Address}, direction => "i2cread" ); +# $sendpackage{reg} = hex("AA"); +# $sendpackage{nbyte} = 22; +# return "$name: no IO device defined" unless ($hash->{IODev}); +# my $phash = $hash->{IODev}; +# my $pname = $phash->{NAME}; +# CallFn($pname, "I2CWrtFn", $phash, \%sendpackage); + + return undef; +} + +sub I2C_SHT21_Catch($) { + my $exception = shift; + if ($exception) { + $exception =~ /^(.*)( at.*FHEM.*)$/; + return $1; + } + return undef; +} + + +sub I2C_SHT21_Attr (@) {# hier noch Werteüberprüfung einfügen + my (undef, $name, $attr, $val) = @_; + my $hash = $defs{$name}; + my $msg = ''; + + if ($attr eq 'poll_interval') { + #my $pollInterval = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0; + + if ($val > 0) { + RemoveInternalTimer($hash); + InternalTimer(1, 'I2C_SHT21_Poll', $hash, 0); + } else { + $msg = 'Wrong poll intervall defined. poll_interval must be a number > 0'; + } + } elsif ($attr eq 'roundHumidityDecimal') { + $msg = 'Wrong $attr defined. Use one of 0, 1, 2' if defined($val) && $val >= 0 && $val <= 2 ; + } elsif ($attr eq 'roundTemperatureDecimal') { + $msg = 'Wrong $attr defined. Use one of 0, 1, 2' if defined($val) && $val >= 0 && $val <= 2 ; + } + return ($msg) ? $msg : undef; +} + +sub I2C_SHT21_Poll($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + + # Read values + I2C_SHT21_Set($hash, ($name, 'readValues')); + + my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0); + if ($pollInterval > 0) { + InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_SHT21_Poll', $hash, 0); + } +} + +sub I2C_SHT21_Set($@) { + my ($hash, @a) = @_; + my $name = $a[0]; + my $cmd = $a[1]; + + if(!defined($sets{$cmd})) { + return 'Unknown argument ' . $cmd . ', choose one of ' . join(' ', keys %sets) + } + + if ($cmd eq 'readValues') { + I2C_SHT21_readTemperature($hash); + I2C_SHT21_readHumidity($hash); + } +} + +sub I2C_SHT21_Undef($$) { + my ($hash, $arg) = @_; + + RemoveInternalTimer($hash); + return undef; +} + +sub I2C_SHT21_I2CRec ($$) { + my ($hash, $clientmsg) = @_; + my $name = $hash->{NAME}; + my $phash = $hash->{IODev}; + my $pname = $phash->{NAME}; + while ( my ( $k, $v ) = each %$clientmsg ) { #erzeugen von Internals für alle Keys in $clientmsg die mit dem physical Namen beginnen + $hash->{$k} = $v if $k =~ /^$pname/ ; + } + if ($clientmsg->{direction} && $clientmsg->{type} && $clientmsg->{$pname . "_SENDSTAT"} && $clientmsg->{$pname . "_SENDSTAT"} eq "Ok") { + if ( $clientmsg->{direction} eq "i2cread" && defined($clientmsg->{received}) ) { + Log3 $hash, 5, "empfangen: $clientmsg->{received}"; + I2C_SHT21_GetTemp ($hash, $clientmsg->{received}) if $clientmsg->{type} eq "temp" && $clientmsg->{nbyte} == 2; + I2C_SHT21_GetHum ($hash, $clientmsg->{received}) if $clientmsg->{type} eq "hum" && $clientmsg->{nbyte} == 2; + } + } +} + +sub I2C_SHT21_GetTemp ($$) { + my ($hash, $rawdata) = @_; + my @raw = split(" ",$rawdata); + my $temperature = $raw[0] << 8 | $raw[1]; + $temperature = ( 175.72 * $temperature / 2**16 ) - 46.85; + $temperature = sprintf( + '%.' . AttrVal($hash->{NAME}, 'roundTemperatureDecimal', 1) . 'f', + $temperature + ); + readingsSingleUpdate($hash,"temperature", $temperature, 1); +} + +sub I2C_SHT21_GetHum ($$) { + my ($hash, $rawdata) = @_; + my @raw = split(" ",$rawdata); + my $name = $hash->{NAME}; + my $temperature = ReadingsVal($name,"temperature","0"); + + my $humidity = $raw[0] << 8 | $raw[1]; + $humidity = ( 125 * $humidity / 2**16 ) - 6; + $humidity = sprintf( + '%.' . AttrVal($hash->{NAME}, 'roundHumidityDecimal', 1) . 'f', + $humidity + ); + readingsBeginUpdate($hash); + readingsBulkUpdate( + $hash, + 'state', + 'T: ' . $temperature . ' H: ' . $humidity + ); + #readingsBulkUpdate($hash, 'temperature', $temperature); + readingsBulkUpdate($hash, 'humidity', $humidity); + readingsEndUpdate($hash, 1); +} + + +sub I2C_SHT21_readTemperature($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + return "$name: no IO device defined" unless ($hash->{IODev}); + my $phash = $hash->{IODev}; + my $pname = $phash->{NAME}; + + # Write 0xF3 to device. This requests a temperature reading + my $i2creq = { i2caddress => $hash->{I2C_Address}, direction => "i2cwrite" }; + $i2creq->{data} = hex("F3"); + CallFn($pname, "I2CWrtFn", $phash, $i2creq); + usleep(85000); #für 14bit + + # Read the two byte result from device + my $i2cread = { i2caddress => $hash->{I2C_Address}, direction => "i2cread" }; + $i2cread->{nbyte} = 2; + $i2cread->{type} = "temp"; + CallFn($pname, "I2CWrtFn", $phash, $i2cread); + + return; +} + +sub I2C_SHT21_readHumidity($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + return "$name: no IO device defined" unless ($hash->{IODev}); + my $phash = $hash->{IODev}; + my $pname = $phash->{NAME}; + + # Write 0xF5 to device. This requests a humidity reading + my $i2creq = { i2caddress => $hash->{I2C_Address}, direction => "i2cwrite" }; + $i2creq->{data} = hex("F5"); + CallFn($pname, "I2CWrtFn", $phash, $i2creq); + usleep(39000); #für 12bit + + # Read the two byte result from device + my $i2cread = { i2caddress => $hash->{I2C_Address}, direction => "i2cread" }; + $i2cread->{nbyte} = 2; + $i2cread->{type} = "hum"; + CallFn($pname, "I2CWrtFn", $phash, $i2cread); + + return; # $retVal; +} + + +1; + +=pod +=begin html + + +

I2C_SHT21

+
    + + Provides an interface to the SHT21 I2C Humidity sensor from Sensirion. + The I2C messages are send through an I2C interface module like RPII2C, FRM + or NetzerI2C so this device must be defined first.
    + attribute IODev must be set
    +
    + Define +
      + define <name> I2C_SHT21 [<I2C Address>]
      + where <I2C Address> is an 2 digit hexadecimal value
      +
    + + Set +
      + set <name> readValues
      + Reads the current temperature and humidity values from sensor.

      +
    + + Attributes +
      +
    • poll_interval
      + Set the polling interval in minutes to query data from sensor
      + Default: 5, valid values: 1,2,5,10,20,30

      +
    • +
    • roundHumidityDecimal
      + Number of decimal places for humidity value
      + Default: 1, valid values: 0 1 2

      +
    • +
    • roundTemperatureDecimal
      + Number of decimal places for temperature value
      + Default: 1, valid values: 0,1,2

      +
    • +
    • IODev
    • +
    • do_not_notify
    • +
    • showtime
    • +

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

I2C_SHT21

+
    + + Ermöglicht die Verwendung eines SHT21 I2C Feuchtesensors von Sensirion. + I2C-Botschaften werden über ein I2C Interface Modul wie beispielsweise das RPII2C + oder NetzerI2C gesendet. Daher muss dieses vorher definiert werden.
    + Das Attribut IODev muss definiert sein.
    +
    + Define +
      + define <name> I2C_SHT21 [<I2C Address>]
      + Der Wert <I2C Address> ist ein zweistelliger Hex-Wert
      +
    + + Set +
      + set <name> readValues
      + Aktuelle Temperatur und Feuchte Werte vom Sensor lesen.

      +
    + + Attribute +
      +
    • poll_interval
      + Aktualisierungsintervall aller Werte in Minuten.
      + Standard: 5, gültige Werte: 1,2,5,10,20,30

      +
    • +
    • roundHumidityDecimal
      + Anzahl Dezimalstellen für den Feuchtewert
      + Standard: 1, gültige Werte: 0 1 2

      +
    • +
    • roundTemperatureDecimal
      + Anzahl Dezimalstellen für den Temperaturwert
      + Standard: 1, gültige Werte: 0,1,2

      +
    • +
    • IODev
    • +
    • do_not_notify
    • +
    • showtime
    • +

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