diff --git a/fhem/FHEM/52_I2C_EEPROM.pm b/fhem/FHEM/52_I2C_EEPROM.pm new file mode 100644 index 000000000..418b6a989 --- /dev/null +++ b/fhem/FHEM/52_I2C_EEPROM.pm @@ -0,0 +1,400 @@ +############################################################################## +# $Id: 52_I2C_EEPROM.pm 7007 2014-11-17 18:03:42Z klauswitt $ +############################################################################## +# Modul for I2C EEPROM +# +# contributed by Klaus Wittstock (2013) email: klauswittstock bei gmail +############################################################################## + +package main; +use strict; +use warnings; +use SetExtensions; +use Scalar::Util qw(looks_like_number); + +my %setsP = ( +'off' => 0, +'on' => 1, +); + +############################################################################### +sub I2C_EEPROM_Initialize($) { + my ($hash) = @_; + $hash->{DefFn} = "I2C_EEPROM_Define"; + $hash->{InitFn} = 'I2C_EEPROM_Init'; + $hash->{UndefFn} = "I2C_EEPROM_Undefine"; + $hash->{AttrFn} = "I2C_EEPROM_Attr"; + $hash->{SetFn} = "I2C_EEPROM_Set"; + $hash->{GetFn} = "I2C_EEPROM_Get"; + $hash->{I2CRecFn} = "I2C_EEPROM_I2CRec"; + $hash->{AttrList} = "IODev do_not_notify:1,0 ignore:1,0 showtime:1,0 ". + "EEPROM_size:2k,128 poll_interval ". + "$readingFnAttributes"; +} +############################################################################### +sub I2C_EEPROM_Define($$) { + my ($hash, $def) = @_; + my @a = split("[ \t]+", $def); + $hash->{STATE} = 'defined'; + if ($main::init_done) { + eval { I2C_EEPROM_Init( $hash, [ @a[ 2 .. scalar(@a) - 1 ] ] ); }; + return I2C_EEPROM_Catch($@) if $@; + } + return undef; +} +############################################################################### +sub I2C_EEPROM_Init($$) { #Geraet beim anlegen/booten/nach Neuverbindung (wieder) initialisieren + my ( $hash, $args ) = @_; + if (defined $args && int(@$args) != 1) { + return "Define: Wrong syntax. Usage:\n" . + "define I2C_EEPROM "; + } + if (defined (my $address = shift @$args)) { + $hash->{I2C_Address} = $address =~ /^0.*$/ ? oct($address) : $address; + } else { + return "$hash->{NAME} I2C Address not valid"; + } + AssignIoPort($hash); + I2C_EEPROM_Get($hash, $hash->{NAME}); + $hash->{STATE} = 'Initialized'; + return undef; +} +############################################################################### +sub I2C_EEPROM_Catch($) { #Fehlermeldung von eval formattieren + my $exception = shift; + if ($exception) { + $exception =~ /^(.*)( at.*FHEM.*)$/; + return $1; + } + return undef; +} +############################################################################### +sub I2C_EEPROM_Undefine($$) { + my ($hash, $arg) = @_; + if ( defined (AttrVal($hash->{NAME}, "poll_interval", undef)) ) { + RemoveInternalTimer($hash); + } +} +############################################################################### +sub I2C_EEPROM_Attr(@) { + my ($command, $name, $attr, $val) = @_; + my $hash = $defs{$name}; + my $msg = ''; + if ($command && $command eq "set" && $attr && $attr eq "IODev") { + if ($main::init_done and (!defined ($hash->{IODev}) or $hash->{IODev}->{NAME} ne $val)) { + main::AssignIoPort($hash,$val); + my @def = split (' ',$hash->{DEF}); + I2C_EEPROM_Init($hash,\@def) if (defined ($hash->{IODev})); + } + } + 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_EEPROM_Poll', $hash, 0); + } else { + $msg = 'Wrong poll intervall defined. poll_interval must be a number > 0'; + } + } + return ($msg) ? $msg : undef; +} +############################################################################### +sub I2C_EEPROM_Poll($) { #function for refresh intervall + my ($hash) = @_; + my $name = $hash->{NAME}; + # Read values + I2C_EEPROM_Get($hash, $name); + my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0); + if ($pollInterval > 0) { + InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_EEPROM_Poll', $hash, 0); + } +} +############################################################################### +sub I2C_EEPROM_Set($@) { + my ($hash, @a) = @_; + my $name =$a[0]; + my $cmd = $a[1]; + my $val = $a[2]; + my $msg = undef; + if (@a > 2) { + if (@a == 4) { + if ($a[2] =~ m/^(B|b)(it|)((0|)[0-7])$/i) { + my $bit = $a[2]; + $bit =~ tr/(B|b)(it|)//d; #Nummer aus String extrahieren + $bit = $bit =~ /^0.*$/ ? oct($bit) : $bit; + my $val = hex( I2C_EEPROM_BytefromReading($hash, $cmd) ); + my $mask = 1 << $bit; + if ($a[3] eq "1") { + $val |= $mask; # set bit + } else { + $val &= ~$mask; # clear bit + } + } else { + return "Unknown argument $a[2] use \"set [Bit] \" where is 0..7 and value is 0..255 (or 0|1 if you use Bit)"; + } + } + $val = $val =~ /^0.*$/ ? oct($val) : $val; + $cmd = $cmd =~ /^0.*$/ ? oct($cmd) : $cmd; + if (looks_like_number($cmd)) { + my $nbyte = ( (AttrVal($hash->{NAME}, "EEPROM_size", "128") eq "2k") ? 256 : 16 ); + if ($nbyte > $cmd ) { + $msg = I2C_EEPROM_SetReg($hash, $cmd, $val); + } else { + $msg = "$name error: $cmd is outside of address range (". $nbyte - 1 .")"; + } + } + } + return ($msg) ? $msg : undef; + +} +############################################################################### +sub I2C_EEPROM_Get($@) { + my ($hash, @a) = @_; + my $name =$a[0]; + #my $args = int(@$a); GEHT NICHT + my $nbyte = ( (AttrVal($hash->{NAME}, "EEPROM_size", "128") eq "2k") ? 256 : 16 ); + #my $reg = 0; + my %sendpackage = ( i2caddress => $hash->{I2C_Address}, direction => "i2cread" ); + $sendpackage{reg} = 0; + $sendpackage{nbyte} = $nbyte; + return "$name: no IO device defined" unless ($hash->{IODev}); + my $phash = $hash->{IODev}; + my $pname = $phash->{NAME}; + CallFn($pname, "I2CWrtFn", $phash, \%sendpackage); + + if ( defined $a[1]) { + $a[1] = $a[1] =~ /^0.*$/ ? oct($a[1]) : $a[1]; + if (looks_like_number($a[1]) ) { + return "$name error: $a[1] is outside of address range (". $nbyte - 1 .")" unless ($nbyte > $a[1] ); + my $rbyte = I2C_EEPROM_BytefromReading($hash, $a[1]); + if ( defined $a[2]) { + if ($a[2] =~ m/^B(it|)((0|)[0-7])$/i){ + $a[2] =~ tr/(B|b)(it|)//d; #Nummer aus String extrahieren + $rbyte = (( hex($rbyte) >> $a[2] ) & 1) == 1 ? 1 : 0 ; + } else { + return "$name error: $a[2] is outside of range (Bit0..Bit7)"; + } + } + return $rbyte; + } + } + +} +############################################################################### +sub I2C_EEPROM_I2CRec($@) { #ueber 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 fuer alle Keys in $clientmsg die mit dem physical Namen beginnen + $hash->{$k} = $v if $k =~ /^$pname/ ; + } + if ($clientmsg->{direction} && defined $clientmsg->{reg} && $clientmsg->{$pname . "_SENDSTAT"} && $clientmsg->{$pname . "_SENDSTAT"} eq "Ok" ) { + if ($clientmsg->{direction} eq "i2cread" && $clientmsg->{received}) { + my @rec = split(" ",$clientmsg->{received}); + Log3 $hash, 3, "$name: wrong amount of registers transmitted from $pname" unless (@rec == $clientmsg->{nbyte}); + foreach (reverse 0..$#rec) { + I2C_EEPROM_UpdReadings($hash, $_ + $clientmsg->{reg} , $rec[$_]); + } + readingsSingleUpdate($hash,"state", "Ok", 1); + } elsif ($clientmsg->{direction} eq "i2cwrite" && defined $clientmsg->{data}) { #readings aktualisieren wenn uebertragung ok + I2C_EEPROM_UpdReadings($hash, $clientmsg->{reg} , $clientmsg->{data}); + readingsSingleUpdate($hash,"state", "Ok", 1); + } else { + readingsSingleUpdate($hash,"state", "transmission error", 1); + Log3 $hash, 3, "$name: failurei in message from $pname"; + Log3 $hash, 3,(defined($clientmsg->{direction}) ? "Direction: " . $clientmsg->{direction} : "Direction: undef"). + (defined($clientmsg->{i2caddress}) ? " I2Caddress: " . sprintf("0x%.2X", $clientmsg->{i2caddress}) : " I2Caddress: undef"). + (defined($clientmsg->{reg}) ? " Register: " . sprintf("0x%.2X", $clientmsg->{reg}) : " Register: undef"). + (defined($clientmsg->{data}) ? " Data: " . sprintf("0x%.2X", $clientmsg->{data}) : " Data: undef"). + (defined($clientmsg->{received}) ? " received: " . sprintf("0x%.2X", $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: " . sprintf("0x%.2X", $clientmsg->{i2caddress}) : " I2Caddress: undef"). + (defined($clientmsg->{reg}) ? " Register: " . sprintf("0x%.2X", $clientmsg->{reg}) : " Register: undef"). + (defined($clientmsg->{data}) ? " Data: " . sprintf("0x%.2X", $clientmsg->{data}) : " Data: undef"). + (defined($clientmsg->{received}) ? " received: " . sprintf("0x%.2X", $clientmsg->{received}) : " received: undef"); + } +} +############################################################################### +sub I2C_EEPROM_UpdReadings($$$) { #nach Rueckmeldung readings updaten (ueber I2CRec aufgerufen) + my ($hash, $reg, $inh) = @_; + my $name = $hash->{NAME}; + Log3 $hash, 5, "$name UpdReadings Register: $reg, Inhalt: $inh"; + my $regb = $reg >> 4; + my $regp = $reg & 15; + my $bank = ReadingsVal($name,"0x".sprintf("%02X",$regb)."x",".. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .."); + my $nbank = $bank; + substr($nbank,$regp * 3,2,sprintf("%02X",$inh)); + if ($nbank ne $bank) { #bei Aenderung + readingsBeginUpdate($hash); + readingsBulkUpdate($hash, "0x".sprintf("%02X",$regb)."x" , $nbank); + readingsEndUpdate($hash, 1); + } + return; +} +############################################################################### +sub I2C_EEPROM_SetReg { #set register + my ($hash, $reg, $inh) = @_; + + if (defined (my $iodev = $hash->{IODev})) { + CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, { + direction => "i2cwrite", + i2caddress => $hash->{I2C_Address}, + reg => $reg, + data => $inh, + }) if (defined $hash->{I2C_Address}); + } else { + return "no IODev assigned to '$hash->{NAME}'"; + } +} +############################################################################### +sub I2C_EEPROM_BytefromReading($@) { + my ($hash, $reg) = @_; + my $regb = $reg >> 4; + my $regp = $reg & 15; + my $bank = ReadingsVal($hash->{NAME},"0x".sprintf("%02X",$regb)."x",".. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .."); + return substr($bank,$regp * 3,2); +} +1; + +=pod +=begin html + + +

I2C_EEPROM

+
    + + Provides an interface to an I2C EEPROM.
    + 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_EEPROM <I2C Address>
      + where <I2C Address> is without direction bit
      +
    + + + Set +
      + set <name> <byte address> <value>

      + where <byte address> is a number (0..device specific) and <value> is a number (0..255)
      + both numbers can be written in decimal or hexadecimal notation.
      +
      + Example: +
        + set eeprom1 0x02 0xAA
        + set eeprom1 2 170
        +

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

      + refreshes all readings +

    +
      + get <name> <byte address> [Bit<bitnumber(0..7)>] +

      + returnes actual reading of stated <byte address> or a single bit of <byte address>
      + Values are readout from readings, NOT from device! +

    + + + + Attributes +
      +
    • poll_interval
      + Set the polling interval in minutes to query the EEPROM content
      + Default: -, valid values: decimal number

      +
    • +
    • EEPROM_size
      + Sets the storage size of the EEPROM
      + Default: 128, valid values: 128 (128bit), 2k (2048bit)

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

I2C_EEPROM

+
    + + Ermöglicht die Verwendung I2C EEPROM. + I2C-Botschaften werden über ein I2C Interface Modul wie beispielsweise das RPII2C, FRM + oder NetzerI2C gesendet. Daher muss dieses vorher definiert werden.
    + Das Attribut IODev muss definiert sein.
    +
    + Define +
      + define <name> I2C_EEPROM <I2C Address>
      + Der Wert <I2C Address> ist ohne das Richtungsbit +
    + + + Set +
      + set <name> <byte address> <value>

      + <byte address> ist die Registeradresse (0..IC abhängig) und <value> der Registerinhalt (0..255)
      + Beide Zahlen können sowohl eine Dezimal- als auch eine Hexadezimalzahl sein.
      +
      + Beispiel: +
        + set eeprom1 0x02 0xAA
        + set eeprom1 2 170
        +

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

      + Aktualisierung aller Werte +

    +
      + get <name> <byte address> [Bit<bitnumber(0..7)>] +

      + Gibt den Inhalt des in <byte address> angegebenen Registers zurück, bzw. ein einzelnes Bit davon.
      + Achtung mit diesem Befehl werden nur die Werte aus den Readings angezeigt und nicht der Registerinhalt selbst! +

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

      +
    • +
    • EEPROM_size
      + Speichergröße des EEPROM
      + Standard: 128, gültige Werte: 128 (128bit), 2k (2048bit)

      +
    • +
    • IODev
    • +
    • ignore
    • +
    • do_not_notify
    • +
    • showtime
    • +
    +
    +
+ +=end html_DE + +=cut \ No newline at end of file