Watchdog
Version 1.3
Voici une scène permettant de surveiller le fonctionnement des Scènes et Main Loop de Modules Virtuels sur Home Center 2 en version 4.x.
Pour ce faire, les messages de la fenêtre de Debug sont analysés. De plus, pour les scènes uniquement, le nombre d'instances est compté.
En cas de problème détecté, la scène ou le virtual device considéré est automatiquement redémarré, et une notification peut être envoyée.
Copier/coller le script LUA suivant dans une nouvelle scène :
--[[
%% autostart
%% properties
%% globals
--]]
--------------------------------------------------
-- Scene : Watchdog
-- Author : Lazer
-- Version : 1.3
-- Date : June 2017
--------------------------------------------------
-- User variables
local intervalle = 60
local delay = 15*60
local watchdog = {
}
local userID = {} -- Email
local smartphoneID = {} -- Push
local sms = {
["VD_ID"] = 0, -- Virtual Device ID
["VD_Button"] = "1", -- Virtual Device Button
["VG_Name"] = "SMS" -- Global Variable Name
}
local debug = false
--
-- Message function
--
function Message(color, message)
if color and color ~= "" then
fibaro:debug('<span style="color:'..color..';">'..(message or '<nil>')..'</span>')
else
fibaro:debug(message or '<nil>')
end
end
--
-- Notification function
--
function Notification(message, param)
local message = message or "<vide>"
Message("yellow", "Notification : "..message)
if param then
for _, notif in ipairs(param) do
if debug then
Message("grey", notif)
end
-- Envoi Push
if notif == "push" and smartphoneID then
for _, id in ipairs(smartphoneID) do
if debug then
Message("grey", "Send Push smartphone ID : "..id)
end
fibaro:call(id, "sendPush", message)
end
-- Envoi Email
elseif notif == "email" and userID then
for _, id in ipairs(userID) do
if debug then
Message("grey", "Send Email user ID : "..id)
end
fibaro:call(id, "sendEmail", "HC2 Watchdog", message)
end
-- Envoi SMS
elseif notif == "sms" and sms then
if debug then
Message("grey", "Send SMS : VD_ID="..(sms["VD_ID"] or 0).." VD_Button="..(sms["VD_Button"] or "0").." VG_Name="..(sms["VG_Name"] or ""))
end
fibaro:setGlobal(sms["VG_Name"], message)
if sms["VD_ID"] and tonumber(sms["VD_ID"])>0 and sms["VD_Button"] and tonumber(sms["VD_Button"])>0 then
fibaro:call(sms["VD_ID"], "pressButton", sms["VD_Button"])
end
end
end
else
Message("orange", "Warning : no notification options given")
end
end
--
-- Restart function
--
function Restart(type, id, restart, notification, reason)
Message("blue", 'Restart '..type..'('..id..')')
-- Prepare API URL
local getURL = ""
local putURL = ""
if type:lower() == "scene" then
getURL = 'http://127.0.0.1:11111/api/scenes/'..id
putURL = 'http://127.0.0.1:11111/api/scenes/'..id
elseif type:lower() == "vd" then
getURL = 'http://127.0.0.1:11111/api/virtualDevices/'..id
putURL = 'http://127.0.0.1:11111/api/virtualDevices/'..id
end
-- Load VD/Scene
local httpClient = net.HTTPClient()
httpClient:request(getURL, {
success = function(response)
if response.status == 200 then
local jsonTable = json.decode(response.data)
local name = jsonTable.name or ""
if restart and restart == true then
-- Add new line at end of scene lua code
if type:lower() == "scene" and jsonTable.lua then
jsonTable.lua = jsonTable.lua .. "\n"
response.data = json.encode(jsonTable)
end
-- Save VD/Scene
httpClient:request(putURL, {
success = function(response)
if response.status == 200 then
Message("green", type.."("..id..") successfully restarted")
Notification('Watchdog : '..type..' « '..(name or "")..' » ('..id..") a été redémarré : "..(reason or "???"), notification)
else
Message("red", type.."("..id..") Error : status="..tostring(response.status))
Notification('Watchdog : '..type..' « '..(name or "")..' » ('..id..") n'a pas pu être redémarré : "..(reason or "???"), notification)
end
end,
error = function(err)
Message("red", type.."("..id..") Error : "..err)
Notification('Watchdog : '..type..' « '..(name or "")..' » ('..id..") n'a pas pu être redémarré : "..(reason or "???"), notification)
end,
options = {
method = 'PUT',
-- headers = {
-- ["content-type"] = 'application/x-www-form-urlencoded;'
-- },
data = response.data
}
})
else
Notification('Watchdog : '..type..' « '..(name or "")..' » ('..id..") doit être redémarré manuellement : "..(reason or "???"), notification)
end
else
Message("red", type.."("..id..") Error : status="..tostring(response.status))
Notification('Watchdog : '..type..' ('..id..") n'a pas pu être redémarré : "..(reason or "???"), notification)
end
end,
error = function(err)
Message("red", type.."("..id..") Error : "..err)
Notification('Watchdog : '..type..' ('..id..") n'a pas pu être redémarré : "..(reason or "???"), notification)
end,
options = {
method = 'GET'
}
})
end -- function
--
-- Check function
--
function Check(interval)
Message(nil, "Check")
-- Browse VD/Scene list
local httpClient = net.HTTPClient()
local elements = #watchdog
for i = 1, elements do
-- Initialization
local countscene_found = false
if debug then
Message(nil, "Check : type="..watchdog[i].type.." id="..watchdog[i].id)
end
-- Check number of running scene instances
if watchdog[i].type:lower() == "scene" and watchdog[i].count and watchdog[i].count > 0 then
local countScenes = fibaro:countScenes(watchdog[i].id)
if countScenes < watchdog[i].count then
Message("orange", watchdog[i].type..'('..watchdog[i].id..') '..countScenes..' running instance')
countscene_found = true
Restart(watchdog[i].type, watchdog[i].id, watchdog[i].restart, watchdog[i].notification, countScenes..' instance')
elseif debug then
Message("green", watchdog[i].type..'('..watchdog[i].id..') '..countScenes.." running instance")
end
end
if countscene_found == false then -- Do not enter this loop if scene has already been restarted
-- Prepare API URL
local getURL = ""
if watchdog[i].type:lower() == "scene" then
getURL = "http://127.0.0.1:11111/api/scenes/"..watchdog[i].id.."/debugMessages"
elseif watchdog[i].type:lower() == "vd" then
getURL = "http://127.0.0.1:11111/api/virtualDevices/"..watchdog[i].id.."/debugMessages/0"
else
Message("red", "Error : unknown type value")
end
if getURL ~= "" then
if debug then
Message("grey", getURL)
end
-- Load VD/Scene debug messages
httpClient:request(getURL, {
success = function(response)
if response.status == 200 then
if response.data and response.data ~= "" then
local jsonTable = json.decode(response.data)
local current_timestamp = os.time()
local oldest_timestamp = current_timestamp
local match_found = false
local no_match_found = false
local reason = ""
-- Reverse browsing of debug messages
for j = #jsonTable, 1, -1 do
oldest_timestamp = jsonTable[j].timestamp
-- Check if debug message match lookup string within allowed interval
if watchdog[i].match.text and watchdog[i].match.text ~= "" and watchdog[i].match.interval > 0 and jsonTable[j].txt:match(watchdog[i].match.text) then
if jsonTable[j].timestamp > current_timestamp - watchdog[i].match.interval then
if debug then
Message("green", watchdog[i].type..'('..watchdog[i].id..') Found string "'..watchdog[i].match.text..'"')
end
match_found = true
end
end
-- Check if debug message match forbidden string
if watchdog[i].no_match.text and watchdog[i].no_match.text ~= "" and jsonTable[j].txt:match(watchdog[i].no_match.text) then
Message("orange", watchdog[i].type..'('..watchdog[i].id..') Found string "'..watchdog[i].no_match.text..'"')
no_match_found = true
reason = os.date('%H:%M:%S', (jsonTable[j].timestamp or 0)) .. " " .. jsonTable[j].type .. " : " .. jsonTable[j].txt
break
end
if watchdog[i].no_match.type and watchdog[i].no_match.type ~= "" and jsonTable[j].type == watchdog[i].no_match.type then
Message("orange", watchdog[i].type..'('..watchdog[i].id..') Found type "'..watchdog[i].no_match.type..'"')
no_match_found = true
reason = os.date('%H:%M:%S', (jsonTable[j].timestamp or 0)) .. " " .. jsonTable[j].type .. " : " .. jsonTable[j].txt
break
end
end -- for
if debug and oldest_timestamp > current_timestamp - watchdog[i].match.interval then
Message("grey", watchdog[i].type..'('..watchdog[i].id..') oldest debug timestamp more recent than interval')
end
-- Restart VD/Scene
if watchdog[i].match.text and watchdog[i].match.text ~= "" and watchdog[i].match.interval > 0 and match_found == false and oldest_timestamp < current_timestamp - watchdog[i].match.interval then
Message("orange", watchdog[i].type..'('..watchdog[i].id..') String "'..watchdog[i].match.text..'" not found')
reason = 'Chaine « ' .. watchdog[i].match.text .. ' » non trouvée'
if #jsonTable > 0 then
reason = reason .. ', dernier message : ' .. os.date('%H:%M:%S', (jsonTable[#jsonTable].timestamp or 0)) .. ' « ' .. (jsonTable[#jsonTable].txt or "<nil>") .. ' »'
end
Restart(watchdog[i].type, watchdog[i].id, watchdog[i].restart, watchdog[i].notification, reason)
--if watchdog[i].no_match.text and watchdog[i].no_match.text ~= "" and no_match_found == true then
elseif no_match_found == true then
Restart(watchdog[i].type, watchdog[i].id, watchdog[i].restart, watchdog[i].notification, reason)
end
else
Message("red", "Error : empty response")
end
else
Message("red", "Error : status=" .. tostring(response.status))
end
end,
error = function(err)
Message("red", 'Error : ' .. err)
end,
options = {
method = 'GET'
}
})
end
end
end -- for
-- Wait
if interval and interval > 0 then
setTimeout(function() Check(interval) end, interval*1000)
end
end -- function
--
-- Main loop
--
local trigger = fibaro:getSourceTrigger()
if trigger["type"] == "autostart" then
Message(nil, "Watchdog instance autostart")
-- Check function call delayed to prevent killing all other scenes not already started right after HC2 boot
setTimeout(function() Check(intervalle) end, delay*1000)
else
Message(nil, "Watchdog instance manual launch")
-- Call Check function
Check(nil)
end
.
Le paramétrage du script s'effectue dans les quelques lignes situées sous le commentaire "User variables" :
intervalle : durée entre 2 vérifications
delay : délai avant la première vérification. En effet, cette scène ayant la propriété autostart afin de démarrer automatiquement au boot de la box, le risque est de démarrer avant les autres Scène/VD, et de forcer un redémarrage de ceux-ci alors qu'ils n'ont pas encore effectivement démarré. Ce délai laisse donc aux autres Scène/VD le temps de démarrer et de s'initialiser proprement.
watchdog : tableau dont chaque ligne représente une Scène ou un Virtual Device à monitorer :
type : "Scene" ou "VD"
id : valeur numérique représentant l'ID de la Scène ou VD à monitorer
match : texte qui doit être trouvé pendant un certain laps de temps afin de confirmer le bon fonctionnement du VD/Scène. Cela correspond typiquement à un message affiché cycliquement à intervalle régulier. Les 2 champs suivants doivent être renseignés pour que la condition soit prise en compte (condition ET) :
text : chaine de caractères à trouver
interval : durée en secondes
no_match : texte qui ne doit pas être trouvé, sous peine de considérer le VD/Scène comme planté. Cela correspond typiquement à un message d'erreur LUA qui entraine le plantage du script. A noter que la condition no_match à priorité sur la condition match, c'est à dire que si le texte recherché par no_match est détecté, le VD/Scène sera redémarrer même si le texte recherché par match a été détecté. L'un ou l'autre des 2 champs suivants peuvent être renseignés pour que la condition soit prise en compte (condition OU) :
text : chaine de caractères à trouver
type : "ERROR" correspond aux messages affichés en rouge dans la fenêtre de Debug de l'interface HC2. A noter que jusqu'en v4.056, dans une scène une erreur LUA affichait le message en rouge avec le type "ERROR", tandis que depuis les Beta v4.057 et v4.058, cette même erreur s'affiche en blanc sans le type ERROR, par conséquent ce test ne fonctionne plus. En revanche, aucun changement de mode de fonctionnement concernant les Virtual Devices.
count : valeur valable pour les scènes uniquement, et indiquant le nombre minimal d'instances qui doivent fonctionner pour confirmer le bon fonctionnement de la scène. Cela correspond typiquement à l'instance de type boucle infinie lancée en autostart.
restart : true ou false afin de redémarrer le VD/Scène concerné, ou seulement envoyer une notification signalant qu'il faut le redémarrer manuellement.
notification : liste séparée par des virgules des moyens de notifications à employer.
userID : liste séparée par des virgules des ID des utilisateurs qui doivent recevoir des notifications par email (le mail est celui configuré pour chaque utilisateur dans le panneau de contrôle d'accès)
smartphoneID : liste séparée par des virgules des ID des smartphones qui doivent recevoir des notifications push (à récupérer dans le toolkit de Krikroff ou via l'API : /api/iosDevices)
sms : si vous avez une passerelle SMS sous Android avec SMS Gateway (ou équivalent) pilotée par un module virtuel, il faut renseigner ici les informations nécessaires :
VD_ID : ID du module virtuel
VD_Button : ID du bouton du module virtuel
VG_Name : nom de la variable globale
debug : true ou false afin d'activer l'affichage étendu dans la fenêtre de débugage de la scène.
Exemple de paramètres :
-- User variables
local intervalle = 60
local delay = 15*60
local watchdog = {
{type = "Scene", id = 1, match = {text="", interval=0}, no_match = {text=""}, count=1, restart=true, notification = {"push", "email", "sms"}}, -- Présence Lazer
{type = "Scene", id = 2, match = {text="Last run", interval=2*60}, no_match = {text=""}, count=1, restart=true, notification = {"push", "email", "sms"}}, -- DomoCharts
{type = "Scene", id = 3, match = {text="Durée des traitements", interval=11*60}, no_match = {text=""}, count=1, restart=true, notification = {"push", "email", "sms"}}, -- GEA
{type = "VD", id = 1, match = {text="", interval=0}, no_match = {text="", type="ERROR"}, restart=true, notification = {"push", "email", "sms"}}, -- Surveillance Station
{type = "VD", id = 2, match = {text="", interval=0}, no_match = {text="", type="ERROR"}, restart=true, notification = {"push", "email", "sms"}}, -- Clock Sync
{type = "VD", id = 3, match = {text="", interval=0}, no_match = {text="", type="ERROR"}, restart=true, notification = {"push", "email", "sms"}}, -- My Batteries
{type = "VD", id = 4, match = {text="", interval=0}, no_match = {text="", type="ERROR"}, restart=true, notification = {"push", "email", "sms"}}, -- Evénements
{type = "VD", id = 5, match = {text="", interval=0}, no_match = {text="", type="ERROR"}, restart=true, notification = {"push", "email", "sms"}}, -- Network Monitor
{type = "VD", id = 6, match = {text="", interval=0}, no_match = {text="", type="ERROR"}, restart=true, notification = {"push", "email", "sms"}}, -- GEA Alarm
{type = "VD", id = 7, match = {text=" ", interval=30}, no_match = {text="", type="ERROR"}, restart=true, notification = {"push", "email", "sms"}}, -- Sonos Player (Tk.isTraceEnabled = true)
{type = "VD", id = 8, match = {text="Start main process", interval=31*60}, no_match = {text="", type="ERROR"}, restart=true, notification = {"push", "email", "sms"}} -- Freebox Serveur
}
local userID = {1} -- Email
local smartphoneID = {1, 2} -- Push
local sms = {
["VD_ID"] = 99, -- Virtual Device ID
["VD_Button"] = "1", -- Virtual Device Button
["VG_Name"] = "SMS" -- Global Variable Name
}
local debug = false