From 9715e0bc80ea97b2f9e4d702b48ed5eb21384f98 Mon Sep 17 00:00:00 2001 From: rudolfkoenig <> Date: Mon, 5 Jul 2010 06:43:52 +0000 Subject: [PATCH] Example for non-blocking device read via fork git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@644 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- contrib/21_OWTEMP.pm.fork | 531 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 531 insertions(+) create mode 100644 contrib/21_OWTEMP.pm.fork diff --git a/contrib/21_OWTEMP.pm.fork b/contrib/21_OWTEMP.pm.fork new file mode 100644 index 000000000..08818e2a4 --- /dev/null +++ b/contrib/21_OWTEMP.pm.fork @@ -0,0 +1,531 @@ +################################################################ +# +# Copyright notice +# +# (c) 2009 Copyright: Martin Fischer (m_fischer at gmx dot de) +# All rights reserved +# +# This script free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# A copy is found in the textfile GPL.txt and important notices to the license +# from the author is found in LICENSE.txt distributed with these scripts. +# +# This script is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +################################################################ +package main; + +use strict; +use warnings; +use Time::HiRes qw(gettimeofday); +use OW; + +my %gets = ( + "address" => "", + "alias" => "", + "crc8" => "", + "family" => "10", + "id" => "", + "locator" => "", + "power" => "", + "present" => "", +# "r_address" => "", +# "r_id" => "", +# "r_locator" => "", + "temperature" => "", + "temphigh" => "", + "templow" => "", + "type" => "", +); + +my %sets = ( + "alias" => "", + "temphigh" => "", + "templow" => "", + "interval" => "", + "alarminterval" => "", +); + +my %updates = ( + "present" => "", + "temperature" => "", + "templow" => "", + "temphigh" => "", +); + +my %dummy = ( + "crc8" => "4D", + "alias" => "dummy", + "locator" => "FFFFFFFFFFFFFFFF", + "power" => "0", + "present" => "1", + "temphigh" => "75", + "templow" => "10", + "type" => "DS18S20", + "warnings" => "none", +); + +##################################### +sub +OWTEMP_Initialize($) +{ + my ($hash) = @_; + + $hash->{DefFn} = "OWTEMP_Define"; + $hash->{UndefFn} = "OWTEMP_Undef"; + $hash->{GetFn} = "OWTEMP_Get"; + $hash->{SetFn} = "OWTEMP_Set"; + $hash->{AttrList}= "IODev do_not_notify:0,1 showtime:0,1 model:DS18S20 loglevel:0,1,2,3,4,5"; +} + +##################################### +sub +OWTEMP_UpdateReading($$$$) +{ + my ($hash,$reading,$now,$value) = @_; + + # define vars + my $temp; + + # exit if empty value + return 0 + if(!defined($value) || $value eq ""); + + # trim value + $value =~ s/\s//g + if($reading ne "warnings"); + if($reading eq "temperature") { + $value = sprintf("%.4f",$value); + $temp = $value; + $value = $value . " (".$hash->{OW_SCALE}.")"; + } + + # update readings + $hash->{READINGS}{$reading}{TIME} = $now; + $hash->{READINGS}{$reading}{VAL} = $value; + Log 4, "OWTEMP $hash->{NAME} $reading: $value"; + + return $value; +} + +##################################### +sub +OWTEMP_GetUpdate($$) +{ + my ($hash, $a) = @_; + + # define vars + my $name = $hash->{NAME}; + my $now = TimeNow(); + my $value = ""; + my $temp = ""; + my $ret = ""; + my $count = 0; + + # define warnings + my $warn = "none"; + $hash->{ALARM} = "0"; + + # check for real sensor + if($hash->{OW_ID} ne "none") { + # real sensor + + if(!$hash->{LOCAL} || $a eq "") { + + ##################### + # OW::Get is too slow: do it in the background by fork. After receiving + # the data from the OW module, the child contacts the parent, and calls + # "set childupdate ", which in turn will call this function + # again with a filled CHILDDATA + + if(!$hash->{CHILDDATA}) { + if($hash->{CHILDPID}) { + Log 2, "OWTEMP: Child already forked: timeout too short?"; + return; + } + + return if(($hash->{CHILDPID} = fork)); + + my @ret; + foreach my $r (sort keys %updates) { + my $ret = OW::get("/uncached/".$hash->{OW_PATH}."/".$r); + $ret = "" if(!defined($ret)); + push(@ret, $ret); + last if($ret eq ""); + } + my @port = split(" ", $attr{global}{port}); + my $server = IO::Socket::INET->new(PeerAddr => "localhost:$port[0]"); + Log 0, "OWTEMP: Can't connect to parent\n" if(!$server); + syswrite($server, "set $hash->{NAME} childupdate ".join(":",@ret)."\n"); + exit(0); + + } else { + + ##################### + # Digest the data sent by the CHILD. + my @ret = split(":", $hash->{CHILDDATA}); + delete($hash->{CHILDPID}); + delete($hash->{CHILDDATA}); + foreach my $r (sort keys %updates) { + $ret = shift(@ret); + if($ret eq "") { + # + $hash->{PRESENT} = "0"; + $r = "present"; + $value = "0"; + $ret = OWTEMP_UpdateReading($hash,$r,$now,$value); + $hash->{CHANGED}[$count] = "present: ".$value + } else { + $hash->{PRESENT} = "1"; + $value = $ret; + if($r eq "temperature") { + $temp = sprintf("%.4f",$value); + $temp =~ s/\s//g; + } + $ret = OWTEMP_UpdateReading($hash,$r,$now,$value); + } + last if($hash->{PRESENT} eq "0"); + } + } + } else { + $ret = ""; + $ret = OW::get("/uncached/".$hash->{OW_PATH}."/".$a); + if(!defined($ret)) { + $hash->{PRESENT} = "0"; + $a = "present"; + $value = "0"; + $ret = OWTEMP_UpdateReading($hash,$a,$now,$value); + } else { + $hash->{PRESENT} = "1"; + $value = $ret; + if($a eq "temperature") { + $temp = sprintf("%.4f",$value); + $temp =~ s/\s//g; + $value = $temp; + } + $ret = OWTEMP_UpdateReading($hash,$a,$now,$value); + } + } + } else { + # dummy sensor + $temp = sprintf("%.4f",rand(85)); + $dummy{temperature} = $temp; + $dummy{present} = "1"; + $hash->{PRESENT} = $dummy{present}; + + if(!$hash->{LOCAL} || $a eq "") { + foreach my $r (sort keys %updates) { + $ret = OWTEMP_UpdateReading($hash,$r,$now,$dummy{$r}); + } + } else { + $ret = ""; + $ret = $dummy{$a}; + if($ret ne "") { + $value = $ret; + if($a eq "temperature") { + $temp = sprintf("%.4f",$value); + $temp =~ s/\s//g; + } + $ret = OWTEMP_UpdateReading($hash,$a,$now,$value); + } + } + } + + return 1 + if($hash->{LOCAL} && $a eq "" && $hash->{PRESENT} eq "0"); + + # check for warnings + my $templow = $hash->{READINGS}{templow}{VAL}; + my $temphigh = $hash->{READINGS}{temphigh}{VAL}; + + if($hash->{PRESENT} eq "1") { + if($temp <= $templow) { + # low temperature + $hash->{ALARM} = "1"; + $warn = "templow"; + } elsif($temp >= $temphigh) { + # high temperature + $hash->{ALARM} = "1"; + $warn = "temphigh"; + } + } else { + # set old state + $temp = $hash->{READINGS}{temperature}{VAL}; + ($temp,undef) = split(" ",$temp); + # sensor is missing + $hash->{ALARM} = "1"; + $warn = "not present"; + } + + if(!$hash->{LOCAL} || $a eq "") { + $ret = OWTEMP_UpdateReading($hash,"warnings",$now,$warn); + } + + $hash->{STATE} = "T: ".$temp." ". + "L: ".$templow." ". + "H: ".$temphigh." ". + "P: ".$hash->{PRESENT}." ". + "A: ".$hash->{ALARM}." ". + "W: ".$warn; + + # inform changes + # state + $hash->{CHANGED}[$count++] = $hash->{STATE}; + # present + $hash->{CHANGED}[$count++] = "present: ".$hash->{PRESENT} + if(defined($hash->{PRESENT}) && $hash->{PRESENT} ne ""); + # temperature + $hash->{CHANGED}[$count++] = "temperature: ".$temp." (".$hash->{OW_SCALE}.")" + if(defined($temp) && $temp ne ""); + # temperature raw + $hash->{CHANGED}[$count++] = "tempraw: ".$temp + if(defined($temp) && $temp ne ""); + # low temperature + $hash->{CHANGED}[$count++] = "templow: ".$templow + if(defined($templow) && $templow ne ""); + # high temperature + $hash->{CHANGED}[$count++] = "temphigh: ".$temphigh + if(defined($temphigh) && $temphigh ne ""); + # warnings + $hash->{CHANGED}[$count++] = "warnings: ".$warn + if(defined($warn) && $warn ne ""); + + + if(!$hash->{LOCAL}) { + # update timer + RemoveInternalTimer($hash); + # check alarm + if($hash->{ALARM} eq "0") { + $hash->{INTERVAL} = $hash->{INTV_CHECK}; + } else { + $hash->{INTERVAL} = $hash->{INTV_ALARM}; + } + InternalTimer(gettimeofday()+$hash->{INTERVAL}, "OWTEMP_GetUpdate", $hash, 1); + } else { + return $value; + } + + if(!$hash->{LOCAL}) { + DoTrigger($name, undef) if($init_done); + } + + return $hash->{STATE}; +} + +##################################### +sub +OWTEMP_Get($@) +{ + my ($hash, @a) = @_; + + # check syntax + return "argument is missing @a" + if(int(@a) != 2); + # check argument + return "Unknown argument $a[1], choose one of ".join(",", sort keys %gets) + if(!defined($gets{$a[1]})); + + # define vars + my $value; + + # get value + $hash->{LOCAL} = 1; + $value = OWTEMP_GetUpdate($hash,$a[1]); + delete $hash->{LOCAL}; + + my $reading = $a[1]; + + if(defined($hash->{READINGS}{$reading})) { + $value = $hash->{READINGS}{$reading}{VAL}; + } + + return "$a[0] $reading => $value"; +} + +##################################### +sub +OWTEMP_Set($@) +{ + my ($hash, @a) = @_; + + # check syntax + return "set needs one parameter" + if(int(@a) != 3); + # check arguments + return "Unknown argument $a[1], choose one of ".join(",", sort keys %sets) + if(!defined($sets{$a[1]}) && $a[1] ne "childupdate"); + + # define vars + my $key = $a[1]; + my $value = $a[2]; + my $ret; + + if($key eq "childupdate") { + $hash->{CHILDDATA} = $value; + OWTEMP_GetUpdate($hash,undef); + return undef; + } + + # set new timer + if($key eq "interval" || $key eq "alarminterval") { + $key = "INTV_CHECK" + if($key eq "interval"); + $key = "INTV_ALARM" + if($key eq "alarminterval"); + # update timer + $hash->{$key} = $value; + RemoveInternalTimer($hash); + # check alarm + if($hash->{ALARM} eq "0") { + $hash->{INTERVAL} = $hash->{INTV_CHECK}; + } else { + $hash->{INTERVAL} = $hash->{INTV_ALARM}; + } + InternalTimer(gettimeofday()+$hash->{INTERVAL}, "OWTEMP_GetUpdate", $hash, 1); + } + + # set warnings + if($key eq "templow" || $key eq "temphigh") { + # check range + return "wrong value: range -55°C - 125°C" + if(int($value) < -55 || int($value) > 125); + } + + # set value + Log 4, "OWTEMP set $hash->{NAME} $key $value"; + + # check for real sensor + if($hash->{OW_ID} ne "none") { + # real senson + $ret = OW::put($hash->{OW_PATH}."/$key",$value); + } else { + # dummy sensor + $dummy{$key} = $value; + } + + # update readings + if($key ne "interval" || $key ne "alarminterval") { + $hash->{LOCAL} = 1; + $ret = OWTEMP_GetUpdate($hash,$key); + delete $hash->{LOCAL}; + } + + return undef; +} + +##################################### +sub +OWTEMP_Define($$) +{ + my ($hash, $def) = @_; + + # define OWTEMP [interval] [alarminterval] + # e.g.: define flow OWTEMP 332670010800 300 + + my @a = split("[ \t][ \t]*", $def); + + # check syntax + return "wrong syntax: define OWTEMP [interval] [alarminterval]" + if(int(@a) < 2 && int(@a) > 5); + # check ID format + return "Define $a[0]: missing ID or wrong ID format: specify a 12 digit value or set it to none for demo mode" + if(lc($a[2]) ne "none" && lc($a[2]) !~ m/^[0-9|a-f]{12}$/); + + # define vars + my $name = $a[0]; + my $id = $a[2]; + my $interval = 300; + my $alarminterval = 300; + my $scale = ""; + my $ret = ""; + + # overwrite default intervals if set by define + if(int(@a)==4) { $interval = $a[3]; } + if(int(@a)==5) { $interval = $a[3]; $alarminterval = $a[4] } + + # define device internals + $hash->{ALARM} = 0; + $hash->{INTERVAL} = $interval; + $hash->{INTV_CHECK} = $interval; + $hash->{INTV_ALARM} = $alarminterval; + $hash->{OW_ID} = $id; + $hash->{OW_FAMILY} = $gets{family}; + $hash->{OW_PATH} = $hash->{OW_FAMILY}.".".$hash->{OW_ID}; + $hash->{PRESENT} = 0; + + $modules{OWTEMP}{defptr}{$a[2]} = $hash; + + # assign IO port + AssignIoPort($hash); + return "No I/O device found. Please define a OWFS device first." + if(!defined($hash->{IODev}->{NAME})); + + # get scale from I/O device + $scale = $attr{$hash->{IODev}->{NAME}}{"temp-scale"}; + # define scale for temperature values + $scale = "Celsius" if ($scale eq "C"); + $scale = "Fahrenheit" if ($scale eq "F"); + $scale = "Kelvin" if ($scale eq "K"); + $scale = "Rankine" if ($scale eq "R"); + $hash->{OW_SCALE} = $scale; + + $hash->{STATE} = "Defined"; + + # define dummy values for testing + if($hash->{OW_ID} eq "none") { + my $now = TimeNow(); + $dummy{address} = $hash->{OW_FAMILY}.$hash->{OW_ID}.$dummy{crc8}; + $dummy{family} = $hash->{OW_FAMILY}; + $dummy{id} = $hash->{OW_ID}; + $dummy{temperature} = "80.0000 (".$hash->{OW_SCALE}.")"; + foreach my $r (sort keys %gets) { + $hash->{READINGS}{$r}{TIME} = $now; + $hash->{READINGS}{$r}{VAL} = $dummy{$r}; + Log 4, "OWTEMP $hash->{NAME} $r: ".$dummy{$r}; + } + } + + $hash->{STATE} = "Initialized"; + + # initalize + $hash->{LOCAL} = 1; + $ret = OWTEMP_GetUpdate($hash,""); + delete $hash->{LOCAL}; + + # exit if sensor is not present + return "Define $hash->{NAME}: Sensor is not reachable. Check first your 1-wire connection." + if(defined($ret) && $ret eq 1); + + if(!$hash->{LOCAL}) { + if($hash->{ALARM} eq "0") { + $hash->{INTERVAL} = $hash->{INTV_CHECK}; + } else { + $hash->{INTERVAL} = $hash->{INTV_ALARM}; + } + InternalTimer(gettimeofday()+$hash->{INTERVAL}, "OWTEMP_GetUpdate", $hash, 0); + } + + return undef; +} + +##################################### +sub +OWTEMP_Undef($$) +{ + my ($hash, $name) = @_; + + delete($modules{OWTEMP}{defptr}{$hash->{NAME}}); + RemoveInternalTimer($hash); + + return undef; +} + +1;