2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-03-03 16:56:54 +00:00

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
This commit is contained in:
schlawiano 2016-05-27 14:03:44 +00:00
parent 7497e4759d
commit d7df0ee99c
2 changed files with 1294 additions and 0 deletions

View File

@ -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 <name> I2C_GY30_BH1750FVI [<i2caddress>]";
}
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
<a name="I2C_GY30_BH1750FVI"></a>
<h3>I2C_GY30_BH1750FVI</h3>
<ul>
<a name="I2C_GY30_BH1750FVI"></a>
Provides an interface to the I2C GY-30 with chip BH1750FVI light intensity sensor from <a href=" http://www.ti.com">Texas Instruments</a>.
The I2C messages are send through an I2C interface module like <a href="#RPII2C">RPII2C</a>, <a href="#FRM">FRM</a>
or <a href="#NetzerI2C">NetzerI2C</a> so this device must be defined first.<br>
<b>attribute IODev must be set</b><br>
<a name="I2C_GY30_BH1750FVIDefine"></a><br>
<b>Define</b>
<ul>
<code>define &lt;name&gt; I2C_GY30_BH1750FVI [&lt;I2C Address&gt;]</code><br>
where <code>&lt;I2C Address&gt;</code> is an 2 digit hexadecimal value<br>
</ul>
<a name="I2C_GY30_BH1750FVISet"></a>
<b>Set</b>
<ul>
<code>set &lt;name&gt; Update</code><br>
Reads the current light intensity values from sensor.<br><br>
<code>set &lt;name&gt; Reset</code><br>
Resets the sensor (only when sensor is power on).<br><br>
<code>set &lt;name&gt; ReConfig</code><br>
Sends in Continuously mode the configuration parameter again to the sensor.<br><br>
<code>set &lt;name&gt; PowerDown</code><br>
turn the sensor in standby (no active state).<br><br>
<code>set &lt;name&gt; PowerOn</code><br>
turn on the sensor
</ul>
<a name="I2C_GY30_BH1750FVIAttr"></a>
<b>Attributes</b>
<ul>
<li>interval<br>
Set the polling interval in minutes to query data from sensor<br>
Default: 5, valid values: 1,2,5,10,20,30<br><br>
</li>
<li>Resolution<br>
resolution for measurement<br>
Standard: HalfLux, valid values: HalfLux, 1Lux, 4Lux<br><br>
</li>
<li>OperationMode<br>
operation mode. One: One-time measurement , then the sensor turns off. Continuously: re- measure possible without re-configuration then sensor<br>
standard: One, valid values: Continuously,One<br><br>
</li>
<li>roundLightIntensityDecimal<br>
Number of decimal places for light intensity value<br>
Default: 1, valid values: 0 1 2,...<br><br>
</li>
<li><a href="#IODev">IODev</a></li>
</ul><br>
</ul>
=end html
=begin html_DE
<a name="I2C_GY30_BH1750FVI"></a>
<h3>I2C_GY30_BH1750FVI</h3>
<ul>
<a name="I2C_GY30_BH1750FVI"></a>
Erm&ouml;glicht die Verwendung eines I2C GY-30 mit Chip BH1750FVI Lichtst&auml;rke-Sensors von <a href=" http://www.ti.com">Texas Instruments</a>.
I2C-Botschaften werden &uuml;ber ein I2C Interface Modul wie beispielsweise das <a href="#RPII2C">RPII2C</a>, <a href="#FRM">FRM</a>
oder <a href="#NetzerI2C">NetzerI2C</a> gesendet. Daher muss dieses vorher definiert werden.<br>
<b>Das Attribut IODev muss definiert sein.</b><br>
<a name="I2C_GY30_BH1750FVIDefine"></a><br>
<b>Define</b>
<ul>
<code>define &lt;name&gt; I2C_GY30_BH1750FVI [&lt;I2C Address&gt;]</code><br>
Der Wert <code>&lt;I2C Address&gt;</code> ist ein zweistelliger Hex-Wert<br>
</ul>
<a name="I2C_GY30_BH1750FVISet"></a>
<b>Set</b>
<ul>
<code>set &lt;name&gt; Update</code><br>
Aktuelle Lichtst&auml;rke vom Sensor lesen.<br><br>
<code>set &lt;name&gt; Reset ( nur bei eingeschalteten Sensor )</code><br>
Setzt den Sensor zur&uuml;ck<br><br>
<code>set &lt;name&gt; ReConfig</code><br>
Sendet im Continuously-Mode die Konfigurationsparameter erneut zum Sensor<br><br>
<code>set &lt;name&gt; PowerDown</code><br>
Schaltet den Sensors in einen Ruhezustand<br><br>
<code>set &lt;name&gt; PowerOn</code><br>
Schaltet den Sensors an
</ul>
<a name="I2C_GY30_BH1750FVIAttr"></a>
<b>Attribute</b>
<ul>
<li>interval<br>
Aktualisierungsintervall aller Werte in Minuten.<br>
Standard: 5, g&uuml;ltige Werte: 1,2,5,10,20,30<br><br>
</li>
<li>Resolution<br>
Genauigkeit mit der gemessen werden soll.<br>
Standard: HalfLux, g&uuml;ltige Werte: HalfLux, 1Lux, 4Lux<br><br>
</li>
<li>OperationMode<br>
Betriebsmodus. One: Einmaliges Messen, dann schaltet der Sensor sich wieder aus. Continuously: erneutes Messen ohne Neukonfiguration des Sensors m&ouml;glich<br>
Standard: One, g&uuml;ltige Werte: Continuously,One<br><br>
</li>
<li>roundLightIntensityDecimal<br>
Anzahl Dezimalstellen f&uuml;r die Lichtst&auml;rke<br>
Standard: 1, g&uuml;ltige Werte: 0,1,2<br><br>
</li>
<li><a href="#IODev">IODev</a></li>
</ul><br>
</ul>
=end html
=cut

654
fhem/FHEM/52_I2C_HDC1008.pm Normal file
View File

@ -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 <name> I2C_HDC1008 [<i2caddress>]";
}
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
<a name="I2C_HDC1008"></a>
<h3>I2C_HDC1008</h3>
<ul>
<a name="I2C_HDC1008"></a>
Provides an interface to the I2C_HDC1008 I2C Humidity sensor from <a href=" http://www.ti.com">Texas Instruments</a>.
The I2C messages are send through an I2C interface module like <a href="#RPII2C">RPII2C</a>, <a href="#FRM">FRM</a>
or <a href="#NetzerI2C">NetzerI2C</a> so this device must be defined first.<br>
<b>attribute IODev must be set</b><br>
<a name="I2C_HDC1008Define"></a><br>
<b>Define</b>
<ul>
<code>define &lt;name&gt; I2C_HDC1008 [&lt;I2C Address&gt;]</code><br>
where <code>&lt;I2C Address&gt;</code> is an 2 digit hexadecimal value<br>
</ul>
<a name="I2C_HDC1008Set"></a>
<b>Set</b>
<ul>
<code>set &lt;name&gt; Update</code><br>
Reads the current temperature and humidity values from sensor.<br><br>
<code>set &lt;name&gt; Reset</code><br>
Resets the sensor
<code>set &lt;name&gt; Heater {on|off}</code><br>
turns the sensor heater on or off
</ul>
<a name="I2C_HDC1008Attr"></a>
<b>Attributes</b>
<ul>
<li>interval<br>
Set the polling interval in minutes to query data from sensor<br>
Default: 5, valid values: 1,2,5,10,20,30<br><br>
</li>
<li>Resolution_Temperature<br>
resolution for measurement temperature.<br>
Standard: 14Bit, valid values: 11Bit, 14Bit<br><br>
</li>
<li>Resolution_Humidity<br>
resolution for measurement humidity.<br>
Standard: 14Bit, valid values: 8Bit, 11Bit, 14Bit<br><br>
</li>
<li>roundHumidityDecimal<br>
Number of decimal places for humidity value<br>
Default: 1, valid values: 0 1 2,...<br><br>
</li>
<li>roundTemperatureDecimal<br>
Number of decimal places for temperature value<br>
Default: 1, valid values: 0,1,2,...<br><br>
</li>
<li><a href="#IODev">IODev</a></li>
</ul><br>
</ul>
=end html
=begin html_DE
<a name="I2C_HDC1008"></a>
<h3>I2C_HDC1008</h3>
<ul>
<a name="I2C_HDC1008"></a>
Erm&ouml;glicht die Verwendung eines I2C_HDC1008 I2C Feuchtesensors von <a href=" http://www.ti.com">Texas Instruments</a>.
I2C-Botschaften werden &uuml;ber ein I2C Interface Modul wie beispielsweise das <a href="#RPII2C">RPII2C</a>, <a href="#FRM">FRM</a>
oder <a href="#NetzerI2C">NetzerI2C</a> gesendet. Daher muss dieses vorher definiert werden.<br>
<b>Das Attribut IODev muss definiert sein.</b><br>
<a name="I2C_HDC1008Define"></a><br>
<b>Define</b>
<ul>
<code>define &lt;name&gt; I2C_HDC1008 [&lt;I2C Address&gt;]</code><br>
Der Wert <code>&lt;I2C Address&gt;</code> ist ein zweistelliger Hex-Wert<br>
</ul>
<a name="I2C_HDC1008Set"></a>
<b>Set</b>
<ul>
<code>set &lt;name&gt; Update</code><br>
Aktuelle Temperatur und Feuchte Werte vom Sensor lesen.<br><br>
<code>set &lt;name&gt; Reset</code><br>
Setzt den Sensor zur&uuml;ck
<code>set &lt;name&gt; Heater {on|off}</code><br>
Schaltet das Heizelement des Sensors an oder aus
</ul>
<a name="I2C_HDC1008Attr"></a>
<b>Attribute</b>
<ul>
<li>interval<br>
Aktualisierungsintervall aller Werte in Minuten.<br>
Standard: 5, g&uuml;ltige Werte: 1,2,5,10,20,30<br><br>
</li>
<li>Resolution_Temperature<br>
Genauigkeit mit der die Temperatur gemessen werden soll.<br>
Standard: 14Bit, g&uuml;ltige Werte: 11Bit, 14Bit<br><br>
</li>
<li>Resolution_Humidity<br>
Genauigkeit mit der die Feuchtigkeit gemessen werden soll.<br>
Standard: 14Bit, g&uuml;ltige Werte: 8Bit, 11Bit, 14Bit<br><br>
</li>
<li>roundHumidityDecimal<br>
Anzahl Dezimalstellen f&uuml;r den Feuchtewert<br>
Standard: 1, g&uuml;ltige Werte: 0 1 2<br><br>
</li>
<li>roundTemperatureDecimal<br>
Anzahl Dezimalstellen f&uuml;r den Temperaturwert<br>
Standard: 1, g&uuml;ltige Werte: 0,1,2<br><br>
</li>
<li><a href="#IODev">IODev</a></li>
</ul><br>
</ul>
=end html
=cut