############################################## # I2C_K30.pm: heavily based on I2C_SHT21.pm # # $Id$ package main; use strict; use warnings; use constant { # For details, see SenseAir "I2C communication guide for K20/K21/K22/K30 platforms" K30_I2C_ADDRESS => 0x68, K30_REQ_READ_RAM => 0x20, K30_RESP_READ_COMPLETE => 0x21, K30_ADDR_CO2 => 0x08, K30_LEN_CO2 => 2 }; ################################################## # Forward declarations # sub I2C_K30_Initialize($); sub I2C_K30_Define($$); sub I2C_K30_Attr(@); sub I2C_K30_Poll($); sub I2C_K30_Set($@); sub I2C_K30_Undef($$); sub I2C_K30_DbLog_splitFn($); my %sets = ( 'readValues' => 1, ); sub I2C_K30_Initialize($) { my ($hash) = @_; $hash->{DefFn} = 'I2C_K30_Define'; $hash->{InitFn} = 'I2C_K30_Init'; $hash->{AttrFn} = 'I2C_K30_Attr'; $hash->{SetFn} = 'I2C_K30_Set'; $hash->{UndefFn} = 'I2C_K30_Undef'; $hash->{I2CRecFn} = 'I2C_K30_I2CRec'; $hash->{AttrList} = 'IODev do_not_notify:0,1 showtime:0,1 poll_interval:1,2,5,10,20,30 ' . $readingFnAttributes; $hash->{DbLog_splitFn} = "I2C_K30_DbLog_splitFn"; } sub I2C_K30_Define($$) { my ($hash, $def) = @_; my @a = split('[ \t][ \t]*', $def); $hash->{STATE} = "defined"; if ($main::init_done) { eval { I2C_K30_Init( $hash, [ @a[ 2 .. scalar(@a) - 1 ] ] ); }; return I2C_K30_Catch($@) if $@; } return undef; } sub I2C_K30_Init($$) { my ( $hash, $args ) = @_; my $name = $hash->{NAME}; if (defined $args && int(@$args) > 1) { Log3 $hash, 1, "Define: Wrong syntax. Can't initialize sensor."; return; } 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} = K30_I2C_ADDRESS; } my $msg = ''; # create default attributes if (AttrVal($name, 'poll_interval', '?') eq '?') { $msg = CommandAttr(undef, $name . ' poll_interval 5'); if ($msg) { Log3 ($hash, 1, $msg); return $msg; } } AssignIoPort($hash); $hash->{STATE} = 'Initialized'; return undef; } sub I2C_K30_Catch($) { my $exception = shift; if ($exception) { $exception =~ /^(.*)( at.*FHEM.*)$/; return $1; } return undef; } sub I2C_K30_Attr (@) {# hier noch Werteueberpruefung einfuegen my ($command, $name, $attr, $val) = @_; my $hash = $defs{$name}; my $msg = ''; if ($command && $command eq "set" && $attr && $attr eq "IODev") { eval { if ($main::init_done and (!defined ($hash->{IODev}) or $hash->{IODev}->{NAME} ne $val)) { main::AssignIoPort($hash,$val); my @def = split (' ',$hash->{DEF}); I2C_K30_Init($hash,\@def) if (defined ($hash->{IODev})); } }; return I2C_K30_Catch($@) if $@; } if ($attr eq 'poll_interval') { if ($val > 0) { RemoveInternalTimer($hash); InternalTimer(gettimeofday() + 5, 'I2C_K30_Poll', $hash, 0); } else { $msg = 'Wrong poll intervall defined. poll_interval must be a number > 0'; } } return ($msg) ? $msg : undef; } sub I2C_K30_Poll($) { my ($hash) = @_; my $name = $hash->{NAME}; # Read values I2C_K30_Set($hash, ($name, 'readValues')); my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0); if ($pollInterval > 0) { InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_K30_Poll', $hash, 0); } } sub I2C_K30_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_K30_readCO2($hash); } } sub I2C_K30_Undef($$) { my ($hash, $arg) = @_; RemoveInternalTimer($hash); return undef; } sub I2C_K30_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 $hash->{$k} = $v if $k =~ /^$pname/ ; } # Read Complete Response if ( $clientmsg->{direction} && $clientmsg->{$pname . "_SENDSTAT"} && $clientmsg->{$pname . "_SENDSTAT"} eq "Ok" ) { if ( $clientmsg->{direction} eq "i2cread" && defined($clientmsg->{received}) ) { Log3 $hash, 4, "empfangen: $clientmsg->{received}"; my @raw = split(" ",$clientmsg->{received}); I2C_K30_ParseCO2 ($hash, $clientmsg->{received}) if ($raw[0] == K30_RESP_READ_COMPLETE) && $clientmsg->{nbyte} == 4; } } } sub I2C_K30_ParseCO2 ($$) { my ($hash, $rawdata) = @_; my @raw = split(" ",$rawdata); if ( defined (my $crc = I2C_K30_CheckCrc(@raw)) ) { #CRC Test Log3 $hash, 3, "CRC error CO2 data: $rawdata, Checksum calculated: $crc"; $hash->{CRCError}++; return; } my $co2 = $raw[1] << 8 | $raw[2]; $co2 = sprintf('%i', $co2); readingsBeginUpdate($hash); readingsBulkUpdate( $hash, 'state', 'CO2: ' . $co2 ); readingsBulkUpdate($hash, 'CO2', $co2); readingsEndUpdate($hash, 1); } sub I2C_K30_readCO2($) { my ($hash) = @_; my $name = $hash->{NAME}; return "$name: no IO device defined" unless ($hash->{IODev}); my $phash = $hash->{IODev}; my $pname = $phash->{NAME}; my $i2creq = { i2caddress => $hash->{I2C_Address}, direction => "i2cwrite" }; # read CO2 from sensor RAM, last byte: "checksum" of all preceding bytes $i2creq->{data} = join(" ", (K30_REQ_READ_RAM | K30_LEN_CO2, 0, K30_ADDR_CO2, (K30_REQ_READ_RAM + K30_LEN_CO2 + 0 + K30_ADDR_CO2) ) ); CallFn($pname, "I2CWrtFn", $phash, $i2creq); RemoveInternalTimer($hash); InternalTimer(gettimeofday() + 1, 'I2C_K30_readValue', $hash, 0); #nach 1s Wert lesen (min. 20ms lt. Datenblatt) return; } sub I2C_K30_readValue($) { my ($hash) = @_; my $name = $hash->{NAME}; return "$name: no IO device defined" unless ($hash->{IODev}); my $phash = $hash->{IODev}; my $pname = $phash->{NAME}; # Reset Internal Timer to Poll Sub RemoveInternalTimer($hash); my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0); InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_K30_Poll', $hash, 0) if ($pollInterval > 0); # Read the three byte result from device + 1byte CRC my $i2cread = { i2caddress => $hash->{I2C_Address}, direction => "i2cread" }; $i2cread->{nbyte} = 4; CallFn($pname, "I2CWrtFn", $phash, $i2cread); return; } sub I2C_K30_CheckCrc(@) { my @data = @_; my $crc = 0; for (my $n = 0; $n < (scalar(@data) - 1); ++$n) { $crc += $data[$n]; } return ($crc = $data[3] ? undef : $crc); } sub I2C_K30_DbLog_splitFn($) { my ($event) = @_; Log3 undef, 3, "in DbLog_splitFn empfangen: $event"; my ($reading, $value, $unit) = ""; my @parts = split(/ /,$event); $reading = shift @parts; $reading =~ tr/://d; $value = $parts[0]; $unit = "ppm" if(lc($reading) =~ m/CO2/); return ($reading, $value, $unit); } 1; =pod =item device =item summary read SenseAir K30 CO2 sensor via I2C bus =item summary_DE SenseAir K30 CO2 Sensor über I2C auslesen =begin html

I2C_K30

(en | de) =end html =begin html_DE

I2C_K30

(en | de) =end html_DE =cut