Plans in dzVents

Easy to use, 100% Lua-based event scripting framework.

Moderator: leecollings

Post Reply
Eoreh
Posts: 65
Joined: Tuesday 13 October 2015 13:50
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Poland
Contact:

Plans in dzVents

Post by Eoreh »

EDIT: This version is no longer supported. You can download new version with new sytex and in module. from here
viewtopic.php?f=59&t=21914


Plans in dzVents version 2.5
plans_2_5.txt
(19.44 KiB) Downloaded 57 times
I wrote a script that easily allows you to add plans, and by the way they are kept in one place, in this script.
In addition, thanks to dzVents, the script can do much more than traditional plans.
Now You can trigger not only on time but also on device and much more. See examples in script

The script is with comments and in English (although is not my native, I apologizes for any linguistic mistakes).

Functionality of version 2.5.
- support for switches and selectors (for now).
- 5 functions: switchOn, switchOff, dimTo, deviceOn, deviceOff
- 3 special action: forSec, repeatAfterSec, statusDevice
- 5 properties: name, description, dontLog, notify, fun

For syntax check inline doc and example plan on begin of script

(note: complete script is in file to download plans_2_5.txt)

A very important warning.
Only one script can be active at a time.
If you have more scripts with "Plans" only the last one in the list is still being processed by dzVents. If it results from it that an event is activated, it is executed as many times as the number of copies of the script you have (no matter if they have the same plans).
I am looking for a cause and solution to the problem.

But of course, the single works well, as reported by a colleague testing in his home automation. (Thanks again, Tom for inspirations to write this scrip and tests).

Plans example:

Code: Select all


local plans = {
         {'test11',  switchOn  = 'at 11:00'}, --  switch test11 ON  when time is 11:00
         {'test12',  switchOn  ={'at 12:00','at nighttime'}, switchOff='at daytime'}, --  switch ON test12 in to times conditions. also have function switch OFF 
         {'test13',  switchOff ={'at 13:00','at 13:10'}, fun='checkFirst().aferSec(5).forSec(10)'}, -- switch test13 OFF with add metods checkFirst().... when times conditions.
         {'test14',  switchOff ='at 14:00', description=' description for write log'}, -- ... with write to log ' description for write log'
         {'test16',  switchOff ='at sunset ', description=' description for write log and notify', notify=true}, -- ... with notify 
         {'test17',  switchOff ='10 minutes before sunrise', description=' description for only notify', notify=true, dontLog=true}, -- ... with notify without write to log 
         
         {'test20',   dimTo  = {10,'at daytime'}}, --  switch selector test21  to lvl = 10 (Level1) when time is 21:00
         {'test20',   dimTo  = {'Level2','at 22:00', 'at 22:30'}}, --  switch selector test20 to lvl = Level2 when time is 22:00 and 22;30
         {'test20',   dimTo  = {'Level3','at 23:00'}, fun='afterSec(5)'}, --  switch selector test20 to lvl = Level3 when time is 23:00  after 5 second from trigger
         {'test20',   dimTo  = {'Off','at nighttime'}, fun='afterSec(1)', notify=true}, --  switch selector test20 to lvl = Off when time is 00:00  after 5 second from trigger  

         {'test31',  deviceOn  = 'testA'},  --  switch test31 ON  when device testA trigger 
         {'test32',  deviceOn  ={'testB','Locked'}}, --  switch test32 ON  when device testB trigger  and its state = 'Locked'
         {'test33',  deviceOn  ={'testC','Locked','at daytime'}}, --  switch test33  ON  when device testC trigger  and its state = 'Locked' and if is daytime
         {'test34',  deviceOff ='testD', fun='checkFirst().aferSec(5).forSec(10)'}, -- switch test34 OFF with add metods checkFirst().... when device testD trigger 
         {'test35',  deviceOff ='testE', description=' text for write log'},  -- ... with write to log text =' text for write log'
         {'test36',  deviceOff ='testF', description=' description for write log and notify', notify=true}, -- ... with write to log text =' text for write log' and send notify PUSHOVER
         {'test37',  deviceOff ='testG', notify=true, dontLog=true}, -- .., without write to log  and send notify PUSHOVER
         {'test38',  deviceOff ='testH', statusDevice={'testS','Open'}}, -- switch test38 OFF when trigger device testH and device testS has state Open 
         {'test39',  deviceOff ={'testI','at daytime'}, statusDevice={'testS','Open'}}, -- switch test39 OFF when trigger device testI (only at daytime) and device testS has state Open              
         
         {'test40', deviceOn  ={'testJ','at daytime'}, statusDevice={'testS','Open'}, forSec=5}, -- switch test40 ON for 5 sec when trigger device testJ (only at daytime) and device testS has state Open              

}
Complete script is below.

Code: Select all

--==================================================================================
-- PLANS in dzVents 
-- *********************************************************************************
-- dzVents have to be version 2.3 and newer 
-- First element of plan must be "device" which have to be change (On, Off, dimTo)  from domoticz  eg  'test1' 
-- Script operate on something called FUNCTION, PROPERTY, ACTION.  List of each type below 
-- Only one instance of the function and property and action allowed on the plan, 
-- Action and property are not obligatory but at least one function must occur
-- Specjal action  fun= can handle any allowed string function in dzVents see eg 
-- The order function and property  does not matter. 
-- Function have to have list of time conditions 
-- List of time conditions can be any time conditions from dzVents wiki (reference eg list on end of this script). 
-- List of time conditions if is only one condition can be just string if is more condition have to be array 
-- The device must be able to operate functions
-- Write to log domoticz (FORCE) when LOGGING=true and trigger device and plan dont have properies dontLog=true
-- *********************************************************************************

-- FUNCTION trigger by time : 
-- > switchOn: switchOn{list of time conditions} - ON switch with checkFirst  eg:  switchOn='at 11:00' or for list switchOn={'at 01:33', 'at sunrise'}
-- > switchOff: switchOff{list of time conditions} - OFF switch with checkFirst  eg: switchOff = {'at 01:33', 'at sunrise'}
-- > dimTo:  dimTo{percentage, {list of time conditions} - Switch a dimming device on and/or dim to the specified level. Level can be number or name of level - string  
          -- eg dimTo={20,  {'at 01:33', 'at sunrise'}}
          -- eg dimTo={'Level1', {'at 01:33', 'at sunrise'}}

-- FUNCTION trigger by device: 
-- > deviceOn:  deviceOn{device,state,list of time conditions} - ON switch when trigger device with state  eg: deviceOn = {'test4,'Locked','at 01:33', 'at sunrise'}
-- > deviceOff:  deviceOff{device,state,list of time conditions} - OFF switch when trigger device with state  eg: deviceOn = {'test4,'Locked','at 01:33', 'at sunrise'}


-- PROPERTY: (all property is not obligatory)  
-- > name: short name used in write to log end and in header notify eg name = ' It is name '
-- > description: used in write to log end and body notify  eg description = ' It is description '
-- > notify: notify only if present and is true  to PUSHOVER (defined in function fun_notify() in script.   eg. notify = true 
-- > dontLog: if true script dont write to log action. If not present or is false script will write to log when LOGGING = true   eg. donLog = true 

--- ACTION: 
-- > statusDevice: device to check status other device instead of checkFirst()  eg statusDevice={'test4','Unlocked'}, in this case trigger only if state 'test4' == 'Unlocked'  
-- > repeatAfterSec - repeat function  eg. repeatAfterSec={1,3}
-- > forSec - work only with SwitchOn and switchOff  eg forSec=10
-- > fun: can be any string of methods  controlling a delay or a duration for switches eg fun='afterSec(5).forMin(2)'
--==================================================================================

local plans = {
         {'test11',  switchOn  = 'at 11:00'}, --  switch test11 ON  when time is 11:00
         {'test12',  switchOn  ={'at 12:00','at nighttime'}, switchOff='at daytime'}, --  switch ON test12 in to times conditions. also switch OFF 
         {'test13',  switchOff ={'at 13:00','at 13:10'}, fun='checkFirst().aferSec(5).forSec(10)'}, -- switch test13 OFF with add metods checkFirst().... when times conditions.
         {'test14',  switchOff ='at 14:00', description=' description for write log'}, -- ... with write to log ' description for write log'
         {'test16',  switchOff ='testAF with notify', description=' description for write log and notify', notify=true}, -- ... with notify 
         {'test17',  switchOff ='testAG with notify', description=' description for only notify', notify=true, dontLog=true}, -- ... with notify without write to log 
         
         {'test20',   dimTo  = {10,'at daytime'}}, --  switch selector test21  to lvl = 10 (Level1) when time is 21:00
         {'test20',   dimTo  = {'Level2','at 22:00', 'at 22:30'}}, --  switch selector test20 to lvl = Level2 when time is 22:00 and 22;30
         {'test20',   dimTo  = {'Level3','at 23:00'}, fun='afterSec(5)'}, --  switch selector test20 to lvl = Level3 when time is 23:00  after 5 second from trigger
         {'test20',   dimTo  = {'Off','at nighttime'}, fun='afterSec(1)', notify=true}, --  switch selector test20 to lvl = Off when time is 00:00  after 5 second from trigger  

         {'test31',  deviceOn  = 'testA'},  --  switch test31 ON  when device testA trigger 
         {'test32',  deviceOn  ={'testB','Locked'}}, --  switch test32 ON  when device testB trigger  and its state = 'Locked'
         {'test33',  deviceOn  ={'testC','Locked','at daytime'}}, --  switch test33  ON  when device testC trigger  and its state = 'Locked' and if is daytime
         {'test34',  deviceOff ='testD', fun='checkFirst().aferSec(5).forSec(10)'}, -- switch test34 OFF with add metods checkFirst().... when device testD trigger 
         {'test35',  deviceOff ='testE', description=' text for write log'},  -- ... with write to log text =' text for write log'
         {'test36',  deviceOff ='testF', description=' description for write log and notify', notify=true}, -- ... with write to log text =' text for write log' and send notify PUSHOVER
         {'test37',  deviceOff ='testG', notify=true, dontLog=true}, -- .., without write to log  and send notify PUSHOVER
         {'test38',  deviceOff ='testH', statusDevice={'testS','Open'}}, -- switch test38 OFF when trigger device testH and device testS has state Open 
         {'test39',  deviceOff ={'testI','at daytime'}, statusDevice={'testS','Open'}}, -- switch test39 OFF when trigger device testI (only at daytime) and device testS has state Open              
         
         {'test40', deviceOn  ={'testJ','at daytime'}, statusDevice={'testS','Open'}, forSec=5}, -- switch test40 ON for 5 sec when trigger device testJ (only at daytime) and device testS has state Open              

}

-------------------------------------------------------------------------------------
local version = '2.5'                   -- current version of script 
local LOGGING =  true                   -- true or false LOGGING info to domoticz log when trigger  and plan dont have properies  dontLog=true
local NOTIFY_TIME   = 'at 07:00-22:00'  -- matchesRule when notify will be send  (default used - PUSHOVER)
local NOTIFY_TEXT = 'Plany: '           -- main text for notify
local LOG_TEXT = 'Plany: '              -- main text for notify
local TIME_WORK  =  'every minute'      -- when script work. and how offen!!!  eg 'every minute between 01:00 and 23:45'
local TRIGGER_DEVICE ='*'               -- used for trigger on devices for function deviceOn, deviceOff  * wild-card mean used for All devices. if You dont needed change to TRIGGER_DEVICE =''  
                                        -- PIR_* or *_PIR. eg is use in devices = {TRIGGER_DEVICE}
local DEBUG = false                     -- used only for debbuging 
                                
-------------------------------------------------------------------------------------

--=================================
-- helpers variable and function
--=================================
local switchOn  ='switchOn'
local switchOff ='switchOff'
local dimTo     ='dimTo'
local deviceOn  ='deviceOn'
local deviceOff ='deviceOff'

local functions_time   = {switchOn,switchOff, dimTo}
local functions_device = {deviceOn,deviceOff}

function fun_notify(domoticz,plan,device, fun,text)
    if (plan.notify ~= nil and plan.notify == true)  and domoticz.time.matchesRule(NOTIFY_TIME) then
        local heding = NOTIFY_TEXT..' '..device
        domoticz.notify(heding ,text, domoticz.PRIORITY_HIGH, domoticz.SOUND_PUSHOVER, '', domoticz.NSS_PUSHOVER)    -- notify system default - PUSHOVER
    end 
end 

function fun_device(domoticz,device)
    for i,plan in pairs(plans) do 
        for fun, params in pairs(plan) do
            local trigger_device = device 
            if isFun(domoticz,fun,functions_device) then 
                 if isDeviceTrigger(domoticz,plan,device,fun) then
                    fun_acction(domoticz,plan,fun,trigger_device)
                end
            end 
        end 
    end     
    return false 
end 

function fun_time(domoticz)
    for  p , plan in ipairs(plans)	do 	
        for fun, params in pairs(plan) do
        if isFun(domoticz,fun,functions_time) then fun_acction(domoticz,plan,fun) end 
        end 
    end
end 


function fun_acction(domoticz,plan, fun, trigger_device)
    local log_text = nil 
    if isMatchRule(domoticz,plan,fun) then 
        local device = plan[1]
        log_text = fun_switch(domoticz,plan,device,fun,trigger_device)
        if log_text ~= nil then fun_notify(domoticz,plan,device,fun, log_text, trigger_device) end
    end 
    return log_text
end     

function isDeviceTrigger(domoticz,plan,trigger_device,fun)
    local ret = false 
    local name= nil 
    local state = nil 
    if fun==deviceOn then 
          if type(plan.deviceOn) == 'string' then name = plan.deviceOn else name = plan.deviceOn[1] end       
          if plan.deviceOn[2]~= nil and plan.deviceOn[2]~= '' then state = plan.deviceOn[2] end 
    end  
    if fun==deviceOff then 
          if type(plan.deviceOff) == 'string' then name = plan.deviceOff else name = plan.deviceOff[1] end       
          if plan.deviceOff[2]~= nil  and plan.deviceOff[2]~= '' then state = plan.deviceOff[2] end 
    end  
    if name == trigger_device.name and (state == nil or state == trigger_device.state)  then 
        return true 
    else 
        return false 
    end 
end 

function fun_switch(domoticz,plan,device,fun, trigger_device)
    local log_text = nil
    local param = ''
    if  plan.statusDevice ~= nil then 
        if  domoticz.devices(plan.statusDevice[1]).state == plan.statusDevice[2] then 
            prop = 'statusDevice: '..tostring(plan.statusDevice[1])..' = '.. tostring(plan.statusDevice[2])
        else 
            return log_text  --- without trigger  cos statusDevice is not true. 
        end 
    end 
    
    if fun == deviceOn then fun=switchOn end
    if fun == deviceOff then fun=switchOff end 
    if fun == dimTo then param = getLvl(domoticz,device,plan.dimTo[1]) end 
    
    local forRun="domoticz.devices('"..device.."')."..fun..'('..param..')'
    if plan.fun ~= nil then forRun=forRun..'.'..plan.fun end 
    if plan.repeatAfterSec ~= nil then 
        param ='repeatAfterSec('..tostring(plan.repeatAfterSec[1])..','..tostring(plan.repeatAfterSec[2])..')'
        forRun=forRun..'.'..param
    end     
    if plan.forSec ~= nil then
        param = 'forSec('..tostring(plan.forSec)..')'
        forRun=forRun..'.'..param
    end 
    runFunDomoticz(domoticz,forRun)
    log_text = writeLogTrigger(domoticz,plan,device,fun,param)
    return log_text
end 

function runFunDomoticz(dz,forRun)
	    domoticz=dz
        debug(domoticz,forRun)
	    fun=load(forRun, chunkname)
        fun(dz)
end 


--=================================
-- other helpers function
--=================================

function writeLogTrigger(domoticz,plan,device,fun,text)
    local log_text = ''
    if LOGGING and (plan.dontLog==nil or plan.dontLog==false) then  
        if plan.description ~= nil then 
            log_text = log_text..' - '..plan.description 
        else
            if plan.name  ~= nil then log_text =log_text..plan.name end 
            if text ~= '' then text='.'..text end 
            log_text = log_text ..' -> '..device ..'.'..fun..'()'..text 
        end 
        if plan.dontLog == nil or plan.dontLog == false then
            domoticz.log(LOG_TEXT..log_text, domoticz.LOG_FORCE) 
        end 
    end 
    return log_text 
end 

function isMatchRule(domoticz,plan,fun)
    local ret = false 
    if fun == switchOn then ret = isTimeMatchRule(domoticz, plan.switchOn, fun, plan) end 
    if fun == switchOff then ret = isTimeMatchRule(domoticz, plan.switchOff, fun, plan ) end 
    if fun == dimTo then ret = isTimeMatchRule(domoticz, plan.dimTo, fun, plan) end 
    if fun == deviceOn then ret = plan.deviceOn[3]==nil or isTimeMatchRule(domoticz, plan.deviceOn, fun, plan) end 
    if fun == deviceOff then ret = plan.deviceOff[3]==nil or isTimeMatchRule(domoticz, plan.deviceOff, fun, plan ) end 
    return ret 
end     

function isTimeMatchRule(domoticz,time_rule, fun, plan)
    if type(time_rule) == 'string' then 
        return domoticz.time.matchesRule(time_rule) 
    else 
        for  j, value in ipairs(time_rule) do
            if type(value) == 'string' then 
                if domoticz.time.matchesRule(value) then return true end 
            end 
            if type(value) == 'table' then 
                for  k, value2 in ipairs(value) do 
                    if type(value2) == 'string' then
                        if domoticz.time.matchesRule(value2) then return true end 
                    end     
                end 
            end         
        end 
    end 
    return false 
end     

function getLvl(domoticz,device,lvl)
    local ret = ''
    if type(lvl) == 'number' then ret= lvl end 
    if type(lvl) == 'string' then 
       if type(domoticz.devices(device).levelNames) == 'table' then 
            for i,l in ipairs(domoticz.devices(device).levelNames) do
                if l == lvl then 
                    ret = (i-1)*10    -- we calculate value level for name lvl in selector. 
                end 
            end 
        else
            domoticz.log(LOG_TEXT..device..' should be type selector',domoticz.LOG_ERROR) 
        end 
    end 
    return ret      
end 

function isFun(domoticz,val,functions)
    for v, fun in ipairs(functions) do 
        if fun == val then return true end 
    end 
    return false
end     

function isParam(domoticz,val)
    for v, param in ipairs(params) do 
        if param == val then return true end 
    end 
    return false
end     

function getParams(domoticz,plan)
    local list_params = {}
    for fun, params in pairs(plan) do
            if isParam(domoticz,fun) then table.insert(list_params, fun) end 
    end 
    return list_params
end     

---- FUNCTION HELPING FOR DEBUGING 
function debug(domoticz,text1, text2, text3, text4, text5, text6, text7, text8, text9)
    local text = ''
    if text1 ~=nil then text = tostring(text1) end 
    if text2 ~=nil then text = text..' '..tostring(text2) end 
    if text3 ~=nil then text = text..' '..tostring(text3) end
    if text4 ~=nil then text = text..' '..tostring(text4) end
    if text5 ~=nil then text = text..' '..tostring(text5) end
    if text6 ~=nil then text = text..' '..tostring(text6) end 
    if text7 ~=nil then text = text..' '..tostring(text7) end 
    if text8 ~=nil then text = text..' '..tostring(text8) end
    if text9 ~=nil then text = text..' '..tostring(text9) end 
    if DEBUG then domoticz.log(tostring(text),domoticz.LOG_FORCE) end 
end

function logDEBUG(domoticz,plan,device,fun)
    if DEBUG then 
        local name = ''
        local description =''
        local notify = ' notify= '
        if plan.name ~= nil then name = plan.name end
        if plan.description ~= nil then description = plan.description end
        if plan.notify ~= nil then notify = notify..tostring(plan.notify) else notify = notify..tostring(false) end 
        domoticz.log(LOG_TEXT..' checking ... '..tostring(device)..' '..tostring(fun)..' '..tostring(name)..' '..tostring(description),domoticz.LOG_FORCE) 
    end 
end
function logPref(domoticz)
    if DEBUG then
        domoticz.log('',domoticz.LOG_FORCE) 
        domoticz.log('*********************************************************************************',domoticz.LOG_FORCE) 
        domoticz.log('PLANS ver '..version,domoticz.LOG_FORCE) 
        domoticz.log('*********************************************************************************',domoticz.LOG_FORCE) 
    end 
end     
function logSuffix(domoticz)
    if DEBUG then
        domoticz.log('*********************************************************************************',domoticz.LOG_FORCE) 
        domoticz.log('',domoticz.LOG_FORCE) 
    end 
end     


--=================================
-- main dzVents body of script 
--=================================
    
return {
	on = {
		timer = {TIME_WORK},
		devices = {TRIGGER_DEVICE}
	},
	execute = function(domoticz,device,triggerInfo)
	 if triggerInfo.type==domoticz.EVENT_TYPE_TIMER then 
	    fun_time(domoticz) 
	 else 
	    fun_device(domoticz,device) 
	 end     
end 

}

-- *********************************************************************************
-- List of time conditions from dzVents wiki https://www.domoticz.com/wiki/DzVents:_next_generation_LUA_scripting
-- *********************************************************************************
--
--        'every minute',              -- causes the script to be called every minute
--        'every other minute',        -- minutes: xx:00, xx:02, xx:04, ..., xx:58
--        'every <xx> minutes',        -- starting from xx:00 triggers every xx minutes
--                                    -- (0 > xx < 60)
--        'every hour',                -- 00:00, 01:00, ..., 23:00  (24x per 24hrs)
--        'every other hour',          -- 00:00, 02:00, ..., 22:00  (12x per 24hrs)
--        'every <xx> hours',          -- starting from 00:00, triggers every xx
--                                     -- hours (0 > xx < 24)
--        'at 13:45',                  -- specific time
--        'at *:45',                   -- every 45th minute in the hour
--        'at 15:*',                   -- every minute between 15:00 and 16:00
--        'at 12:45-21:15',            -- between 12:45 and 21:15. You cannot use '*'!
--        'at 19:30-08:20',            -- between 19:30 and 8:20 then next day
--        'at 13:45 on mon,tue',       -- at 13:45 only on Mondays and Tuesdays (english)
--        'every hour on sat',         -- you guessed it correctly
--        'at sunset',                 -- uses sunset/sunrise info from Domoticz
--        'at sunrise',
--        'at sunset on sat,sun',
--        'xx minutes before sunset',
--        'xx minutes after sunset',
--        'xx minutes before sunrise',
--        'xx minutes after sunrise'   -- guess ;-)
--        'between aa and bb'          -- aa/bb can be a time stamp like 15:44
--                                     -- aa/bb can be sunrise/sunset
--                                     -- aa/bb can be 'xx minutes before/after
--                                        sunrise/sunset'
--        'at nighttime',              -- between sunset and sunrise
--        'at daytime',                -- between sunrise and sunset
--        'at daytime on mon,tue',     -- between sunrise and sunset
--                                        only on Mondays and Tuesdays
--
--        -- or if you want to go really wild:
--        'at nighttime at 21:32-05:44 every 5 minutes on sat, sun',
--        'every 10 minutes between 20 minutes before sunset and 30 minutes after sunrise on mon,fri,tue'
--
-- *********************************************************************************
Last edited by Eoreh on Sunday 28 January 2018 1:04, edited 21 times in total.
Eoreh
Posts: 65
Joined: Tuesday 13 October 2015 13:50
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Poland
Contact:

Re: Plans in dzVents

Post by Eoreh »

New version 2.5 - see first post.
Whats new:
- support also trigger on device by new function deviceOn, deviceOff
- support by new property fun='' all command options (delay, duration, event triggering) - see dzVents wiki.
- simplification syntex for add time to function (not need {})
- refactoring code.
Eoreh
Posts: 65
Joined: Tuesday 13 October 2015 13:50
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Poland
Contact:

Re: Plans in dzVents

Post by Eoreh »

A very important warning.
Only one script can be active at a time.
If you have more scripts with "Plans" only the last one in the list is still being processed by dzVents. If it results from it that an event is activated, it is executed as many times as the number of copies of the script you have (no matter if they have the same plans).

But of course, the single works well, as reported by a colleague testing in his home automation. (Thanks again, Tom for inspirations to write this scrip and tests).

I am looking for a cause and solution to the problem with many instace of scripts
Eoreh
Posts: 65
Joined: Tuesday 13 October 2015 13:50
Target OS: Raspberry Pi / ODroid
Domoticz version: beta
Location: Poland
Contact:

Re: Plans in dzVents

Post by Eoreh »

EDIT: This version is no longer supported. You can download new version with new sytex and in module. from here
viewtopic.php?f=59&t=21914
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest