Page 1 of 1

usage of pinger script

Posted: Thursday 31 October 2019 6:41
by pvklink
HI, i was interested in the pinger script of @waaren.
When i use some of my own sites to ping, i get the following error:

019-10-31 06:35:00.567 Status: dzVents: Debug: ping: Checking 192.168.20.32
2019-10-31 06:35:01.595 Status: dzVents: Info: ping: ------ Finished ping
2019-10-31 06:35:01.595 Error: dzVents: Error: (2.4.29) ping: An error occurred when calling event handler ping
2019-10-31 06:35:01.595 Error: dzVents: Error: (2.4.29) ping: ...e/pi/domoticz/scripts/dzVents/generated_scripts/ping.lua:201: attempt to perform arithmetic on field 'consecutiveFailCount' (a nil value)
2019-10-31 06:37:47.707 Status: EventSystem: reset all events...
2019-10-31 06:37:47.710 Status: dzVents: Write file: /home/pi/domoticz/scripts/dzVents/generated_scripts/DZ_denon.lua
20

Code: Select all

  --[[ pinger: requires - dzVents >= 2.4, 
                       - find Operating System command available and executable by domoticz user
                       - ping Operating System command available and executable by domoticz user
                       - nmap Operating System command available and executable by domoticz user (only when also scanning TCP ports)
    
    This script checks if systems are reacting to a ping command or if a tcp port is responding with "open" to a nmap command
    The systems to be checked are in the table sites2Check.
    
    when no port(s) is/are in that the table, a standard ping is executed at the OS level and the returnCode is evaluated
    The default settings for notifications are entered in the table "defaults".
    These defaults can be overruled by entering them in the table sites2Check (see the example)
    When no message is entered in the table sites2Check, the composed Operating System command is used as message
    
    NB. silentTime "never" or "24:00"  means allways notify. (never silent = ) 
    
    The script checks the modification dates of the script and its companion datafile 
    to ensure a refresh or initialization of __data_scriptname.lua after any
    change to the script. 
    Note: After a refresh the number of fails and the isMessageSend flag are preserved.
   
   
    History
    ========
    20181026 Start coding
    20181027 First version active and shared on forum
    20181109 Bugfixes
    20181109 Script will recognize requirement to refresh datafile
    20181109 Add option to check ports (with nmap)
    20181109 Add option to suppress notifications / Email after first message of the day 
    20181109 Add option for non standard message

    
 ]]--

 return {
    on      =   {   timer   = { "every 5 minutes"},   
                   devices  = { "Pinger"},                      -- Used for test / development. Can be ignored
                },
                
    logging =   {   level   = domoticz.LOG_DEBUG ,              -- Change to ERROR when script is behaving as expected  
                    marker  = ""},                              -- Will be set in execute = function block 
            
    data    =   {   sites2Check   =      { initial = {}   }},   -- Iitialize empty table

                 

 execute = function(dz,_,info)                             -- We don't use the trigger but we do need the info (for scriptName) 
        
        local sites2Check , defaults  = {}, {}             -- Initialize empty tables

        -- **************************************************************** Your settings below this line
       
        defaults  = {   messageClearTime        = "05:05",    -- must be a multiple of 5 ( or change the on = { timer - {} setting
                                                              -- Once a day the sendMessage flag must be cleared; it will happen at this time      
                        maxExecutionTime        = 3.0,        -- If multiple pings fail the script will run too long. This will 
                                                              -- ensure the script stops after maxExecutionTime seconds
                        pingWaitTime            = 1,          -- the ping command parm -w is how long the ping should wait for an answer
                                                              -- if one second is not long enough you can set it to an higher value here
                        maxFailsBeforeMessage   = 3,          -- the message will only be send after this amount of consecutive failed pings or nmaps 
                        repeatMessageAfterFails = 5,          -- number of consecutive fails before Message will be send again repeat  
                        
                        silentTime     = "23:00-07:00",       -- The time window when no messages are to be send
                        repeatMessage  = false,               -- When set to false only first message on a day will be send, 
                                                              -- when true every x fails a message will be send 
                        toLog          = true,                -- used in report (should we send message to log on failure)
                        toEmail        = false,               -- used in report (should we send message to Email on failure)
                        notify         = true,                -- used in report (should we send message to notification system on failure)
                        notifyPriority = dz.PRIORITY_NORMAL,  
                        mailAddress    = "[email protected]",
                } 
                    
     -- sites2Check.example      = {address = "192.168.193.1", toEmail = true, silentTime = "never" ,repeatMessage = true }
     -- sites2Check.example2     = {address = "RaspBerry1", port = { 80,8084,1111,8080 }, message = "nmap to port failed" }
            
    --    sites2Check.HomeWizard   = {address =  "192.168.192.61" }           -- Label, Address (dns name or IP ), defaultOverrides, port(s)
    --    sites2Check.PI2          = {address = "PI-2" }                     
        sites2Check.Google       = {address = "google-public-dns-a.google.com"}
    --    sites2Check.synology     = {address = "LM46" }

        sites2Check.router       = {    address                 = "192.168.20.1",
                                        toEmail                 = true,                              -- Overrule defaults
                                        silentTime              = "01:00-06:00",                     -- Overrule defaults
                                        notifyPriority          = dz.PRIORITY_HIGH,                  -- Overrule defaults
                                        message                 = "Ping to router failed"    -- Overrule defaults
                                   }
       
        sites2Check.test         = {    address                 = "192.168.20.32",         -- address (mandatory) , port(s) (optional)
         --                               port                    = { 82 },            -- this will cause the use of nmap 
                                        maxFailsBeforeMessage   = 5,                           -- Overrule defaults   
                                        silentTime              = "00:00-07:00",               -- Overrule defaults
                                        notifyPriority          = dz.PRIORITY_HIGH,                  -- Overrule defaults
                                   } 
        
        myNotificationTable     =  {
                                     -- table with one or more notification system. 
                                     -- uncomment the notification systems that you want to be used
                                     -- Can be one or more of
                                     
                                     -- dz.NSS_GOOGLE_CLOUD_MESSAGING, 
                                     dz.NSS_PUSHOVER,               
                                     -- dz.NSS_HTTP, 
                                     -- dz.NSS_KODI, 
                                     -- dz.NSS_LOGITECH_MEDIASERVER, 
                                     -- dz.NSS_NMA,
                                     -- dz.NSS_PROWL, 
                                     -- dz.NSS_PUSHALOT, 
                                     -- dz.NSS_PUSHBULLET, 
                                     -- dz.NSS_PUSHOVER, 
                                     -- dz.NSS_PUSHSAFER,                        
                                    }
  -- **************************************************************** No changes required below this  
  
        _G.logMarker                    = info.scriptName     -- sets the logmarker for dz.log
        local messageSendClearRequired  = dz.time.matchesRule("at " .. defaults.messageClearTime)

        local function logWrite(str,level)
            dz.log(str,level or dz.LOG_DEBUG)
        end

        -- initialise persistent data
        local function initSites2Check()
            dz.data.sites2Check.defaults = defaults
            for system,_ in pairs(sites2Check) do                                   -- We only need the system names or IP, ports, consecutive failCount and flag isMessageSend
                dz.data.sites2Check[system] = dz.data.sites2Check[system] or {}
                if sites2Check[system].port then 
                    for i,portNumber in ipairs(sites2Check[system].port) do
                        dz.data.sites2Check[system][portNumber] = {}
                        dz.data.sites2Check[system][portNumber].consecutiveFailCount    =  
                        dz.data.sites2Check[system][portNumber].consecutiveFailCount or 0
                        dz.data.sites2Check[system][portNumber].isMessageSend             = 
                        dz.data.sites2Check[system][portNumber].isMessageSend or false
                    end
                else
                    dz.data.sites2Check[system].consecutiveFailCount    = 
                    dz.data.sites2Check[system].consecutiveFailCount or 0
                    dz.data.sites2Check[system].isMessageSend             = 
                    dz.data.sites2Check[system].isMessageSend or false
                end            
            end            
        end
        
        --Report to log and/or Email and/or noticication system
        local function report(systemName,returnCode,commandUsed,messageCount,isMessageSend)
            
            local system        = sites2Check[systemName]
            logWrite("default repeatMessage in report: " .. tostring(defaults.repeatMessage))
            logWrite(systemName .. " repeatMessage in report: " .. tostring(system.repeatMessage ))
            
            local repeatMessage             = system.repeatMessage           or defaults.repeatMessage
            local repeatMessageAfterFails   = system.repeatMessageAfterFails or defaults.repeatMessageAfterFails
            local priority                  = tonumber(system.notifyPriority or defaults.notifyPriority)
            local defaults                  = dz.data.sites2Check.defaults
            local message                   = system.message or  
                                                "osCommand ".. commandUsed .. " failed for " .. systemName .. ": ReturnCode ==>>: " .. returnCode
        
            local function doReport(str)
                local returnValue = false
                local silent  = dz.time.matchesRule("at " .. (system.silentTime or defaults.silentTime)) 
                local toLog   = system.toLog            or defaults.toLog 
                local toEmail = ( system.toEmail        or defaults.toEmail ) and not silent 
                local mailTo  = system.mailAddress      or defaults.mailAddress
                local notify  = ( system.notify         or defaults.notify ) and not silent
                            
                if toLog then logWrite(str,dz.LOG_FORCE) end
                if toEmail then 
                    dz.email(info.scriptName,str,mailTo) 
                    returnValue = true 
                end
           
                if notify then 
                    dz.notify(info.scriptName, str, priority, dz.SOUND_INTERMISSION,"",   myNotificationTable ) 
                    returnValue = true 
                end
                return returnValue
            end
            
            local messageCountFence = math.min(defaults.maxFailsBeforeMessage,system.maxFailsBeforeMessage or 99)
            
            if messageCount == messageCountFence then
                return doReport("initial: ".. message)
            elseif messageCount % repeatMessageAfterFails == 0 and repeatMessage and isMessageSend then
                return doReport("repeated: ".. message)
            elseif messageCount > messageCountFence  and messageCount % repeatMessageAfterFails == 0 and repeatMessage and not isMessageSend then
                return doReport("initial but delayed: " .. message)
            elseif isMessageSend then
               return true
            elseif messageCount < messageCountFence then
               return false
            end
        end
        
        local function osCommand(commandString)
            local fileHandle     = assert(io.popen(commandString, 'r'))
            local commandOutput  = assert(fileHandle:read('*a'))
            local returnTable    = {fileHandle:close()}
            return commandOutput,returnTable[3]                     -- returnTable[3] contains returnCode
        end
                
        local function evalReturnCode(t,returnCode,systemName,cmdString)
            if returnCode ~= 0 then
                t.consecutiveFailCount = t.consecutiveFailCount + 1
                t.isMessageSend = report(systemName,returnCode,cmdString,t.consecutiveFailCount,t.isMessageSend) 
            else
                t.consecutiveFailCount = 0
                t.isMessageSend = false
            end
        end
        
        local function fileExists(filename)
            local file = io.open(filename, "r")
            if file ~= nil then io.close(file) return true end
            return false 
        end
        
        local function getPersistentDataFileName()
            return globalvariables['script_path'] .. "data/__data_" .. info.scriptName .. ".lua"
        end
        
        local function invalidPersistentDatafile()
            local dataFile  = getPersistentDataFileName()
            if not fileExists(dataFile) then
                logWrite("initSites2Check() must be called. (file does not exist)" )
                return true
            end
            
            if fileExists(_G.generatedScriptsFolderPath ..  info.scriptName .. ".lua") then
                script = _G.generatedScriptsFolderPath ..  info.scriptName .. ".lua"
            else
                script = globalvariables['script_path'] .. "scripts/" .. info.scriptName .. ".lua"
            end

            if (select(1,osCommand("find " .. script .." -newer " .. dataFile))):find(script) then
                logWrite("initSites2Check() must be called. (script newer than datafile)" )
                return true
            end
            return false
        end

        local function clearMessageSend()
            logWrite("isMessageSend will be cleared")
            for systemName,attributes in pairs(sites2Check) do
                if attributes.address ~= nil then         -- defaults does not have an address
                    if attributes.port ~= nil then  -- Ports defined so we need to call nmap (ping cannot do this)
                        for i=1,#attributes.port do
                            attributes.port.isMessageSend = false
                            dz.data.sites2Check[systemName].port.isMessageSend = false
                        end    
                    else 
                        attributes.isMessageSend = false
                        dz.data.sites2Check[systemName].isMessageSend = false
                    end
                end
            end
            return true
        end
        
        local function isExecutionTimeTooLong(startedAt)
            return (os.clock() - startedAt) > defaults.maxExecutionTime 
        end
        
        
        -- main 
        local startedAt  = os.clock()
        if messageSendClearRequired then clearMessageSend() end
        if invalidPersistentDatafile() then initSites2Check() end
               
        for systemName,attributes in pairs(sites2Check) do
            if attributes.address ~= nil then         -- defaults does not have an address
                local cmdString 
                local message
                local system        = dz.data.sites2Check[systemName]
                if isExecutionTimeTooLong(startedAt) then logWrite("break main") break end
                if attributes.port ~= nil then  -- Ports defined so we need to call nmap (ping cannot do this)
                    for i=1,#attributes.port do
                        logWrite("ports defined; Checking " .. attributes.address .. " for port " .. attributes.port[i])
                        cmdString = "nmap " .. attributes.address .. " -p " .. attributes.port[i] .. " | grep open"
                        evalReturnCode(system[attributes.port[i]],select(2,osCommand(cmdString)),systemName,cmdString)
                        if isExecutionTimeTooLong(startedAt) then break end
                    end    
                else -- Simple ping command
                    local pingWaitTime = attributes.pingWaitTime or defaults.pingWaitTime
                    logWrite("Checking " .. attributes.address )
                    cmdString = "ping -c1 -w" .. pingWaitTime .. " >/dev/null " .. attributes.address
                    evalReturnCode(system,select(2,osCommand(cmdString)),systemName,cmdString)
                    if isExecutionTimeTooLong(startedAt) then break end
                end
            end
        end
    end
}

Re: usage of pinger script

Posted: Thursday 31 October 2019 8:55
by waaren
pvklink wrote: Thursday 31 October 2019 6:41 When i use some of my own sites to ping, i get the following error:
2019-10-31 06:35:01.595 Error: dzVents: Error: (2.4.29) ping: ...e/pi/domoticz/scripts/dzVents/generated_scripts/ping.lua:201: attempt to perform arithmetic on field 'consecutiveFailCount' (a nil value)
This is a bug. Thx for reporting !
The error occurs when the first result of an IP check is not OK.
Can you change line 201
from

Code: Select all

t.consecutiveFailCount = t.consecutiveFailCount + 1
to

Code: Select all

t.consecutiveFailCount = ( t.consecutiveFailCount or 0 ) + 1
and try again ?

Re: usage of pinger script

Posted: Thursday 31 October 2019 18:30
by pvklink
ok, i did!
I also changed some ip adresses to make the content fit my environment...
still new error

next one
2019-10-31 18:25:00.369 Status: dzVents: Info: : ------ Start internal script: ping:, trigger: every 5 minutes
2019-10-31 18:25:00.400 Status: dzVents: Debug: ping: Checking 192.168.20.1
2019-10-31 18:25:00.431 Status: dzVents: Debug: ping: Checking 192.168.20.35
2019-10-31 18:25:00.454 Status: dzVents: Info: ping: ------ Finished ping
2019-10-31 18:25:00.454 Error: dzVents: Error: (2.4.29) ping: An error occurred when calling event handler ping
2019-10-31 18:25:00.454 Error: dzVents: Error: (2.4.29) ping: ...e/pi/domoticz/scripts/dzVents/generated_scripts/ping.lua:199: attempt to index local 't' (a nil value)

and some later
019-10-31 18:40:00.755 Error: dzVents: Error: (2.4.29) ping: An error occurred when calling event handler ping
2019-10-31 18:40:00.755 Error: dzVents: Error: (2.4.29) ping: ...e/pi/domoticz/scripts/dzVents/generated_scripts/ping.lua:272: attempt to index local 'system' (a nil value)
2

Code: Select all

  --[[ pinger: requires - dzVents >= 2.4, 
                       - find Operating System command available and executable by domoticz user
                       - ping Operating System command available and executable by domoticz user
                       - nmap Operating System command available and executable by domoticz user (only when also scanning TCP ports)
    
    This script checks if systems are reacting to a ping command or if a tcp port is responding with "open" to a nmap command
    The systems to be checked are in the table sites2Check.
    
    when no port(s) is/are in that the table, a standard ping is executed at the OS level and the returnCode is evaluated
    The default settings for notifications are entered in the table "defaults".
    These defaults can be overruled by entering them in the table sites2Check (see the example)
    When no message is entered in the table sites2Check, the composed Operating System command is used as message
    
    NB. silentTime "never" or "24:00"  means allways notify. (never silent = ) 
    
    The script checks the modification dates of the script and its companion datafile 
    to ensure a refresh or initialization of __data_scriptname.lua after any
    change to the script. 
    Note: After a refresh the number of fails and the isMessageSend flag are preserved.
   
   
    History
    ========
    20181026 Start coding
    20181027 First version active and shared on forum
    20181109 Bugfixes
    20181109 Script will recognize requirement to refresh datafile
    20181109 Add option to check ports (with nmap)
    20181109 Add option to suppress notifications / Email after first message of the day 
    20181109 Add option for non standard message

    
 ]]--

 return {
    on      =   {   timer   = { "every 5 minutes"},   
                   devices  = { "Pinger"},                      -- Used for test / development. Can be ignored
                },
                
    logging =   {   level   = domoticz.LOG_DEBUG ,              -- Change to ERROR when script is behaving as expected  
                    marker  = ""},                              -- Will be set in execute = function block 
            
    data    =   {   sites2Check   =      { initial = {}   }},   -- Iitialize empty table

                 

 execute = function(dz,_,info)                             -- We don't use the trigger but we do need the info (for scriptName) 
        
        local sites2Check , defaults  = {}, {}             -- Initialize empty tables

        -- **************************************************************** Your settings below this line
       
        defaults  = {   messageClearTime        = "05:05",    -- must be a multiple of 5 ( or change the on = { timer - {} setting
                                                              -- Once a day the sendMessage flag must be cleared; it will happen at this time      
                        maxExecutionTime        = 3.0,        -- If multiple pings fail the script will run too long. This will 
                                                              -- ensure the script stops after maxExecutionTime seconds
                        pingWaitTime            = 1,          -- the ping command parm -w is how long the ping should wait for an answer
                                                              -- if one second is not long enough you can set it to an higher value here
                        maxFailsBeforeMessage   = 3,          -- the message will only be send after this amount of consecutive failed pings or nmaps 
                        repeatMessageAfterFails = 5,          -- number of consecutive fails before Message will be send again repeat  
                        
                        silentTime     = "23:00-07:00",       -- The time window when no messages are to be send
                        repeatMessage  = false,               -- When set to false only first message on a day will be send, 
                                                              -- when true every x fails a message will be send 
                        toLog          = true,                -- used in report (should we send message to log on failure)
                        toEmail        = false,               -- used in report (should we send message to Email on failure)
                        notify         = true,                -- used in report (should we send message to notification system on failure)
                        notifyPriority = dz.PRIORITY_NORMAL,  
                        mailAddress    = [email protected]",
                } 
                    
    --  sites2Check.example2     = {address = "RaspBerry1", port = { 80,8084,1111,8080 }, message = "nmap to port failed" }
        sites2Check.Google       = {address = "google-public-dns-a.google.com"}

        sites2Check.router       = {    address                 = "192.168.20.1",
                                        toEmail                 = true,                              -- Overrule defaults
                                        silentTime              = "01:00-06:00",                     -- Overrule defaults
                                        notifyPriority          = dz.PRIORITY_HIGH,                  -- Overrule defaults
                                        message                 = "Ping to router failed"    -- Overrule defaults
                                   }
       
        sites2Check.domoticz    = {    address                 = "192.168.20.35",         -- address (mandatory) , port(s) (optional)
         --                               port                    = { 82 },            -- this will cause the use of nmap 
                                        maxFailsBeforeMessage   = 5,                           -- Overrule defaults   
                                        silentTime              = "00:00-07:00",               -- Overrule defaults
                                        notifyPriority          = dz.PRIORITY_HIGH,                  -- Overrule defaults
                                   } 
        
        myNotificationTable     =  {
                                     -- table with one or more notification system. 
                                     -- uncomment the notification systems that you want to be used
                                     -- Can be one or more of
                                     
                                     -- dz.NSS_GOOGLE_CLOUD_MESSAGING, 
                                     dz.NSS_PUSHOVER,               
                                     -- dz.NSS_HTTP, 
                                     -- dz.NSS_KODI, 
                                     -- dz.NSS_LOGITECH_MEDIASERVER, 
                                     -- dz.NSS_NMA,
                                     -- dz.NSS_PROWL, 
                                     -- dz.NSS_PUSHALOT, 
                                     -- dz.NSS_PUSHBULLET, 
                                     -- dz.NSS_PUSHOVER, 
                                     -- dz.NSS_PUSHSAFER,                        
                                    }
  -- **************************************************************** No changes required below this  
  
        _G.logMarker                    = info.scriptName     -- sets the logmarker for dz.log
        local messageSendClearRequired  = dz.time.matchesRule("at " .. defaults.messageClearTime)

        local function logWrite(str,level)
            dz.log(str,level or dz.LOG_DEBUG)
        end

        -- initialise persistent data
        local function initSites2Check()
            dz.data.sites2Check.defaults = defaults
            for system,_ in pairs(sites2Check) do                                   -- We only need the system names or IP, ports, consecutive failCount and flag isMessageSend
                dz.data.sites2Check[system] = dz.data.sites2Check[system] or {}
                if sites2Check[system].port then 
                    for i,portNumber in ipairs(sites2Check[system].port) do
                        dz.data.sites2Check[system][portNumber] = {}
                        dz.data.sites2Check[system][portNumber].consecutiveFailCount    =  
                        dz.data.sites2Check[system][portNumber].consecutiveFailCount or 0
                        dz.data.sites2Check[system][portNumber].isMessageSend             = 
                        dz.data.sites2Check[system][portNumber].isMessageSend or false
                    end
                else
                    dz.data.sites2Check[system].consecutiveFailCount    = 
                    dz.data.sites2Check[system].consecutiveFailCount or 0
                    dz.data.sites2Check[system].isMessageSend             = 
                    dz.data.sites2Check[system].isMessageSend or false
                end            
            end            
        end
        
        --Report to log and/or Email and/or noticication system
        local function report(systemName,returnCode,commandUsed,messageCount,isMessageSend)
            
            local system        = sites2Check[systemName]
            logWrite("default repeatMessage in report: " .. tostring(defaults.repeatMessage))
            logWrite(systemName .. " repeatMessage in report: " .. tostring(system.repeatMessage ))
            
            local repeatMessage             = system.repeatMessage           or defaults.repeatMessage
            local repeatMessageAfterFails   = system.repeatMessageAfterFails or defaults.repeatMessageAfterFails
            local priority                  = tonumber(system.notifyPriority or defaults.notifyPriority)
            local defaults                  = dz.data.sites2Check.defaults
            local message                   = system.message or  
                                                "osCommand ".. commandUsed .. " failed for " .. systemName .. ": ReturnCode ==>>: " .. returnCode
        
            local function doReport(str)
                local returnValue = false
                local silent  = dz.time.matchesRule("at " .. (system.silentTime or defaults.silentTime)) 
                local toLog   = system.toLog            or defaults.toLog 
                local toEmail = ( system.toEmail        or defaults.toEmail ) and not silent 
                local mailTo  = system.mailAddress      or defaults.mailAddress
                local notify  = ( system.notify         or defaults.notify ) and not silent
                            
                if toLog then logWrite(str,dz.LOG_FORCE) end
                if toEmail then 
                    dz.email(info.scriptName,str,mailTo) 
                    returnValue = true 
                end
           
                if notify then 
                    dz.notify(info.scriptName, str, priority, dz.SOUND_INTERMISSION,"",   myNotificationTable ) 
                    returnValue = true 
                end
                return returnValue
            end
            
            local messageCountFence = math.min(defaults.maxFailsBeforeMessage,system.maxFailsBeforeMessage or 99)
            
            if messageCount == messageCountFence then
                return doReport("initial: ".. message)
            elseif messageCount % repeatMessageAfterFails == 0 and repeatMessage and isMessageSend then
                return doReport("repeated: ".. message)
            elseif messageCount > messageCountFence  and messageCount % repeatMessageAfterFails == 0 and repeatMessage and not isMessageSend then
                return doReport("initial but delayed: " .. message)
            elseif isMessageSend then
               return true
            elseif messageCount < messageCountFence then
               return false
            end
        end
        
        local function osCommand(commandString)
            local fileHandle     = assert(io.popen(commandString, 'r'))
            local commandOutput  = assert(fileHandle:read('*a'))
            local returnTable    = {fileHandle:close()}
            return commandOutput,returnTable[3]                     -- returnTable[3] contains returnCode
        end
                
        local function evalReturnCode(t,returnCode,systemName,cmdString)
            if returnCode ~= 0 then
                t.consecutiveFailCount = ( t.consecutiveFailCount or 0 ) + 1
                t.isMessageSend = report(systemName,returnCode,cmdString,t.consecutiveFailCount,t.isMessageSend) 
            else
                t.consecutiveFailCount = 0
                t.isMessageSend = false
            end
        end
        
        local function fileExists(filename)
            local file = io.open(filename, "r")
            if file ~= nil then io.close(file) return true end
            return false 
        end
        
        local function getPersistentDataFileName()
            return globalvariables['script_path'] .. "data/__data_" .. info.scriptName .. ".lua"
        end
        
        local function invalidPersistentDatafile()
            local dataFile  = getPersistentDataFileName()
            if not fileExists(dataFile) then
                logWrite("initSites2Check() must be called. (file does not exist)" )
                return true
            end
            
            if fileExists(_G.generatedScriptsFolderPath ..  info.scriptName .. ".lua") then
                script = _G.generatedScriptsFolderPath ..  info.scriptName .. ".lua"
            else
                script = globalvariables['script_path'] .. "scripts/" .. info.scriptName .. ".lua"
            end

            if (select(1,osCommand("find " .. script .." -newer " .. dataFile))):find(script) then
                logWrite("initSites2Check() must be called. (script newer than datafile)" )
                return true
            end
            return false
        end

        local function clearMessageSend()
            logWrite("isMessageSend will be cleared")
            for systemName,attributes in pairs(sites2Check) do
                if attributes.address ~= nil then         -- defaults does not have an address
                    if attributes.port ~= nil then  -- Ports defined so we need to call nmap (ping cannot do this)
                        for i=1,#attributes.port do
                            attributes.port.isMessageSend = false
                            dz.data.sites2Check[systemName].port.isMessageSend = false
                        end    
                    else 
                        attributes.isMessageSend = false
                        dz.data.sites2Check[systemName].isMessageSend = false
                    end
                end
            end
            return true
        end
        
        local function isExecutionTimeTooLong(startedAt)
            return (os.clock() - startedAt) > defaults.maxExecutionTime 
        end
        
        
        -- main 
        local startedAt  = os.clock()
        if messageSendClearRequired then clearMessageSend() end
        if invalidPersistentDatafile() then initSites2Check() end
               
        for systemName,attributes in pairs(sites2Check) do
            if attributes.address ~= nil then         -- defaults does not have an address
                local cmdString 
                local message
                local system        = dz.data.sites2Check[systemName]
                if isExecutionTimeTooLong(startedAt) then logWrite("break main") break end
                if attributes.port ~= nil then  -- Ports defined so we need to call nmap (ping cannot do this)
                    for i=1,#attributes.port do
                        logWrite("ports defined; Checking " .. attributes.address .. " for port " .. attributes.port[i])
                        cmdString = "nmap " .. attributes.address .. " -p " .. attributes.port[i] .. " | grep open"
                        evalReturnCode(system[attributes.port[i]],select(2,osCommand(cmdString)),systemName,cmdString)
                        if isExecutionTimeTooLong(startedAt) then break end
                    end    
                else -- Simple ping command
                    local pingWaitTime = attributes.pingWaitTime or defaults.pingWaitTime
                    logWrite("Checking " .. attributes.address )
                    cmdString = "ping -c1 -w" .. pingWaitTime .. " >/dev/null " .. attributes.address
                    evalReturnCode(system,select(2,osCommand(cmdString)),systemName,cmdString)
                    if isExecutionTimeTooLong(startedAt) then break end
                end
            end
        end
    end
}

Re: usage of pinger script

Posted: Thursday 31 October 2019 20:48
by waaren
pvklink wrote: Thursday 31 October 2019 18:30

Code: Select all

2019-10-31 18:25:00.369 Status: dzVents: Info: : ------ Start internal script: ping:, trigger: every 5 minutes
2019-10-31 18:25:00.400 Status: dzVents: Debug: ping: Checking 192.168.20.1
2019-10-31 18:25:00.431 Status: dzVents: Debug: ping: Checking 192.168.20.35
2019-10-31 18:25:00.454 Status: dzVents: Info: ping: ------ Finished ping
2019-10-31 18:25:00.454 Error: dzVents: Error: (2.4.29) ping: An error occurred when calling event handler ping
2019-10-31 18:25:00.454 Error: dzVents: Error: (2.4.29) ping: ...e/pi/domoticz/scripts/dzVents/generated_scripts/ping.lua:199: attempt to index local 't' (a nil value)
Funny; looking at the times in this log it seems that the Errors occur after the script finished.

To enable more debugging can you replace the evalReturnCode function with the one below ? Best to report back via PM because I don't have a clue yet and I don't want to bore the other forum users. When the error(s) are found and solved I will update the original script and explain what was wrong.

Code: Select all


        local function evalReturnCode(t,returnCode,systemName,cmdString)
            if type(t) == 'table' then dz.utils.dumpTable(t) else dz.log('No table: ' .. tostring(t),dz.LOG_FORCE) end
            dz.log('Returncode: ' .. tostring(returnCode),dz.LOG_FORCE)
            dz.log('systemName: ' .. tostring(systemName),dz.LOG_FORCE)
            dz.log('cmdString: ' .. tostring(cmdString),dz.LOG_FORCE)
            
            if returnCode ~= 0 then
                t.consecutiveFailCount = ( t.consecutiveFailCount or 0 ) + 1
                t.isMessageSend = report(systemName,returnCode,cmdString,t.consecutiveFailCount,t.isMessageSend) 
            else
                t.consecutiveFailCount = 0
                t.isMessageSend = false
            end
        end

Re: usage of pinger script  [Solved]

Posted: Thursday 31 October 2019 23:29
by pvklink
Solved!

When changing a ping device in the script, you have to:
1. rename the script
2. or delete the data file

otherwise you got an error as described...

Re: usage of pinger script

Posted: Friday 01 November 2019 7:22
by pvklink
@waaren thanks again, for challenging to solve the problem...

Re: usage of pinger script

Posted: Wednesday 06 November 2019 12:04
by pvklink
Hi @waaren

I did adjust the script so it can switch an device on or off..
I added lines, see -- pvk
All works great!
Only thing i cant solve is that when my switch is named "06-peter" it gives an error!
I think because the script thinks it has to do an arithmic job on this variable pvkswitch when value is 06-peter
Is this easy to solve, otherwise no problem, i rename my devices...

rule
dz.devices(system.pvkswitch).switchOn().checkFirst()

It works all ok, when i rename the device to mobielpeter

Code: Select all

  --[[ pinger: requires - dzVents >= 2.4, 
                       - find Operating System command available and executable by domoticz user
                       - ping Operating System command available and executable by domoticz user
                       - nmap Operating System command available and executable by domoticz user (only when also scanning TCP ports)
    
    This script checks if systems are reacting to a ping command or if a tcp port is responding with "open" to a nmap command
    The systems to be checked are in the table sites2Check.
    
    when no port(s) is/are in that the table, a standard ping is executed at the OS level and the returnCode is evaluated
    The default settings for notifications are entered in the table "defaults".
    These defaults can be overruled by entering them in the table sites2Check (see the example)
    When no message is entered in the table sites2Check, the composed Operating System command is used as message
    
    NB. silentTime "never" or "24:00"  means allways notify. (never silent = ) 
    
    The script checks the modification dates of the script and its companion datafile 
    to ensure a refresh or initialization of __data_scriptname.lua after any
    change to the script. 
    Note: After a refresh the number of fails and the isMessageSend flag are preserved.
   
 ]]--

 return {
    on      =   {   timer   = { "every 5 minutes"},   
                   devices  = { "Pinger"},                      -- Used for test / development. Can be ignored
                },
                
    logging =   {   level   = domoticz.LOG_DEBUG ,              -- Change to ERROR when script is behaving as expected  
                    marker  = ""},                              -- Will be set in execute = function block 
            
    data    =   {   sites2Check   =      { initial = {}   }},   -- Iitialize empty table

                 

 execute = function(dz,_,info)                             -- We don't use the trigger but we do need the info (for scriptName) 
        
        local sites2Check , defaults  = {}, {}             -- Initialize empty tables

        -- **************************************************************** Your settings below this line
       
        defaults  = {   messageClearTime        = "05:05",    -- must be a multiple of 5 ( or change the on = { timer - {} setting
                                                              -- Once a day the sendMessage flag must be cleared; it will happen at this time      
                        maxExecutionTime        = 3.0,        -- If multiple pings fail the script will run too long. This will 
                                                              -- ensure the script stops after maxExecutionTime seconds
                        pingWaitTime            = 1,          -- the ping command parm -w is how long the ping should wait for an answer
                                                              -- if one second is not long enough you can set it to an higher value here
                        pvkswitch               = "",         -- device to put on or off when ping succeeds or fails
                        maxFailsBeforeMessage   = 3,          -- the message will only be send after this amount of consecutive failed pings or nmaps 
                        repeatMessageAfterFails = 5,          -- number of consecutive fails before Message will be send again repeat  
                        
                        silentTime     = "23:00-07:00",       -- The time window when no messages are to be send
                        repeatMessage  = true,                -- When set to false only first message on a day will be send, 
                                                              -- when true every x fails a message will be send 
                        toLog          = true,                -- used in report (should we send message to log on failure)
                        toEmail        = true,                -- used in report (should we send message to Email on failure)
                        notify         = false,                -- used in report (should we send message to notification system on failure)
                        notifyPriority = dz.PRIORITY_NORMAL,  
                        mailAddress    = "[email protected]",
                } 
                    
    --    sites2Check.dnsgoogle               =   {address = "8.8.8.8", pvkswitch = "dns google"}
    --    sites2Check.NAS_ip                  =   {address = "192.168.20.80", pvkswitch = "NAS_ip"}
   --     sites2Check.Accesspoint_ip          =   {address = "192.168.1.99", pvkswitch = "Accesspoint_ip"}
    --    sites2Check.switch_ip               =   {address = "192.168.1.95", pvkswitch = "switch_ip"}
   --     sites2Check.Ziggo_ip_wan            =   {address = "83.85.15.117", pvkswitch = "Ziggo_ip_wan"}
   --     sites2Check.Ziggo_ip_lan            =   {address = "192.168.178.1", pvkswitch = "Ziggo_ip_lan"}
   --     sites2Check.edgerouter_ip           =   {address = "192.168.20.1", pvkswitch = "edgerouter_ip"}
   --     sites2Check.tv_studiekamer          =   {address = "192.168.20.28", pvkswitch = "tv-studiekamer"}
   --     sites2Check.tv_niels                =   {address = "192.168.30.46", pvkswitch = "tv-niels"}
   --     sites2Check.tv_woonkamer            =   {address = "192.168.20.24", pvkswitch = "tv-woonkamer"}
   --     sites2Check.tv_sonia                =   {address = "192.168.30.45", pvkswitch = "tv-sonia"}
   --     sites2Check.pcpeter                 =   {address = "192.168.20.10", pvkswitch = "pc-peter"}
    --    sites2Check.pc_peter_laptop         =   {address = "192.168.20.10", pvkswitch = "pc-peter-laptop"}
    --    sites2Check.pc_sonia_laptop         =   {address = "192.168.30.33", pvkswitch = "pc-sonia-laptop"}
    --    sites2Check.pc_niels_laptop         =   {address = "192.168.30.32", pvkswitch = "pc-niels-laptop"}
    --    sites2Check.pc_niels_game           =   {address = "192.168.30.34", pvkswitch = "pc-niels-game"}
    --    sites2Check.pc_niels_game_noodvz    =   {address = "192.168.178.12", pvkswitch = "pc-niels-game-noodvz"}
    --    sites2Check.pc_niels_game_wifi      =   {address = "192.168.30.37", pvkswitch = "pc-niels-game-wifi"}
    --    sites2Check.tablet_peter            =   {address = "192.168.20.11", pvkswitch = "tablet-peter"}
    --    sites2Check.tablet_sonia            =   {address = "192.168.30.36", pvkswitch = "tablet-sonia"}
    --    sites2Check.tablet_monique          =   {address = "192.168.20.44", pvkswitch = "tablet-monique"}
    --    sites2Check.ps4_lan_ip              =   {address = "192.168.30.30", pvkswitch = "ps4_lan_ip"}
    --    sites2Check.wii_lan_ip              =   {address = "192.168.30.31", pvkswitch = "wii_lan_ip"}
        sites2Check.kodi                    =   {address = "192.168.20.22", pvkswitch = "kodi"} --{address = "RaspBerry1", port = { 80,8084,1111,8080 }, message = "nmap to port failed" }
    --    sites2Check.mobiel_niels            =   {address = "192.168.30.43", pvkswitch = "06-niels"}
    --    sites2Check.mobiel_sonia            =   {address = "192.168.30.42", pvkswitch = "06-sonia"}
          sites2Check.mobiel_peter            =   {address = "192.168.20.42", pvkswitch = "mobielpeter"}
    --    sites2Check.mobiel_monique          =   {address = "192.168.20.41", pvkswitch = "06-monique"}

    --    sites2Check.kodix        =   {    address                 = "192.168.20.22",                    -- address (mandatory) , port(s) (optional)
    --                                    --port                    = { 82 },                           -- this will cause the use of nmap 
    --                                    maxFailsBeforeMessage   = 5,                                -- Overrule defaults   
    --                                    pvkswitch               = "kodix",                             -- Overrule defaults            -- added pvk
    --                                    silentTime              = "00:00-07:00",                    -- Overrule defaults
    --                                    notifyPriority          = dz.PRIORITY_HIGH,                 -- Overrule defaults
    --                                    message                 = "Ping to kodi failed"             -- Overrule defaults
    --                                } 
  
        myNotificationTable     =  {
                                     -- table with one or more notification system. 
                                     -- uncomment the notification systems that you want to be used
                                     -- Can be one or more of
                                     
                                     -- dz.NSS_GOOGLE_CLOUD_MESSAGING, 
                                     dz.NSS_PUSHOVER,               
                                     -- dz.NSS_HTTP, 
                                     -- dz.NSS_KODI, 
                                     -- dz.NSS_LOGITECH_MEDIASERVER, 
                                     -- dz.NSS_NMA,
                                     -- dz.NSS_PROWL, 
                                     -- dz.NSS_PUSHALOT, 
                                     -- dz.NSS_PUSHBULLET, 
                                     -- dz.NSS_PUSHOVER, 
                                     -- dz.NSS_PUSHSAFER,                        
                                    }
  -- **************************************************************** No changes required below this  
  
        _G.logMarker                    = info.scriptName     -- sets the logmarker for dz.log
        local messageSendClearRequired  = dz.time.matchesRule("at " .. defaults.messageClearTime)

        local function logWrite(str,level)
            dz.log(str,level or dz.LOG_DEBUG)
        end

        -- initialise persistent data
        local function initSites2Check()
            dz.data.sites2Check.defaults = defaults
            for system,_ in pairs(sites2Check) do                                   -- We only need the system names or IP, ports, consecutive failCount and flag isMessageSend
                dz.data.sites2Check[system] = dz.data.sites2Check[system] or {}
                if sites2Check[system].port then 
                    for i,portNumber in ipairs(sites2Check[system].port) do
                        dz.data.sites2Check[system][portNumber] = {}
                        dz.data.sites2Check[system][portNumber].consecutiveFailCount    =  
                        dz.data.sites2Check[system][portNumber].consecutiveFailCount or 0
                        dz.data.sites2Check[system][portNumber].isMessageSend             = 
                        dz.data.sites2Check[system][portNumber].isMessageSend or false
                    end
                else
                    dz.data.sites2Check[system].consecutiveFailCount    = 
                    dz.data.sites2Check[system].consecutiveFailCount or 0
                    dz.data.sites2Check[system].isMessageSend             = 
                    dz.data.sites2Check[system].isMessageSend or false
                end            
            end            
        end
        
        --Report to log and/or Email and/or noticication system
        local function report(systemName,returnCode,commandUsed,messageCount,isMessageSend)
            
            local system        = sites2Check[systemName]
            logWrite("default repeatMessage in report: " .. tostring(defaults.repeatMessage))
            logWrite(systemName .. " repeatMessage in report: " .. tostring(system.repeatMessage ))
            
            local repeatMessage             = system.repeatMessage           or defaults.repeatMessage
            local repeatMessageAfterFails   = system.repeatMessageAfterFails or defaults.repeatMessageAfterFails
            local priority                  = tonumber(system.notifyPriority or defaults.notifyPriority)
            local defaults                  = dz.data.sites2Check.defaults
            local message                   = system.message or  
                                                "osCommand ".. commandUsed .. " failed for " .. systemName .. ": ReturnCode ==>>: " .. returnCode
        
            local function doReport(str)
                local returnValue = false
                local silent  = dz.time.matchesRule("at " .. (system.silentTime or defaults.silentTime)) 
                local toLog   = system.toLog            or defaults.toLog 
                local toEmail = ( system.toEmail        or defaults.toEmail ) and not silent 
                local mailTo  = system.mailAddress      or defaults.mailAddress
                local notify  = ( system.notify         or defaults.notify ) and not silent

                if toLog then logWrite(str,dz.LOG_FORCE) end
                if toEmail then 
                    dz.email(info.scriptName,str,mailTo) 
                    returnValue = true 
                end
           
                if notify then 
                    dz.notify(info.scriptName, str, priority, dz.SOUND_INTERMISSION,"",   myNotificationTable ) 
                    returnValue = true 
                end
                return returnValue
            end
            
            local messageCountFence = math.min(defaults.maxFailsBeforeMessage,system.maxFailsBeforeMessage or 99)
            
            if messageCount == messageCountFence then
                return doReport("initial: ".. message)
            elseif messageCount % repeatMessageAfterFails == 0 and repeatMessage and isMessageSend then
                return doReport("repeated: ".. message)
            elseif messageCount > messageCountFence  and messageCount % repeatMessageAfterFails == 0 and repeatMessage and not isMessageSend then
                return doReport("initial but delayed: " .. message)
            elseif isMessageSend then
               return true
            elseif messageCount < messageCountFence then
               return false
            end

        end
        
        local function osCommand(commandString)
            local fileHandle     = assert(io.popen(commandString, 'r'))
            local commandOutput  = assert(fileHandle:read('*a'))
            local returnTable    = {fileHandle:close()}
            return commandOutput,returnTable[3]                     -- returnTable[3] contains returnCode
        end
                
        local function evalReturnCode(t,returnCode,systemName,cmdString)
            if type(t) == 'table' then dz.utils.dumpTable(t) else dz.log('No table: ' .. tostring(t),dz.LOG_FORCE) end
            dz.log('Returncode: ' .. tostring(returnCode),dz.LOG_FORCE)
            dz.log('systemName: ' .. tostring(systemName),dz.LOG_FORCE)
            dz.log('cmdString: ' .. tostring(cmdString),dz.LOG_FORCE)

            -- added pvk 
            local system        = sites2Check[systemName]
            --dz.log('PING1: Device: ' .. systemName .. ' Returncode' ..  tostring(returnCode))
            if system.pvkswitch then
                if returnCode == 0 then -- node aan
                    dz.devices(system.pvkswitch).switchOn().checkFirst()
                    dz.log('PING10: Device: ' .. systemName .. ' Returncode: ' .. tostring(returnCode))
                else    -- node uit
                    dz.devices(system.pvkswitch).switchOff().checkFirst()
                    dz.log('PING112: Device: ' .. systemName .. ' Returncode: ' .. tostring(returnCode))
                end    
            end
            -- end pvk
        
            if returnCode ~= 0 then
                t.consecutiveFailCount = ( t.consecutiveFailCount or 0 ) + 1
                t.isMessageSend = report(systemName,returnCode,cmdString,t.consecutiveFailCount,t.isMessageSend) 
            else
                t.consecutiveFailCount = 0
                t.isMessageSend = false
            end
        end

        local function fileExists(filename)
            local file = io.open(filename, "r")
            if file ~= nil then io.close(file) return true end
            return false 
        end
        
        local function getPersistentDataFileName()
            return globalvariables['script_path'] .. "data/__data_" .. info.scriptName .. ".lua"
        end
        
        local function invalidPersistentDatafile()
            local dataFile  = getPersistentDataFileName()
            if not fileExists(dataFile) then
                logWrite("initSites2Check() must be called. (file does not exist)" )
                return true
            end
            
            if fileExists(_G.generatedScriptsFolderPath ..  info.scriptName .. ".lua") then
                script = _G.generatedScriptsFolderPath ..  info.scriptName .. ".lua"
            else
                script = globalvariables['script_path'] .. "scripts/" .. info.scriptName .. ".lua"
            end

            if (select(1,osCommand("find " .. script .." -newer " .. dataFile))):find(script) then
                logWrite("initSites2Check() must be called. (script newer than datafile)" )
                return true
            end
            return false
        end

        local function clearMessageSend()
            logWrite("isMessageSend will be cleared")
            for systemName,attributes in pairs(sites2Check) do
                if attributes.address ~= nil then         -- defaults does not have an address
                    if attributes.port ~= nil then  -- Ports defined so we need to call nmap (ping cannot do this)
                        for i=1,#attributes.port do
                            attributes.port.isMessageSend = false
                            dz.data.sites2Check[systemName].port.isMessageSend = false
                        end    
                    else 
                        attributes.isMessageSend = false
                        dz.data.sites2Check[systemName].isMessageSend = false
                    end
                end
            end
            return true
        end
        
        local function isExecutionTimeTooLong(startedAt)
            return (os.clock() - startedAt) > defaults.maxExecutionTime 
        end
        
        
        -- main 
        local startedAt  = os.clock()
        if messageSendClearRequired then clearMessageSend() end
        if invalidPersistentDatafile() then initSites2Check() end
               
        for systemName,attributes in pairs(sites2Check) do
            if attributes.address ~= nil then         -- defaults does not have an address
                local cmdString 
                local message
                local system        = dz.data.sites2Check[systemName]
                if isExecutionTimeTooLong(startedAt) then logWrite("break main") break end
                if attributes.port ~= nil then  -- Ports defined so we need to call nmap (ping cannot do this)
                    for i=1,#attributes.port do
                        logWrite("ports defined; Checking " .. attributes.address .. " for port " .. attributes.port[i])
                        cmdString = "nmap " .. attributes.address .. " -p " .. attributes.port[i] .. " | grep open"
                        evalReturnCode(system[attributes.port[i]],select(2,osCommand(cmdString)),systemName,cmdString)
                        if isExecutionTimeTooLong(startedAt) then break end
                    end    
                else -- Simple ping command
                    local pingWaitTime = attributes.pingWaitTime or defaults.pingWaitTime
                    logWrite("Checking " .. attributes.address )
                    cmdString = "ping -c1 -w" .. pingWaitTime .. " >/dev/null " .. attributes.address
                    evalReturnCode(system,select(2,osCommand(cmdString)),systemName,cmdString)
                    if isExecutionTimeTooLong(startedAt) then break end
                end
            end
        end
    end
}








Re: usage of pinger script

Posted: Wednesday 06 November 2019 13:38
by waaren
pvklink wrote: Wednesday 06 November 2019 12:04 Only thing i cant solve is that when my switch is named "06-peter" it gives an error!
Please don't let me guess where the error is. Show the logline with the error !

Re: usage of pinger script

Posted: Wednesday 06 November 2019 16:30
by pvklink
ok, problems solved!

1. i restarted domoticz
2. cleared the datafiles
3. removed the remote server connection.. I pinged the devices on a client of a remote server..

It works all fine now, i even can use switchname with a '-' in the name...

For those who want to switch on/off devices when ping is ok/nok, this is the script with a little addon...
It now has the same functionality as the default ping script in the hardware tab.

Code: Select all

   --[[ pinger: requires - dzVents >= 2.4, 
                       - find Operating System command available and executable by domoticz user
                       - ping Operating System command available and executable by domoticz user
                       - nmap Operating System command available and executable by domoticz user (only when also scanning TCP ports)
    
    This script checks if systems are reacting to a ping command or if a tcp port is responding with "open" to a nmap command
    The systems to be checked are in the table sites2Check.
    
    when no port(s) is/are in that the table, a standard ping is executed at the OS level and the returnCode is evaluated
    The default settings for notifications are entered in the table "defaults".
    These defaults can be overruled by entering them in the table sites2Check (see the example)
    When no message is entered in the table sites2Check, the composed Operating System command is used as message
    
    NB. silentTime "never" or "24:00"  means allways notify. (never silent = ) 
    
    The script checks the modification dates of the script and its companion datafile 
    to ensure a refresh or initialization of __data_scriptname.lua after any
    change to the script. 
    Note: After a refresh the number of fails and the isMessageSend flag are preserved.
   
 ]]--

 return {
    on      =   {   timer   = { "every 5 minutes"},   
                   devices  = { "Pinger"},                      -- Used for test / development. Can be ignored
                },
                
    logging =   {   level   = domoticz.LOG_DEBUG ,              -- Change to ERROR when script is behaving as expected  
                    marker  = ""},                              -- Will be set in execute = function block 
            
    data    =   {   sites2Check   =      { initial = {}   }},   -- Iitialize empty table

                 

 execute = function(dz,_,info)                             -- We don't use the trigger but we do need the info (for scriptName) 
        
        local sites2Check , defaults  = {}, {}             -- Initialize empty tables

        -- **************************************************************** Your settings below this line
       
        defaults  = {   messageClearTime        = "05:05",    -- must be a multiple of 5 ( or change the on = { timer - {} setting
                                                              -- Once a day the sendMessage flag must be cleared; it will happen at this time      
                        maxExecutionTime        = 30.0,       -- If multiple pings fail the script will run too long. This will 
                                                              -- ensure the script stops after maxExecutionTime seconds
                        pingWaitTime            = 1,          -- the ping command parm -w is how long the ping should wait for an answer
                                                              -- if one second is not long enough you can set it to an higher value here
                        pvkswitch               = "",         -- device to put on or off when ping succeeds or fails
                        maxFailsBeforeMessage   = 3,          -- the message will only be send after this amount of consecutive failed pings or nmaps 
                        repeatMessageAfterFails = 5,          -- number of consecutive fails before Message will be send again repeat  
                        
                        silentTime     = "23:00-07:00",       -- The time window when no messages are to be send
                        repeatMessage  = true,                -- When set to false only first message on a day will be send, 
                                                              -- when true every x fails a message will be send 
                        toLog          = true,                -- used in report (should we send message to log on failure)
                        toEmail        = false,                -- used in report (should we send message to Email on failure)
                        notify         = false,               -- used in report (should we send message to notification system on failure)
                        notifyPriority = dz.PRIORITY_NORMAL,  
                        mailAddress    = "[email protected]",
                } 
                    
        sites2Check.dnsgoogle               =   {address = "8.8.8.8", pvkswitch = "dns_google"}
        sites2Check.NAS_ip                  =   {address = "192.168.20.80", pvkswitch = "NAS_ip"}
        sites2Check.Accesspoint_ip          =   {address = "192.168.1.99", pvkswitch = "Accesspoint_ip"}
        sites2Check.switch_ip               =   {address = "192.168.1.95", pvkswitch = "switch_ip"}
        sites2Check.Ziggo_ip_lan            =   {address = "192.168.178.1", pvkswitch = "Ziggo_ip_lan"}
        sites2Check.edgerouter_ip           =   {address = "192.168.20.1", pvkswitch = "edgerouter_ip"}
        sites2Check.tv_studiekamer          =   {address = "192.168.20.28", pvkswitch = "tv_studiekamer"}
        sites2Check.tv_niels                =   {address = "192.168.30.46", pvkswitch = "tv_niels"}
        sites2Check.tv_woonkamer            =   {address = "192.168.20.24", pvkswitch = "tv_woonkamer"}
        sites2Check.tv_sonia                =   {address = "192.168.30.45", pvkswitch = "tv_sonia"}
        sites2Check.pc_peter                =   {address = "192.168.1.10", pvkswitch = "pc-peter"}
        sites2Check.pc_peter_laptop         =   {address = "192.168.20.10", pvkswitch = "pc_peter_laptop"}
        sites2Check.pc_sonia_laptop         =   {address = "192.168.30.33", pvkswitch = "pc_sonia_laptop"}
        sites2Check.pc_niels_laptop         =   {address = "192.168.30.32", pvkswitch = "pc_niels_laptop"}
        sites2Check.pc_niels_game           =   {address = "192.168.30.34", pvkswitch = "pc_niels_game"}
        sites2Check.pc_niels_game_noodvz    =   {address = "192.168.178.12", pvkswitch = "pc_niels_game_noodvz"}
        sites2Check.pc_niels_game_wifi      =   {address = "192.168.30.37", pvkswitch = "pc_niels_game_wifi"}
        sites2Check.tablet_peter            =   {address = "192.168.20.11", pvkswitch = "tablet_peter"}
        sites2Check.tablet_sonia            =   {address = "192.168.30.36", pvkswitch = "tablet_sonia"}
        sites2Check.tablet_monique          =   {address = "192.168.20.44", pvkswitch = "tablet_monique"}
        sites2Check.ps4_lan_ip              =   {address = "192.168.30.30", pvkswitch = "ps4_lan_ip"}
        sites2Check.wii_lan_ip              =   {address = "192.168.30.31", pvkswitch = "wii_lan_ip"}
        sites2Check.kodi                    =   {address = "192.168.20.22", pvkswitch = "kodi"} 
        sites2Check.mobiel_niels            =   {address = "192.168.30.43", pvkswitch = "mobiel_niels"}
        sites2Check.mobiel_sonia            =   {address = "192.168.30.42", pvkswitch = "mobiel_sonia"}
        sites2Check.mobiel_peter            =   {address = "192.168.20.42", pvkswitch = "mobiel_peter"}
        sites2Check.mobiel_monique          =   {address = "192.168.20.41", pvkswitch = "mobiel_monique"}

    --    sites2Check.kodix        =   {    address                 = "192.168.20.22",                    -- address (mandatory) , port(s) (optional)
    --                                    --port                    = { 82, 88 },                           -- this will cause the use of nmap 
    --                                    maxFailsBeforeMessage   = 5,                                -- Overrule defaults   
    --                                    pvkswitch               = "kodix",                             -- Overrule defaults            -- added pvk
    --                                    silentTime              = "00:00-07:00",                    -- Overrule defaults
    --                                    notifyPriority          = dz.PRIORITY_HIGH,                 -- Overrule defaults
    --                                    message                 = "Ping to kodi failed"             -- Overrule defaults
    --                                } 
  
        myNotificationTable     =  {
                                     -- table with one or more notification system. 
                                     -- uncomment the notification systems that you want to be used
                                     -- Can be one or more of
                                     
                                     -- dz.NSS_GOOGLE_CLOUD_MESSAGING, 
                                     dz.NSS_PUSHOVER,               
                                     -- dz.NSS_HTTP, 
                                     -- dz.NSS_KODI, 
                                     -- dz.NSS_LOGITECH_MEDIASERVER, 
                                     -- dz.NSS_NMA,
                                     -- dz.NSS_PROWL, 
                                     -- dz.NSS_PUSHALOT, 
                                     -- dz.NSS_PUSHBULLET, 
                                     -- dz.NSS_PUSHOVER, 
                                     -- dz.NSS_PUSHSAFER,                        
                                    }
  -- **************************************************************** No changes required below this  
  
        _G.logMarker                    = info.scriptName     -- sets the logmarker for dz.log
        local messageSendClearRequired  = dz.time.matchesRule("at " .. defaults.messageClearTime)

        local function logWrite(str,level)
            dz.log(str,level or dz.LOG_DEBUG)
        end

        -- initialise persistent data
        local function initSites2Check()
            dz.data.sites2Check.defaults = defaults
            for system,_ in pairs(sites2Check) do                                   -- We only need the system names or IP, ports, consecutive failCount and flag isMessageSend
                dz.data.sites2Check[system] = dz.data.sites2Check[system] or {}
                if sites2Check[system].port then 
                    for i,portNumber in ipairs(sites2Check[system].port) do
                        dz.data.sites2Check[system][portNumber] = {}
                        dz.data.sites2Check[system][portNumber].consecutiveFailCount    =  
                        dz.data.sites2Check[system][portNumber].consecutiveFailCount or 0
                        dz.data.sites2Check[system][portNumber].isMessageSend             = 
                        dz.data.sites2Check[system][portNumber].isMessageSend or false
                    end
                else
                    dz.data.sites2Check[system].consecutiveFailCount    = 
                    dz.data.sites2Check[system].consecutiveFailCount or 0
                    dz.data.sites2Check[system].isMessageSend             = 
                    dz.data.sites2Check[system].isMessageSend or false
                end            
            end            
        end
        
        --Report to log and/or Email and/or noticication system
        local function report(systemName,returnCode,commandUsed,messageCount,isMessageSend)
            
            local system        = sites2Check[systemName]
            logWrite("default repeatMessage in report: " .. tostring(defaults.repeatMessage))
            logWrite(systemName .. " repeatMessage in report: " .. tostring(system.repeatMessage ))
            
            local repeatMessage             = system.repeatMessage           or defaults.repeatMessage
            local repeatMessageAfterFails   = system.repeatMessageAfterFails or defaults.repeatMessageAfterFails
            local priority                  = tonumber(system.notifyPriority or defaults.notifyPriority)
            local defaults                  = dz.data.sites2Check.defaults
            local message                   = system.message or  
                                                "osCommand ".. commandUsed .. " failed for " .. systemName .. ": ReturnCode ==>>: " .. returnCode
        
            local function doReport(str)
                local returnValue = false
                local silent  = dz.time.matchesRule("at " .. (system.silentTime or defaults.silentTime)) 
                local toLog   = system.toLog            or defaults.toLog 
                local toEmail = ( system.toEmail        or defaults.toEmail ) and not silent 
                local mailTo  = system.mailAddress      or defaults.mailAddress
                local notify  = ( system.notify         or defaults.notify ) and not silent

                if toLog then logWrite(str,dz.LOG_FORCE) end
                if toEmail then 
                    dz.email(info.scriptName,str,mailTo) 
                    returnValue = true 
                end
           
                if notify then 
                    dz.notify(info.scriptName, str, priority, dz.SOUND_INTERMISSION,"",   myNotificationTable ) 
                    returnValue = true 
                end
                return returnValue
            end
            
            local messageCountFence = math.min(defaults.maxFailsBeforeMessage,system.maxFailsBeforeMessage or 99)
            
            if messageCount == messageCountFence then
                return doReport("initial: ".. message)
            elseif messageCount % repeatMessageAfterFails == 0 and repeatMessage and isMessageSend then
                return doReport("repeated: ".. message)
            elseif messageCount > messageCountFence  and messageCount % repeatMessageAfterFails == 0 and repeatMessage and not isMessageSend then
                return doReport("initial but delayed: " .. message)
            elseif isMessageSend then
               return true
            elseif messageCount < messageCountFence then
               return false
            end

        end
        
        local function osCommand(commandString)
            local fileHandle     = assert(io.popen(commandString, 'r'))
            local commandOutput  = assert(fileHandle:read('*a'))
            local returnTable    = {fileHandle:close()}
            return commandOutput,returnTable[3]                     -- returnTable[3] contains returnCode
        end
                
        local function evalReturnCode(t,returnCode,systemName,cmdString)
            if type(t) == 'table' then dz.utils.dumpTable(t) else dz.log('No table: ' .. tostring(t),dz.LOG_FORCE) end
            dz.log('Returncode: ' .. tostring(returnCode),dz.LOG_FORCE)
            dz.log('systemName: ' .. tostring(systemName),dz.LOG_FORCE)
            dz.log('cmdString: ' .. tostring(cmdString),dz.LOG_FORCE)

            -- added pvk 
            local system        = sites2Check[systemName]
            --dz.log('PING1: Device: ' .. systemName .. ' Returncode' ..  tostring(returnCode))
            if system.pvkswitch then
                if returnCode == 0 then -- node aan
                    dz.devices(system.pvkswitch).switchOn().checkFirst()
                    dz.log('PING_on: Device: ' .. systemName .. ' Returncode: ' .. tostring(returnCode))
                else    -- node uit
                    dz.devices(system.pvkswitch).switchOff().checkFirst()
                    dz.log('PING_off: Device: ' .. systemName .. ' Returncode: ' .. tostring(returnCode))
                end    
            end
            -- end pvk
        
            if returnCode ~= 0 then
                t.consecutiveFailCount = ( t.consecutiveFailCount or 0 ) + 1
                t.isMessageSend = report(systemName,returnCode,cmdString,t.consecutiveFailCount,t.isMessageSend) 
            else
                t.consecutiveFailCount = 0
                t.isMessageSend = false
            end
        end

        local function fileExists(filename)
            local file = io.open(filename, "r")
            if file ~= nil then io.close(file) return true end
            return false 
        end
        
        local function getPersistentDataFileName()
            return globalvariables['script_path'] .. "data/__data_" .. info.scriptName .. ".lua"
        end
        
        local function invalidPersistentDatafile()
            local dataFile  = getPersistentDataFileName()
            if not fileExists(dataFile) then
                logWrite("initSites2Check() must be called. (file does not exist)" )
                return true
            end
            
            if fileExists(_G.generatedScriptsFolderPath ..  info.scriptName .. ".lua") then
                script = _G.generatedScriptsFolderPath ..  info.scriptName .. ".lua"
            else
                script = globalvariables['script_path'] .. "scripts/" .. info.scriptName .. ".lua"
            end

            if (select(1,osCommand("find " .. script .." -newer " .. dataFile))):find(script) then
                logWrite("initSites2Check() must be called. (script newer than datafile)" )
                return true
            end
            return false
        end

        local function clearMessageSend()
            logWrite("isMessageSend will be cleared")
            for systemName,attributes in pairs(sites2Check) do
                if attributes.address ~= nil then         -- defaults does not have an address
                    if attributes.port ~= nil then  -- Ports defined so we need to call nmap (ping cannot do this)
                        for i=1,#attributes.port do
                            attributes.port.isMessageSend = false
                            dz.data.sites2Check[systemName].port.isMessageSend = false
                        end    
                    else 
                        attributes.isMessageSend = false
                        dz.data.sites2Check[systemName].isMessageSend = false
                    end
                end
            end
            return true
        end
        
        local function isExecutionTimeTooLong(startedAt)
            return (os.clock() - startedAt) > defaults.maxExecutionTime 
        end
                
        -- main 
        local startedAt  = os.clock()
        if messageSendClearRequired then clearMessageSend() end
        if invalidPersistentDatafile() then initSites2Check() end
               
        for systemName,attributes in pairs(sites2Check) do
            if attributes.address ~= nil then         -- defaults does not have an address
                local cmdString 
                local message
                local system        = dz.data.sites2Check[systemName]
                if isExecutionTimeTooLong(startedAt) then logWrite("break main") break end
                if attributes.port ~= nil then  -- Ports defined so we need to call nmap (ping cannot do this)
                    for i=1,#attributes.port do
                        logWrite("ports defined; Checking " .. attributes.address .. " for port " .. attributes.port[i])
                        cmdString = "nmap " .. attributes.address .. " -p " .. attributes.port[i] .. " | grep open"
                        evalReturnCode(system[attributes.port[i]],select(2,osCommand(cmdString)),systemName,cmdString)
                        if isExecutionTimeTooLong(startedAt) then break end
                    end    
                else -- Simple ping command
                    local pingWaitTime = attributes.pingWaitTime or defaults.pingWaitTime
                    logWrite("Checking " .. attributes.address )
                    cmdString = "ping -c1 -w" .. pingWaitTime .. " >/dev/null " .. attributes.address
                    evalReturnCode(system,select(2,osCommand(cmdString)),systemName,cmdString)
                    if isExecutionTimeTooLong(startedAt) then break end
                end
            end
        end
    end
}