# $Id$ ############################################## # # 98_ping.pm # FHEM module to check remote network device using ping. # # Author: Matthew Wire (mattwire) # # This file is part of fhem. # # Fhem is 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. # # Fhem 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. # # You should have received a copy of the GNU General Public License # along with fhem. If not, see . # ############################################################################## package main; use strict; use warnings; use Blocking; use Net::Ping; sub ping_Initialize($) { my ($hash) = @_; $hash->{DefFn} = "ping_Define"; $hash->{UndefFn} = "ping_Undefine"; $hash->{AttrFn} = "ping_Attr"; $hash->{AttrList} = "disable:1 checkInterval minFailCount ".$readingFnAttributes; return undef; } ##################################### # Define ping device sub ping_Define($$) { my ($hash, $def) = @_; my @args = split("[ \t][ \t]*", $def); return "Usage: define ping " if(@args < 5); my ($name, $type, $host, $mode, $timeout) = @args; # Parameters $hash->{HOST} = $host; $hash->{MODE} = lc($mode); $hash->{TIMEOUT} = $timeout; $hash->{FAILCOUNT} = 0; delete $hash->{helper}{RUNNING_PID}; readingsSingleUpdate($hash, "state", "Initialized", 1); return "ERROR: mode must be one of tcp,udp,icmp" if ($hash->{MODE} !~ "tcp|udp|icmp"); return "ERROR: timeout must be 0 or higher." if (($hash->{TIMEOUT} !~ /^\d*$/) || ($hash->{TIMEOUT} < 0)); $attr{$name}{"checkInterval"} = 10 if (!defined($attr{$name}{"checkInterval"})); $attr{$name}{"event-on-change-reading"} = "state" if (!defined($attr{$name}{"event-on-change-reading"})); ping_SetNextTimer($hash); return undef; } ##################################### # Undefine ping device sub ping_Undefine($$) { my ($hash,$arg) = @_; RemoveInternalTimer($hash); BlockingKill($hash->{helper}{RUNNING_PID}) if(defined($hash->{helper}{RUNNING_PID})); return undef; } ##################################### # Manage attribute changes sub ping_Attr($$$$) { my ($command,$name,$attribute,$value) = @_; my $hash = $defs{$name}; Log3 ($hash, 5, "$hash->{NAME}_Attr: Attr $attribute; Value $value"); if ($command eq "set") { if ($attribute eq "checkInterval") { if (($value !~ /^\d*$/) || ($value < 5)) { $attr{$name}{"checkInterval"} = 10; return "checkInterval is required in s (default: 10, min: 5)"; } } # Handle "disable" attribute by opening/closing connection to device elsif ($attribute eq "disable") { # Disable on 1, enable on anything else. if ($value eq "1") { readingsSingleUpdate($hash, "state", "disabled", 1); } else { readingsSingleUpdate($hash, "state", "Initialized", 1); } } } return undef; } ##################################### # Set next timer for ping check sub ping_SetNextTimer($) { my ($hash) = @_; # Check state every X seconds RemoveInternalTimer($hash); InternalTimer(gettimeofday() + AttrVal($hash->{NAME}, "checkInterval", "10"), "ping_Start", $hash, 0); } ##################################### # Prepare and start the blocking call in new thread sub ping_Start($) { my ($hash) = @_; return undef if (IsDisabled($hash->{NAME})); my $timeout = $hash->{TIMEOUT}; my $arg = $hash->{NAME}."|".$hash->{HOST}."|".$hash->{MODE}."|".$hash->{TIMEOUT}; my $blockingFn = "ping_DoPing"; my $finishFn = "ping_DoPingDone"; my $abortFn = "ping_DoPingAbort"; if (!(exists($hash->{helper}{RUNNING_PID}))) { $hash->{helper}{RUNNING_PID} = BlockingCall($blockingFn, $arg, $finishFn, $timeout, $abortFn, $hash); } else { Log3 $hash, 3, "$hash->{NAME} Blocking Call running no new started"; ping_SetNextTimer($hash); } } ##################################### # BlockingCall DoPing in separate thread sub ping_DoPing(@) { my ($string) = @_; my ($name, $host, $mode, $timeout) = split("\\|", $string); Log3 ($name, 5, $name."_DoPing: Executing ping"); # check via ping my $p; $p = Net::Ping->new($mode); my $result = $p->ping($host, $timeout); $p->close(); $result="" if !(defined($result)); return "$name|$result"; } ##################################### # Ping thread completed sub ping_DoPingDone($) { my ($string) = @_; my ($name, $result) = split("\\|", $string); my $hash = $defs{$name}; if ($result) { # State is ok $hash->{FAILCOUNT} = 0; readingsSingleUpdate($hash, "state", "ok", 1); } else { # Increment failcount and report unreachable if over limit $hash->{FAILCOUNT} += 1; if ($hash->{FAILCOUNT} >= AttrVal($hash->{NAME}, "minFailCount", 1)) { readingsSingleUpdate($hash, "state", "unreachable", 1); } } delete($hash->{helper}{RUNNING_PID}); ping_SetNextTimer($hash); } ##################################### # Ping thread timeout sub ping_DoPingAbort($) { my ($hash) = @_; delete($hash->{helper}{RUNNING_PID}); Log3 $hash->{NAME}, 3, "BlockingCall for ".$hash->{NAME}." was aborted"; ping_SetNextTimer($hash); } 1; =pod =begin html

ping

    This module provides a simple "ping" function for testing the state of a remote network device.

    It allows for alerts to be triggered when devices cannot be reached using a notify function.

    Define

      define <name> ping <host/ip> <mode> <timeout>

      Specifies the ping device.
      <host/ip> is the hostname or IP address of the Bridge.

      Specifies ping mode.
      <mode> One of: tcp|udp|icmp. Read the perl docs for more detail: http://perldoc.perl.org/Net/Ping.html

      Timeout.
      <timeout> is the maximum time to wait for each ping.

    Readings

    • state
      [Initialized|ok|unreachable]: Shows reachable status check every 10 (checkInterval) seconds.

    Attributes

    • checkInterval
      Default: 10s. Time after the bridge connection is re-checked.
    • minFailCount
      Default: 1. Number of failures before reporting "unreachable".
=end html =cut