new version 20191031 !
Have Fun !
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
20191031 Fixed bug ( t.consecutiveFailCount not initialized)
]]--
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 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
}