From ef2ca7cd2ecf94bcef51c76d1399748bbd67d00e Mon Sep 17 00:00:00 2001 From: schlawiano Date: Fri, 27 May 2016 14:03:44 +0000 Subject: [PATCH] I2C_HDC1008: added new module for measurement temperature and humidity via i2c-bus I2C_GY30_BH1750FVI: added new module for light intensity via via i2c-bus git-svn-id: https://svn.fhem.de/fhem/trunk@11531 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/52_I2C_GY30_BH1750FVI.pm | 640 ++++++++++++++++++++++++++++ fhem/FHEM/52_I2C_HDC1008.pm | 654 +++++++++++++++++++++++++++++ 2 files changed, 1294 insertions(+) create mode 100644 fhem/FHEM/52_I2C_GY30_BH1750FVI.pm create mode 100644 fhem/FHEM/52_I2C_HDC1008.pm diff --git a/fhem/FHEM/52_I2C_GY30_BH1750FVI.pm b/fhem/FHEM/52_I2C_GY30_BH1750FVI.pm new file mode 100644 index 000000000..79226c2e4 --- /dev/null +++ b/fhem/FHEM/52_I2C_GY30_BH1750FVI.pm @@ -0,0 +1,640 @@ +# Modul für I2C Lichtsensor GY-30 mit dem AD-Wandler BH1750FVI +# Autor : Karsten Grüttner +# $Id$ +# Technische Dokumention für den Sensor befindet sich http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1750fvi-e.pdf + + +package main; +use strict; +use warnings; +use Time::HiRes qw(usleep); + + + + + +# Konfigurationsparameter Auflösung, delay nur im Continuously-Mode nach erstem Lesen, ansonsten delayInit +my %I2C_GY30_BH1750FVI_resParams = # +( + 'HalfLux' => {delay => 120000, code => 1, delayInit => 180000 }, + '1Lux' => {delay => 120000, code => 0, delayInit => 180000 } , + '4Lux' => {delay => 16000, code => 3, delayInit => 24000, } +); + + +# Konfigurationsparameter Betriebsmode +my %I2C_GY30_BH1750FVI_CodeMode = +( + 'Continuously' => 0x10, # einmalig initialisiert, kann immer gelesen werden, geeignet für Dauerüberwachung z.B. Lichtschranke + 'One' => 0x20 # wacht zum einmaligen Lesen auf und legt sich wieder schlafen, geeignet z.B. die Lichtverhältnisse draußen zu messen +); + +# Konfigurationsparameter Befehle +my %I2C_GY30_BH1750FVI_CodeCmd = +( + 'PowerDown' => 0, + 'PowerOn' => 1, + 'Reset' => 7 +); + + +sub I2C_GY30_BH1750FVI_Initialize($) { + my ($hash) = @_; + + $hash->{DefFn} = 'I2C_GY30_BH1750FVI_Define'; + $hash->{UndefFn} = 'I2C_GY30_BH1750FVI_Undef'; + $hash->{SetFn} = 'I2C_GY30_BH1750FVI_Set'; + $hash->{GetFn} = 'I2C_GY30_BH1750FVI_Get'; + $hash->{AttrFn} = 'I2C_GY30_BH1750FVI_Attr'; + $hash->{ReadFn} = 'I2C_GY30_BH1750FVI_Read'; + $hash->{I2CRecFn} = 'I2C_GY30_BH1750FVI_I2CRec'; + $hash->{AttrList} = + "interval ". + "IODev ". + "Resolution:HalfLux,1Lux,4Lux ". # als Dropdown + "OperationMode:Continuously,One ". # als Dropdown + "roundLightIntensityDecimal ". + $readingFnAttributes; + + +} + + +sub I2C_GY30_BH1750FVI_Define($$) { + my ($hash, $def) = @_; + my @a = split('[ \t][ \t]*', $def); + + $hash->{MODUL_STATE} = "defined"; + $hash->{RESOLUTION} = 'HalfLux'; + $hash->{OPERATION_MODE} = 'One'; + $hash->{INTERVAL} = 0; + + + if ($main::init_done) { + eval { I2C_GY30_BH1750FVI_Init( $hash, [ @a[ 2 .. scalar(@a) - 1 ] ] ); }; + return I2C_GY30_BH1750FVI_Catch($@) if $@; + } + else + { + Log3 $hash, 5, "[$hash->{NAME}] I2C_GY30_BH1750FVI_Define main::init_done was false"; + } + + return undef; +} + +sub I2C_GY30_BH1750FVI_Init($$) { + my ( $hash, $args ) = @_; + + my $name = $hash->{NAME}; + + + + if (defined $args && int(@$args) > 1) + { + return "Define: Wrong syntax. Usage:\n" . + "define I2C_GY30_BH1750FVI []"; + } + + if (defined (my $address = shift @$args)) + { + $address = $address =~ /^0.*$/ ? oct($address) : $address; + if (! ($address == 35 || $address == 92)) # nur 0x23 (ohne Jumper) oder 0x5C (mit Jumper auf Pin "Add" gegen UCC) + { + Log3 $hash, 5, "[$name] I2C Address not valid for GY-30 BH1750FVI"; + return "$name I2C Address not valid for GY-30 BH1750FVI"; + } + else + { + $hash->{I2C_Address} = $address; + } + + } + else + { + $hash->{I2C_Address} = oct('0x23'); + Log3 $name, 5, "[$name] I2C_GY30_BH1750FVI_Init default-I2C-addresse 0x23 used"; + } + + + my $msg = ''; + + $msg = CommandAttr(undef, $name . ' interval 5'); + if ($msg) { + + Log3 $hash, 5, "[$name] I2C_GY30_BH1750FVI_Init interval:".$msg; + return $msg; + } + + AssignIoPort($hash); + + if (defined AttrVal($hash->{NAME}, "IODev", undef)) + { + $hash->{MODUL_STATE} = 'Initialized'; + I2C_GY30_BH1750FVI_InitDevice($hash); + + } + else + { + $hash->{MODUL_STATE} = "Error: Missing Attr 'IODev'"; + } + + return undef; +} + +sub I2C_GY30_BH1750FVI_Catch($) { + my $exception = shift; + if ($exception) { + $exception =~ /^(.*)( at.*FHEM.*)$/; + return $1; + } + return undef; +} + +sub I2C_GY30_BH1750FVI_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 fuer alle Keys in $clientmsg die mit dem physical Namen beginnen + my $upper_k = uc $k; + $hash->{$upper_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, "[$name] I2C_GY30_BH1750FVI_I2CRec received: $clientmsg->{type} $clientmsg->{received}"; + I2C_GY30_BH1750FVI_GetLightIntensity ($hash, $clientmsg->{received}) if $clientmsg->{type} eq "light" && $clientmsg->{nbyte} == 2; + + } + } +} + +sub I2C_GY30_BH1750FVI_GetLightIntensity ($$) +{ + my ($hash, $rawdata) = @_; + my $name = $hash->{NAME}; + + my @raw = split(" ",$rawdata); + + + my $LightIntensity = ($raw[1] + $raw[0] * 256) /1.2; + + Log3 $hash, 5, "[$name] I2C_GY30_BH1750FVI_I2CRec ".$raw[1].'x'.$raw[0]." calced Light: $LightIntensity"; + + + $LightIntensity = sprintf( '%.' . AttrVal($hash->{NAME}, 'roundLightIntensityDecimal', 1) . 'f', $LightIntensity ); + + + + readingsBeginUpdate($hash); + readingsBulkUpdate($hash, 'light_intensity', $LightIntensity); + + readingsBulkUpdate( + $hash, + 'state', + 'L: ' . $LightIntensity + ); + + + readingsEndUpdate($hash, 1); + +} + + + +sub I2C_GY30_BH1750FVI_Undef($$) +{ + my ($hash, $name) = @_; + + if ( defined (AttrVal($hash->{NAME}, "interval", undef)) ) + { + RemoveInternalTimer($hash); + } + + return undef; +} + +# schickt ein Reset, PowerDown oder PowerOn zum Sensor +sub I2C_GY30_BH1750FVI_Command($$) +{ + my ($hash, $cmd) = @_; + my $name = $hash->{NAME}; + + if ($hash->{MODUL_STATE} ne 'Initialized') { return "Error MODULE_STATE in $name is not 'Initialized' " }; + return "$name: no IO device defined" unless ($hash->{IODev}); + + my $iodev = $hash->{IODev}; + my $i2caddress = $hash->{I2C_Address}; + + my $code = $I2C_GY30_BH1750FVI_CodeCmd{$cmd}; + + CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, { + direction => "i2cwrite", + i2caddress => $i2caddress, + data => $code + }); + Time::HiRes::usleep(5); # sollte schnell gehen, aber ob die mikrosekunde ausreicht, wird man sehen. +} + +# initialisiert das Gerät +# bei One-Modus, legt er den Sensor schlafen, ansonsten wird er Anhand Auflösung-Parameter in Dauerbetrieb gesetzt + +sub I2C_GY30_BH1750FVI_InitDevice($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + if ($hash->{MODUL_STATE} ne 'Initialized') { return "Error MODULE_STATE in $name is not 'Initialized' " }; + return "$name: no IO device defined" unless ($hash->{IODev}); + + + if ($hash->{OPERATION_MODE} eq 'One') + { + I2C_GY30_BH1750FVI_Command($hash, 'PowerDown'); # bei One kann das Gerät ausgeschalten werden + + } + elsif ($hash->{OPERATION_MODE} eq 'Continuously') + { + + my $resolutionIndex = $hash->{RESOLUTION}; + + my $codeCont = $I2C_GY30_BH1750FVI_CodeMode{'Continuously'}; + my $codeResolution = $I2C_GY30_BH1750FVI_resParams{$resolutionIndex}{code}; + my $code = $codeCont | $codeResolution; + + + my $delay = $I2C_GY30_BH1750FVI_resParams{$resolutionIndex}{delayInit}; + + my $iodev = $hash->{IODev}; + my $i2caddress = $hash->{I2C_Address}; + + Log3 $hash, 5, "[$name] I2C_GY30_BH1750FVI_InitDevice send config with ".sprintf("0x%X", $code); + + CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, { + direction => "i2cwrite", + i2caddress => $i2caddress, + data => $code + }); + + Log3 $hash, 5, "[$name] I2C_GY30_BH1750FVI_InitDevice wait for ".($delay/1000)." ms" ; + Time::HiRes::usleep($delay); + } + +} + +sub I2C_GY30_BH1750FVI_UpdateValues($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + if ($hash->{MODUL_STATE} ne 'Initialized') { return "Error MODULE_STATE in $name is not 'Initialized' " }; + + my $iodev = $hash->{IODev}; + my $i2caddress = $hash->{I2C_Address}; + + + my $resolutionIndex = $hash->{RESOLUTION}; + my $delay = $I2C_GY30_BH1750FVI_resParams{$resolutionIndex}{delay}; + + if ($hash->{OPERATION_MODE} eq 'One') + { + + + my $codeCont = $I2C_GY30_BH1750FVI_CodeMode{'One'}; + my $codeResolution = $I2C_GY30_BH1750FVI_resParams{$resolutionIndex}{code}; + my $code = $codeCont | $codeResolution; + + + + Log3 $hash, 5, "[$name] I2C_GY30_BH1750FVI_UpdateValues send config with ".sprintf("0x%X", $code); + + CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, { + direction => "i2cwrite", + i2caddress => $i2caddress, + data => $code + }); + $delay = $I2C_GY30_BH1750FVI_resParams{$resolutionIndex}{delayInit}; + } + + Log3 $hash, 5, "[$name] I2C_GY30_BH1750FVI_UpdateValues wait for ".($delay/1000)." ms" ; + + Time::HiRes::usleep($delay); + + CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, { # Leider fehlt es hier an Doku. daher hier der Hinweis bei erfolgreichem Lesen wird die Funktion in $hash->{I2CRecFn} aufgerufen + direction => "i2cread", + i2caddress => $i2caddress, + type => "light", + nbyte => 2 + }); + + + return "$name: no IO device defined" unless ($hash->{IODev}); + + + +} + +sub I2C_GY30_BH1750FVI_Get($@) { + my ($hash, @param) = @_; + + + + + I2C_GY30_BH1750FVI_UpdateValues($hash); + + +} + +# set wenn Befehl gesetzt wurde und nicht '?' ist, +# dann führe Befehl aus und gib den Status zurück +# ansonsten gib alle Befehle und deren Optionen zurück + +sub I2C_GY30_BH1750FVI_Set($@) +{ + my ($hash, @param) = @_; + + + + return '"set GY30_BH1750FVI" needs at least one argument' if (int(@param) < 2); + + my $name = shift @param; + my $cmd = shift @param; + my $val = join("", @param); + + if (defined $cmd && $cmd ne '?') # falls set mit Kommand aufgerufen wurde + { + if (defined($I2C_GY30_BH1750FVI_CodeCmd{$cmd})) + { + I2C_GY30_BH1750FVI_Command($hash,$cmd); + return undef; + } + elsif ($cmd eq 'Update') + { + I2C_GY30_BH1750FVI_UpdateValues($hash); + return undef; + } + elsif ($cmd eq 'ReConfig') + { + I2C_GY30_BH1750FVI_InitDevice($hash); + return undef; + } + + return "unknown command"; + + # Debug("Set GY30_BH1750FVI $cmd"); + + } + else # Ansonsten Rückgabe was an set - Optionen möglich ist + { + return "Update:noArg PowerDown:noArg PowerOn:noArg Reset:noArg ReConfig:noArg"; + } + + +} + +sub I2C_GY30_BH1750FVI_CheckState +{ + my ($hash) = @_; + if ($hash->{MODUL_STATE} ne 'Initialized') + { + + my @def = split (' ',$hash->{DEF}); + I2C_GY30_BH1750FVI_Init($hash,\@def) if (defined ($hash->{IODev})); + } +} + +sub I2C_GY30_BH1750FVI_Poll +{ + my ($hash) = @_; + I2C_GY30_BH1750FVI_CheckState($hash); + my $name = $hash->{NAME}; + + + + I2C_GY30_BH1750FVI_UpdateValues($hash); + + my $ret = I2C_GY30_BH1750FVI_Catch($@) if $@; + + # Debug("Update Werte"); + my $pollInterval = AttrVal($hash->{NAME}, 'interval', 0); + if ($pollInterval > 0) + { + Log3 $hash, 5, "[$name] I2C_GY30_BH1750FVI_Poll call InternalTimer with $pollInterval minutes"; + InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_GY30_BH1750FVI_Poll', $hash, 0); + } + else + { + Log3 $name, 5, "[$name] I2C_GY30_BH1750FVI_Poll dont call InternalTimer, not valid pollInterval"; + } + return; +} + +sub I2C_GY30_BH1750FVI_Attr(@) { + + my ($command, $name, $attr, $val) = @_; + my $hash = $defs{$name}; + my $msg = ''; + + if ($attr eq 'interval') + { + if ( defined($val) ) + { + if ( looks_like_number($val) && $val > 0) + { + RemoveInternalTimer($hash); + InternalTimer(1, 'I2C_GY30_BH1750FVI_Poll', $hash, 0); + $hash->{INTERVAL} = $val; + Log3 $hash, 5, "[$hash->{NAME}] I2C_GY30_BH1750FVI_Poll dont call InternalTimer, not valid pollInterval"; + } else + { + $msg .= "$hash->{NAME}: Wrong poll intervall defined. interval must be a number > 0"; + Log3 $hash, 5, "[$hash->{NAME}] I2C_GY30_BH1750FVI_Attr Wrong poll intervall defined. interval must be a number > 0"; + $hash->{INTERVAL} = 0; + } + } + else + { #wird auch aufgerufen wenn $val leer ist, aber der attribut wert wird auf 1 gesetzt + RemoveInternalTimer($hash); + $hash->{INTERVAL} = 0; + } + } + + elsif ($attr eq 'Resolution') + { + if (!defined($val)) + { + $hash->{RESOLUTION} = 'HalfLux'; + I2C_GY30_BH1750FVI_InitDevice($hash); + } + elsif ( defined($I2C_GY30_BH1750FVI_resParams{$val}{code}) ) + { + $hash->{RESOLUTION} = $val; + I2C_GY30_BH1750FVI_InitDevice($hash); + } + else + { + $msg .= "invalid value for attribute $attr"; + } + } + + elsif ($attr eq 'OperationMode') + { + if (!defined($val)) + { + $hash->{OPERATION_MODE} = 'One'; + I2C_GY30_BH1750FVI_InitDevice($hash); + } + elsif ( defined($I2C_GY30_BH1750FVI_CodeMode{$val}) ) + { + $hash->{OPERATION_MODE} = $val; + I2C_GY30_BH1750FVI_InitDevice($hash); + } + else + { + $msg .= "invalid value for attribute $attr"; + } + } + + elsif ($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_GY30_BH1750FVI_Init($hash,\@def) if (defined ($hash->{IODev})); + + } + } + + elsif ( $attr eq 'roundLightIntensityDecimal' ) + { + if (!defined($val)) + { + return undef; + } + elsif (!(looks_like_number($val) && ($val>=0 ))) + { + $msg .= "$attr must be a number >= 0" + } + } + + return ($msg) ? $msg : undef; +} + +1; + +=pod +=begin html + + +

I2C_GY30_BH1750FVI

+
    + + Provides an interface to the I2C GY-30 with chip BH1750FVI light intensity sensor from Texas Instruments. + 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_GY30_BH1750FVI [<I2C Address>]
      + where <I2C Address> is an 2 digit hexadecimal value
      +
    + + Set +
      + set <name> Update
      + Reads the current light intensity values from sensor.

      + set <name> Reset
      + Resets the sensor (only when sensor is power on).

      + set <name> ReConfig
      + Sends in Continuously mode the configuration parameter again to the sensor.

      + set <name> PowerDown
      + turn the sensor in standby (no active state).

      + set <name> PowerOn
      + turn on the sensor +
    + + Attributes +
      +
    • interval
      + Set the polling interval in minutes to query data from sensor
      + Default: 5, valid values: 1,2,5,10,20,30

      +
    • +
    • Resolution
      + resolution for measurement
      + Standard: HalfLux, valid values: HalfLux, 1Lux, 4Lux

      +
    • +
    • OperationMode
      + operation mode. One: One-time measurement , then the sensor turns off. Continuously: re- measure possible without re-configuration then sensor
      + standard: One, valid values: Continuously,One

      +
    • +
    • roundLightIntensityDecimal
      + Number of decimal places for light intensity value
      + Default: 1, valid values: 0 1 2,...

      +
    • + +
    • IODev
    • + +

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

I2C_GY30_BH1750FVI

+
    + + Ermöglicht die Verwendung eines I2C GY-30 mit Chip BH1750FVI Lichtstärke-Sensors von Texas Instruments. + 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_GY30_BH1750FVI [<I2C Address>]
      + Der Wert <I2C Address> ist ein zweistelliger Hex-Wert
      +
    + + Set +
      + set <name> Update
      + Aktuelle Lichtstärke vom Sensor lesen.

      + set <name> Reset ( nur bei eingeschalteten Sensor )
      + Setzt den Sensor zurück

      + set <name> ReConfig
      + Sendet im Continuously-Mode die Konfigurationsparameter erneut zum Sensor

      + set <name> PowerDown
      + Schaltet den Sensors in einen Ruhezustand

      + set <name> PowerOn
      + Schaltet den Sensors an +
    + + Attribute +
      +
    • interval
      + Aktualisierungsintervall aller Werte in Minuten.
      + Standard: 5, gültige Werte: 1,2,5,10,20,30

      +
    • +
    • Resolution
      + Genauigkeit mit der gemessen werden soll.
      + Standard: HalfLux, gültige Werte: HalfLux, 1Lux, 4Lux

      +
    • +
    • OperationMode
      + Betriebsmodus. One: Einmaliges Messen, dann schaltet der Sensor sich wieder aus. Continuously: erneutes Messen ohne Neukonfiguration des Sensors möglich
      + Standard: One, gültige Werte: Continuously,One

      +
    • +
    • roundLightIntensityDecimal
      + Anzahl Dezimalstellen für die Lichtstärke
      + Standard: 1, gültige Werte: 0,1,2

      +
    • +
    • IODev
    • + +

    +
+ +=end html + +=cut \ No newline at end of file diff --git a/fhem/FHEM/52_I2C_HDC1008.pm b/fhem/FHEM/52_I2C_HDC1008.pm new file mode 100644 index 000000000..b05c64a89 --- /dev/null +++ b/fhem/FHEM/52_I2C_HDC1008.pm @@ -0,0 +1,654 @@ +# Modul für I2C Temperatur- und Feuchtigkeitssensor HDC1008 +# Autor : Karsten Grüttner +# $Id$ +# Technische Dokumention für den Sensor befindet sich http://www.ti.com/lit/ds/symlink/hdc1008.pdf + +# es ist mein erstes Modul für FHEM (angelehnt an dem SHT21), +# ich bitte im Nachsicht und ggf. um Rückmeldung, wenn etwas nicht so passt + + +package main; +use strict; +use warnings; +use Time::HiRes qw(usleep); + + + +# Konfigurationsparameter Temperatur, Lesedauer in Microsekunden und Konfigurationscode als Word (Bit 10) +my %I2C_HDC1008_tempParams = +( + '11Bit' => {delay => 3650, code => 1 << 10 }, + '14Bit' => {delay => 6350, code => 0 } +); + + +# Konfigurationsparameter Feuchtigkeit, Lesedauer in Microsekunden und Konfigurationscode als Word (Bit 9:8) +my %I2C_HDC1008_humParams = # +( + '8Bit' => {delay => 2500, code => 1 << 9 }, + '11Bit' => {delay => 3850, code => 1 << 8 } , + '14Bit' => {delay => 6500, code => 0 } +); + + +# Konfigurationsparameter Heizelement, Konfigurationscode als Word (Bit 13 ) +my %I2C_HDC1008_validsHeater = +( + 'off' => 0, # 0 + 'on' => 1 << 13 # 1 +); + + +sub I2C_HDC1008_Initialize($) { + my ($hash) = @_; + + $hash->{DefFn} = 'I2C_HDC1008_Define'; + $hash->{UndefFn} = 'I2C_HDC1008_Undef'; + $hash->{SetFn} = 'I2C_HDC1008_Set'; + $hash->{GetFn} = 'I2C_HDC1008_Get'; + $hash->{AttrFn} = 'I2C_HDC1008_Attr'; + $hash->{ReadFn} = 'I2C_HDC1008_Read'; + $hash->{I2CRecFn} = 'I2C_HDC1008_I2CRec'; + $hash->{AttrList} = + "interval ". + "IODev ". + "Resolution_Temperature:11Bit,14Bit ". # als Dropdown + "Resolution_Humidity:8Bit,11Bit,14Bit ". # als Dropdown + "roundTemperatureDecimal ". + "roundHumidityDecimal ". + $readingFnAttributes; + + +} + + +sub I2C_HDC1008_Define($$) { + my ($hash, $def) = @_; + my @a = split('[ \t][ \t]*', $def); + + $hash->{MODUL_STATE} = "defined"; + $hash->{RESOLUTION_TEMPERATURE} = '14Bit'; + $hash->{RESOLUTION_HUMIDITY} = '14Bit'; + $hash->{HEATER} = 'off'; + $hash->{INTERVAL} = 0; + + if ($main::init_done) { + eval { I2C_HDC1008_Init( $hash, [ @a[ 2 .. scalar(@a) - 1 ] ] ); }; + return I2C_HDC1008_Catch($@) if $@; + } + else + { + Log3 $hash, 5, "[$hash->{NAME}] I2C_HDC1008_Define main::init_done was false"; + } + + return undef; +} + +sub I2C_HDC1008_Init($$) { + my ( $hash, $args ) = @_; + + my $name = $hash->{NAME}; + + + + if (defined $args && int(@$args) > 1) + { + return "Define: Wrong syntax. Usage:\n" . + "define I2C_HDC1008 []"; + } + + if (defined (my $address = shift @$args)) + { + $address = $address =~ /^0.*$/ ? oct($address) : $address; + if ($address < 64 && $address > 67) # nur 0x40 bis 0x43 erlaubt + { + Log3 $hash, 5, "[$name] I2C Address not valid for HDC1008"; + return "$name I2C Address not valid for HDC1008"; + } + else + { + $hash->{I2C_Address} = $address; + } + + } + else + { + $hash->{I2C_Address} = oct('0x40'); + Log3 $name, 5, "[$name] I2C_HDC1008_Init default-I2C-addresse 0x40 used"; + } + + my $msg = ''; + + $msg = CommandAttr(undef, $name . ' interval 5'); + if ($msg) { + + + Log3 $hash, 5, "[$name] I2C_HDC1008_Init interval:".$msg; + return $msg; + } + else + { + + } + + AssignIoPort($hash); + + if (defined AttrVal($hash->{NAME}, "IODev", undef)) + { + $hash->{MODUL_STATE} = 'Initialized'; + + } + else + { + $hash->{MODUL_STATE} = "Error: Missing Attr 'IODev'"; + } + + return undef; +} + +sub I2C_HDC1008_Catch($) { + my $exception = shift; + if ($exception) { + $exception =~ /^(.*)( at.*FHEM.*)$/; + return $1; + } + return undef; +} + +sub I2C_HDC1008_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 fuer alle Keys in $clientmsg die mit dem physical Namen beginnen + my $upper_k = uc $k; + $hash->{$upper_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, "[$name] I2C_HDC1008_I2CRec received: $clientmsg->{type} $clientmsg->{received}"; + I2C_HDC1008_GetTemp ($hash, $clientmsg->{received}) if $clientmsg->{type} eq "temp" && $clientmsg->{nbyte} == 2; + I2C_HDC1008_GetHum ($hash, $clientmsg->{received}) if $clientmsg->{type} eq "hum" && $clientmsg->{nbyte} == 2; + } + } +} + +sub I2C_HDC1008_GetTemp ($$) +{ + my ($hash, $rawdata) = @_; + my $name = $hash->{NAME}; + + my @raw = split(" ",$rawdata); + my $tempWord = ($raw[0] << 8 | $raw[1]); + + my $temperature = (($tempWord /65536.0)*165.0)-40.0; + + Log3 $hash, 5, "[$name] I2C_HDC1008_I2CRec calced Temperatur: $temperature"; + + + $temperature = sprintf( '%.' . AttrVal($hash->{NAME}, 'roundTemperatureDecimal', 1) . 'f', $temperature ); + + readingsSingleUpdate($hash, "temperature", $temperature, 0); +} + +sub I2C_HDC1008_GetHum ($$) +{ + my ($hash, $rawdata) = @_; + my $name = $hash->{NAME}; + + my @raw = split(" ",$rawdata); + my $humWord = ($raw[0] << 8 | $raw[1]); + + my $humidity = ($humWord /65536.0)*100.0; + + Log3 $hash, 5, "[$name] I2C_HDC1008_I2CRec calced humidity: $humidity"; + + my $temperature = ReadingsVal($hash->{NAME} ,"temperature","0"); + $humidity = sprintf( '%.' . AttrVal($hash->{NAME}, 'roundHumidityDecimal', 1) . 'f', $humidity ); + readingsBeginUpdate($hash); + readingsBulkUpdate($hash, 'humidity', $humidity); + readingsBulkUpdate($hash, 'temperature', $temperature); + readingsBulkUpdate( + $hash, + 'state', + 'T: ' . $temperature . ' H: ' . $humidity + ); + + + readingsEndUpdate($hash, 1); +} + +sub I2C_HDC1008_Undef($$) +{ + my ($hash, $name) = @_; + + if ( defined (AttrVal($hash->{NAME}, "interval", undef)) ) + { + RemoveInternalTimer($hash); + } + + return undef; +} + + +sub I2C_HDC1008_Reset($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + if ($hash->{MODUL_STATE} ne 'Initialized') { return "Error MODULE_STATE in $name is not 'Initialized' " }; + + return "$name: no IO device defined" unless ($hash->{IODev}); + + my $Param = 1 << 15; # Bit 15 für Reset + + my $low_byte = $Param & 0xff; + my $high_byte = ($Param & 0xff00) >> 8; + + my $iodev = $hash->{IODev}; + my $i2caddress = $hash->{I2C_Address}; + + CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, { + direction => "i2cwrite", + i2caddress => $i2caddress, + reg => 2, + data => $high_byte. " ".$low_byte + }); + Time::HiRes::usleep(15000); # Sensor braucht bis 15 ms bis er bereit ist +} + +sub I2C_HDC1008_UpdateValues($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + + if ($hash->{MODUL_STATE} ne 'Initialized') { return "Error MODULE_STATE in $name is not 'Initialized' " }; + + return "$name: no IO device defined" unless ($hash->{IODev}); + + + # baue Konfigurationsparameter zusammen + + my $modeReading = 1 << 12; # lies beides gleichzeitig + + my $resTempIndex = $hash->{RESOLUTION_TEMPERATURE}; + my $resHumIndex = $hash->{RESOLUTION_HUMIDITY}; + my $heaterIndex = $hash->{HEATER}; + + my $resTempParam = $I2C_HDC1008_tempParams{$resTempIndex}{code}; + my $resHumParam = $I2C_HDC1008_humParams{$resHumIndex}{code}; + my $headerParam = $I2C_HDC1008_validsHeater{$heaterIndex}; + + my $Param = $modeReading | $resTempParam | $resHumParam | $headerParam; + + # Debug("$modeReading $resTempParam x $resHumParam x $headerParam" ); + + + + # schicke Konfiguration zum HDC1008-Sensor + # -------------------------------------------------------- + + my $low_byte = $Param & 0xff; + my $high_byte = ($Param & 0xff00) >> 8; + + my $iodev = $hash->{IODev}; + my $i2caddress = $hash->{I2C_Address}; + + CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, { + direction => "i2cwrite", + i2caddress => $i2caddress, + reg => 2, + data => $high_byte. " ".$low_byte # Leider fehlt es hier an Doku. Laut Quellcode (00_RPII2C.pm, ab Zeile 369), werden die dezimale Zahlen durch Leerzeichen getrennt, binär gewandelt und zum I2C-Bus geschickt + }); + Time::HiRes::usleep(15000); # Sensor braucht bis 15 ms bis er bereit ist + + + + # lese Temperatur vom HDC1008-Sensor + # -------------------------------------------------------- + + CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, { + direction => "i2cwrite", + i2caddress => $i2caddress, + data => (0) + }); + + my $tempWait = $I2C_HDC1008_tempParams{$resTempIndex}{delay}; + Time::HiRes::usleep($tempWait); + + + CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, { # Leider fehlt es hier an Doku. daher hier der Hinweis bei erfolgreichem Lesen wird die Funktion in $hash->{I2CRecFn} aufgerufen + direction => "i2cread", + i2caddress => $i2caddress, + type => "temp", + nbyte => 2 + }); + + + # lese Feuchtigkeit vom HDC1008-Sensor + # -------------------------------------------------------- + + CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, { + direction => "i2cwrite", + i2caddress => $i2caddress, + data => 1 + }); + my $humWait = $I2C_HDC1008_humParams{$resTempIndex}{delay}; + Time::HiRes::usleep($humWait); + + CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, { + direction => "i2cread", + i2caddress => $i2caddress, + type => "hum", + nbyte => 2 + }); + +} + +sub I2C_HDC1008_Get($@) { + my ($hash, @param) = @_; + + + + I2C_HDC1008_UpdateValues($hash); + + +} + +# set wenn Befehl gesetzt wurde und nicht '?' ist, +# dann führe Befehl aus und gib den Status zurück +# ansonsten gib alle Befehle und deren Optionen zurück + +sub I2C_HDC1008_Set($@) { + my ($hash, @param) = @_; + + + + return '"set HDC1008" needs at least one argument' if (int(@param) < 2); + + my $name = shift @param; + my $cmd = shift @param; + my $val = join("", @param); + + if (defined $cmd && $cmd ne '?') # falls set mit Kommand aufgerufen wurde + { + if ($cmd eq 'Heater') + { + if ( defined($I2C_HDC1008_validsHeater{$val}) ) + { + $hash->{HEATER} = $val; + return undef; + } + else + { + return "Invalid value for setting 'Heater'"; + } + + } + elsif ($cmd eq 'Update') + { + I2C_HDC1008_UpdateValues($hash); + return undef; + } + elsif ($cmd eq 'Reset') + { + I2C_HDC1008_Reset($hash); + return undef; + } + + + # Debug("Set HDC1008 $cmd"); + return -1; + } + else # Ansonsten Rückgabe was an set - Optionen möglich ist + { + return "Update:noArg Heater:off,on Reset:noArg "; + } + + +} + +sub I2C_HDC1008_CheckState +{ + my ($hash) = @_; + if ($hash->{MODUL_STATE} ne 'Initialized') + { + + my @def = split (' ',$hash->{DEF}); + I2C_HDC1008_Init($hash,\@def) if (defined ($hash->{IODev})); + } +} + +sub I2C_HDC1008_Poll +{ + my ($hash) = @_; + I2C_HDC1008_CheckState($hash); + my $name = $hash->{NAME}; + + + + I2C_HDC1008_UpdateValues($hash); + + my $ret = I2C_HDC1008_Catch($@) if $@; + + # Debug("Update Werte"); + my $pollInterval = AttrVal($hash->{NAME}, 'interval', 0); + if ($pollInterval > 0) + { + Log3 $hash, 5, "[$name] I2C_HDC1008_Poll call InternalTimer with $pollInterval minutes"; + InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_HDC1008_Poll', $hash, 0); + } + else + { + Log3 $name, 5, "[$name] I2C_HDC1008_Poll dont call InternalTimer, not valid pollInterval"; + } + return; +} + +sub I2C_HDC1008_Attr(@) { + + my ($command, $name, $attr, $val) = @_; + my $hash = $defs{$name}; + my $msg = ''; + + if ($attr eq 'interval') + { + if ( defined($val) ) + { + if ( looks_like_number($val) && $val > 0) + { + RemoveInternalTimer($hash); + InternalTimer(1, 'I2C_HDC1008_Poll', $hash, 0); + $hash->{INTERVAL} = $val; + Log3 $hash, 5, "[$hash->{NAME}] I2C_HDC1008_Attr call InternalTimer with new value $val "; + } else + { + $msg .= "$hash->{NAME}: Wrong poll intervall defined. interval must be a number > 0"; + Log3 $hash, 5, "[$hash->{NAME}] I2C_HDC1008_Attr Wrong poll intervall defined. interval must be a number > 0"; + $hash->{INTERVAL} = 0; + } + } + else + { #wird auch aufgerufen wenn $val leer ist, aber der attribut wert wird auf 1 gesetzt + RemoveInternalTimer($hash); + $hash->{INTERVAL} = 0; + } + } + + elsif ($attr eq 'Resolution_Temperature') + { + + if (!defined($val)) + { + $hash->{RESOLUTION_TEMPERATURE} = '14Bit'; + } + elsif ( defined($I2C_HDC1008_tempParams{$val}{code}) ) + { + $hash->{RESOLUTION_TEMPERATURE} = $val; + } + else + { + $msg .= "invalid value for attribute $attr"; + } + } + + elsif ($attr eq 'Resolution_Humidity') + { + if (!defined($val)) + { + $hash->{RESOLUTION_HUMIDITY} = '14Bit'; + } + elsif ( defined($I2C_HDC1008_humParams{$val}{code}) ) + { + $hash->{RESOLUTION_HUMIDITY} = $val; + } + else + { + $msg .= "invalid value for attribute $attr"; + } + } + + elsif ($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_HDC1008_Init($hash,\@def) if (defined ($hash->{IODev})); + } + } + + elsif ( ($attr eq 'roundTemperatureDecimal') || ($attr eq 'roundHumidityDecimal')) + { + if (!defined($val)) + { + return undef; + } + elsif (!(looks_like_number($val) && ($val>=0 ))) + { + $msg .= "$attr must be a number >= 0" + } + } + + return ($msg) ? $msg : undef; +} + +1; + +=pod +=begin html + + +

I2C_HDC1008

+
    + + Provides an interface to the I2C_HDC1008 I2C Humidity sensor from Texas Instruments. + 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_HDC1008 [<I2C Address>]
      + where <I2C Address> is an 2 digit hexadecimal value
      +
    + + Set +
      + set <name> Update
      + Reads the current temperature and humidity values from sensor.

      + set <name> Reset
      + Resets the sensor + set <name> Heater {on|off}
      + turns the sensor heater on or off +
    + + Attributes +
      +
    • interval
      + Set the polling interval in minutes to query data from sensor
      + Default: 5, valid values: 1,2,5,10,20,30

      +
    • +
    • Resolution_Temperature
      + resolution for measurement temperature.
      + Standard: 14Bit, valid values: 11Bit, 14Bit

      +
    • +
    • Resolution_Humidity
      + resolution for measurement humidity.
      + Standard: 14Bit, valid values: 8Bit, 11Bit, 14Bit

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

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

I2C_HDC1008

+
    + + Ermöglicht die Verwendung eines I2C_HDC1008 I2C Feuchtesensors von Texas Instruments. + 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_HDC1008 [<I2C Address>]
      + Der Wert <I2C Address> ist ein zweistelliger Hex-Wert
      +
    + + Set +
      + set <name> Update
      + Aktuelle Temperatur und Feuchte Werte vom Sensor lesen.

      + set <name> Reset
      + Setzt den Sensor zurück + set <name> Heater {on|off}
      + Schaltet das Heizelement des Sensors an oder aus +
    + + Attribute +
      +
    • interval
      + Aktualisierungsintervall aller Werte in Minuten.
      + Standard: 5, gültige Werte: 1,2,5,10,20,30

      +
    • +
    • Resolution_Temperature
      + Genauigkeit mit der die Temperatur gemessen werden soll.
      + Standard: 14Bit, gültige Werte: 11Bit, 14Bit

      +
    • +
    • Resolution_Humidity
      + Genauigkeit mit der die Feuchtigkeit gemessen werden soll.
      + Standard: 14Bit, gültige Werte: 8Bit, 11Bit, 14Bit

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

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