LUA / checkAll

Essaie de trouver des soucis d'ID dans les scénarios et VD



--[[
%% autostart
--]]
 
 
-- ===========================================
-- ANALYSEUR DE CODE pour relevé les problèmes d'ID
-- ===========================================
 
local receivePush = true		-- true pour recevoir une notification en cas de problème éventuel
local receiveEmail = true	-- true pour recevoir le rapport d'exécution par email
local checkDisabled = false -- true pour vérifier aussi les scènes et VD désactivés
 
-- Tableau contenant les id des smartphones devant recevoir une notification en cas de problème
-- par défaut, ce script envoi un message à tout les smartphones
local portables_for_notification = {}
 
-- Tableau contenant les id des utilisateurs devant recevoir un e-mail résumant
-- l'analyse
local users_for_mail = {2}
 
-- Va executer cette scène toutes les X heures
-- mettre cette ligne en commentaire pour désactiver
--local run_every = 24
 
-- Tableau permettant d'ajuster les warnings à ignorer
-- Exemple
-- {id=141}  -- tous les warning du VD ou de la scène 141 seront ignoré
-- {id=141, field="id"}  -- tous les warning du VD ou de la scène 141 seront ignoré s'il concerne une variable "id"
-- {id=141, field="id", value="2000"}  -- tous les warning du VD ou de la scène 141 seront ignoré s'il concerne une variable "id" et la valeur est 2000
local ignored = {
	{id=163}
}
 
-- Tableau contenant les noms de variables habituellement utilisés pour identifiant
-- un ID fibaro
local mostUseVariables = {"id", "deviceid", "device_id", "module", "identifiant"}
 
-- Si vous avez un tableau d'ID a tester
local ids = {
    PLUIE = 104, VACSCOLAIRE = 77, DEVANT_LA_PORTE = 48, DETECTEUR_PORTE = 53, PLAFONNIER_ENTREE = 73,
    DETECTEUR_GARAGE = 67, OPENKAROTZ = 140, NOTIFICATION_CENTER = 105, PLAFONNIER_LOCAL = 24,
    PLAFONNIER_NOLAN = 28, PLAFONNIER_NORA = 26, PLAFONNIER_KENDRA = 30, POELE = 10, 
    ROMBA = 36, TV = 35, VD_FREE_PLAYER = 90, SECHE_SERVIETTE = 58, TEMPERATURE_SALON = 98, 
}
 
 
--[[ ===========================================
NE PAS TOUCHER
--=========================================== ]]
-- toolbox c.f. https://gea.piccand.me/doku.php/minifier
local mailmsg = ""
if not tools then tools={log=function(a,b,c)a=tools.tostring(a) mailmsg = mailmsg .. a .. "\n" for d,e in string.gmatch(a,"(#spaces(%d+)#)")do local f=""for g=1,e do f=f.."."end;a=string.gsub(a,d,"<span style=\"color:black;\">"..f.."</span>")end;if debug or c then fibaro:debug("<span style=\"color:"..(b or"white")..";\">"..a.."</span>")end end,error=function(a,b)tools.log(a,b or"red",true)end,warning=function(a,b)tools.log(a,b or"orange",true)end,info=function(a,b)tools.log(a,b or"white",true)end,debug=function(a,b)tools.log(a,b or"gray",false)end,tostring=function(h)if type(h)=="boolean"then if h then return"true"else return"false"end elseif type(h)=="table"then if json then return json.encode(h)else return"table found"end else return tostring(h)end end,split=function(i,j)local j,k=j or":",{}local l=string.format("([^%s]+)",j)i:gsub(l,function(m)k[#k+1]=m end)return k end,trim=function(n)return n:gsub("^%s*(.-)%s*$","%1")end,deep_print=function(o)for g,p in pairs(o)do if type(p)=="table"then deep_print(p)else print(g,p)end end end,getStringTime=function(q)if q then return os.date("%H:%M:%S")end;return os.date("%H:%M")end,toTime=function(r)local s,t=string.match(r,"(%d+):(%d+)")local u=os.date("*t")local v=os.time{year=u.year,month=u.month,day=u.day,hour=s,min=t,sec=0}if v<os.time()then v=os.time{year=u.year,month=u.month,day=u.day+1,hour=s,min=t,sec=0}end;return v end,getStringDate=function()return os.date("%d/%m/%Y")end,isNil=function(w)return type(w)=="nil"end,isNotNil=function(w)return not tools.isNil(w)end}end
 
local version = "3.6"
local scene_id = nil
local scene_name = nil
 
 
 
--[[ ===========================================
      Cherche si l'ID existe sur la HC2 (device ou scène)
	paramètres :
		device: l'identifiant trouvé
		message : le message a afficher
		color : la couleur du message
      ===========================================]]
function exist(device, message, color)
  if (tools.isNil(__fibaro_get_device(tonumber(device))) and tools.isNil(__fibaro_get_scene(tonumber(device)))) then
    tools.log(message, color or "red", true)
    return false
  end
  return true
end
 
--[[ ===========================================
     Vérifie s'il faut afficher un warning ou non
	 paramètres :
		id = id du module ou de la scene
		field : nom du code à tester ou id du module
		value : valeur a ignorer
      ===========================================]]
function shouldCheck(id, field, value)
  for k,v in ipairs(ignored) do
    if (v.id == id and (tools.isNil(v.field) or tools.isNil(field) or v.field == field) and (tools.isNil(v.value) or tools.isNil(value) or v.value==value) ) then
      return false
    end
  end
  return true
end
 
--[[ ===========================================
      Tente de trouver la valeur d'une variable 
	paramètres :
		code : le code a analyser
		device : la variable ou l'ID à traiter
      ===========================================]]
function findId(code, variable, posInCode)
  if (tonumber(variable) ~= nil) then return variable, true end
  local position = 1
  local id = nil
  for deviceid in string.gmatch(code, variable.."[ ]?=[ ]?(%d+)") do
    start, stop = string.find(code, variable.."[ ]?=[ ]?(%d+)", position)
    position = stop + 1
    if (start <= posInCode) then
      id = deviceid
    end
  end
  if (tonumber(id) ~= nil) then return id, false end
end
 
--[[ ===========================================
      Cherche une erreur dans le header d'une scène
	paramètres :
		scene : la scène à analyser
      ===========================================]]
function checkheader(scene)
  local problem = false
  local firstonly = true
  for header in string.gmatch(scene.lua, "%-%-%[%[(.-)%-%-%]%]") do
    if (firstonly) then
      for w, device in string.gmatch(header, "(%a+) (%d+)") do
        if (w ~= "CentralSceneEvent") then
        	if (not(exist(device, "      --> contient une référence erronée dans l'entête : ID " .. device))) then
          		problem = true
        	end
        end
      end
      firstonly = false
    end
  end
  return problem
end
 
 
--[[ ===========================================
      Cherche une erreur dans le code sur les appels : fibaro:XXXXXX
	paramètres :
		code : le code a analyser
		part : la partie analysée (pour le log)
      ===========================================]]
function checkfibaro(code, part, device_id)
  local problem = false
  for func, content, _ in string.gmatch(code, "fibaro:(.-)%((.-)%)") do
    func = string.lower(func)
    local position = 0
    if (func ~= "sleep" and func ~= "setglobal" and func ~= "debug") then
      for variable in string.gmatch(tools.trim(content), "(.-),.*") do
        local pos, stop = string.find(string.lower(code), string.lower("fibaro:"..func.."("..content..")"), position, true)
        position = stop
        local id, direct = findId(code, variable, pos)
        if (id) then
          if (not shouldCheck(device_id, variable, id)) then
            -- avertissement inutile
          else          
            local message =  "      " .. part .. " --> contient une référence erronée dans fibaro:"..func .. "(" .. content..") <span style=\"color:gray\">[ignored = {id="..device_id..", field=\""..id.."\"}]</span>"
            local color = "red"
            if (not direct) then
              message = "      " .. part .. " --> contient une référence probablement erronée dans fibaro:"..func .. "(" .. content..") " .. variable .. "=" .. id .. " ? <span style=\"color:gray\">[ignored = {id="..device_id..", field=\""..variable.."\", value=\""..id.."\"}]</span>" 
              color = "orange"
            end
            if (not(exist(id, message, color))) then
              problem = true
            end
          end
        end
      end
    end
  end
  return problem
end
 
--[[ ===========================================
      Cherche une erreur dans le code sur les appels : id, deviceid
	voir mostUseVariables = {}
	paramètres :
		code : le code a analyser
		part : la partie analysée (pour le log)
      ===========================================]]
function checkVariables(code, part, device_id)
  local problem = false
  local position = 0
  if (mostUseVariables) then
    for _,v in ipairs(mostUseVariables) do
      for deviceid in string.gmatch(code, "%f[%a]"..v.."%f[%A][ ]?=[ ]?(%d+)") do
        if shouldCheck(device_id, v, deviceid) then
          local message = " found " .. v.."=" .. deviceid .. " in ".. part.. " and " .. deviceid .. " is not a device, please check <span style=\"color:gray\">[ignored = {id="..device_id..", field=\""..v.."\", value=\""..deviceid.."\"}]</span>" 
          if (not(exist(deviceid, message, "orange"))) then
            problem = true
          end	
        end
      end
    end
  end
  return problem
end
 
--[[ ===========================================
      Analyse les scénarios
      ===========================================]]
function findErrorsInScenes() 
  local problem = false
  tools.log("--------------------------------------------", "lightblue", true)
  tools.log("Checking scenes ... ", "lightblue", true)
  tools.log("--------------------------------------------", "lightblue", true)
  local scenes = api.get("/scenes")
  for k,v in ipairs(scenes) do
    local scene = __fibaro_get_scene(v.id)
    if (tools.isNotNil(scene) and (scene.runConfig ~= "DISABLED" or checkDisabled)) then
      tools.log("checking scene : [" .. scene.id .. "] " .. scene.name, "gray", true)
      if (string.find(scene.lua, "GEA.add")) then
        table.insert(ignored, {id=scene.id, field="id", value="0"})
      end  
      if (string.find(scene.lua, "findErrorsInScenes()")) then
        scene_id = scene.id
        scene_name = scene.name
        table.insert(ignored, {id=scene.id})
      end
      if (checkheader(scene)) then problem = true end
      if (checkfibaro(scene.lua, "code", scene.id)) then problem = true end
      if (checkVariables(scene.lua, "code", scene.id)) then problem = true end
    end
  end
  tools.log("... done ", "lightblue", true)
  return problem
end
 
--[[ ===========================================
      Analyse les virtual devices
      ===========================================]]
function findErrorsInVirtual() 
  local problem = false
  tools.log("--------------------------------------------", "lightblue", true)
  tools.log("Checking virtual devices ... ", "lightblue", true)
  tools.log("--------------------------------------------", "lightblue", true)
  local filter = {type="virtual_device", enabled = true}
  if (checkDisabled) then
    filter = {type="virtual_device"}
  end
  local ids = fibaro:getDevicesId(filter)
  for k,v in ipairs(ids) do
    vd = __fibaro_get_device(v)
    if (tools.isNotNil(vd)) then
      tools.log("checking vd : [" .. vd.id .. "] " .. vd.name, "gray", true)
      if (tools.isNotNil(vd.properties.mainLoop)) then 
        tools.log(" --> checking mainLoop", "gray")
        if (checkfibaro(vd.properties.mainLoop, "mainloop", vd.id)) then problem = true end
        if (checkVariables(vd.properties.mainLoop, "mainloop", vd.id)) then problem = true end
        for l,w in ipairs(vd.properties.rows) do
          if (w.type=="button") then
            for m,x in ipairs(w.elements) do
              if (x.lua) then
                tools.log(" --> checking " .. x.caption .. " ["..x.name.."]", "gray")
                if (checkfibaro(x.msg, x.caption .. " ["..x.name.."]", vd.id) ) then problem = true end
                if (checkVariables(x.msg, x.caption .. " ["..x.name.."]", vd.id) ) then problem = true end
              end
            end
          end
        end
      end
    end
  end
  tools.log("... done ", "lightblue", true)
  return problem
end
 
--[[ ===========================================
      Analyse le tableau d'ID
      ===========================================]]
function findErrorsInTable()
  if (type(ids) ~= "table") then return false end
  tools.log("--------------------------------------------", "lightblue", true)
  tools.log("Checking table [ids] ... ", "lightblue", true)
  tools.log("--------------------------------------------", "lightblue", true)
  local problem = false
  for k,v in pairs(ids) do
    if (not(exist(v, "      --> " .. k .. " contient une référence erronée le table : " .. v))) then
      problem = true
    end
  end
  tools.log("... done ", "lightblue", true)
  return problem
end
 
--[[ ===========================================
   	  Lancement du code
   	===========================================]]
function run()
  mailmsg = ""
  tools.log("======================", "lightblue", true)
  tools.log(" Starting checker v."..version, "lightblue", true)
  tools.log(" Cet outil ESSAIE de trouver des erreurs d'ID dans le code LUA", "lightblue", true)
  tools.log(" En aucun cas il est exaustif et peux indiquer des warning qui n'ont pas de raison d'être", "lightblue", true)
  tools.log(" A vous d'ajuster le tableau \"ignored\"", "lightblue", true)
  tools.log("======================", "lightblue", true)
  tools.log("code couleur : ", "gray", true)
  tools.log("#spaces5#orange = a vérifier ", "orange", true)
  tools.log("#spaces5#rouge   = erreur ", "red", true)
  tools.log("", "gray", true)
 
  local message = ""
  if (findErrorsInScenes()) then
    message = message .. " Error(s) in scene"
  end
  if (findErrorsInVirtual()) then
    message = message .. " Error(s) in VD"
  end
  if (findErrorsInTable()) then
    message = message .. " Error(s) in Table"
  end
  if (string.len(message)>0 and receivePush) then
    local ids = portables_for_notification or fibaro:getDevicesId({type="iOS_device", enabled=true})
    for _,v in ipairs(ids) do
      tools.log("Message d'avertissement envoyé à " .. fibaro:getName(v), "lightblue", true)
      fibaro:call(v, "sendPush", "["..v .. "] : " .. message)
    end
    HomeCenter.PopupService.publish({
        title = "Scénario " .. scene_name .. " v." .. version,
        subtitle = os.date("%I:%M:%S %p | %B %d, %Y"),
        contentTitle = "Problème éventuel veuillez vérifier le scénario",
        contentBody = message,
        img = "../img/topDashboard/warnings.png",
        type = "Warning",
        buttons = {{ caption = "Quitter", sceneId = 0 }}
      })  
    fibaro:setGlobal("Notification", message)
    fibaro:call(105, "pressButton", 2)
  end
  tools.log("--- Terminé ---", "lightblue", true)
  if (type(users_for_mail) ~= "nil" and receiveEmail) then
    for _,v in ipairs(users_for_mail) do
      fibaro:call(v, "sendEmail", "HC2 Scénario " .. scene_name .. " v." .. version, mailmsg)
      --fibaro:setGlobal("Pushbullet", mailmsg)
    end
  end
end
 
function boucle()
  setTimeout(function() boucle() end, run_every*60*60*1000)
  run()
end
 
if (tools.isNotNil(run_every)) then
  boucle()
  tools.info("will restart every " .. run_every .. " hours", "lightblue")
else
  run()
end