Page 1 of 1

Run script if device left on for a period of time?

Posted: Friday 03 May 2019 12:29
by doh
I know it's possible to detect if a device is turned on, and also to determine how long it has been left on for.
However, is it possible to trigger a script based on whether a device has been left on for, say, an hour, rather than having a script running every few minutes to check this?
Thanks

Re: Run script if device left on for a period of time?

Posted: Friday 03 May 2019 17:46
by waaren
doh wrote: Friday 03 May 2019 12:29 I know it's possible to detect if a device is turned on, and also to determine how long it has been left on for.
However, is it possible to trigger a script based on whether a device has been left on for, say, an hour, rather than having a script running every few minutes to check this?
Thanks
Yes this is possible. Using the "easy" way with cancelQueuedCommands and afterSec() methods. Works most of the times but not if domoticz or system is restarted between time device was switched On and scheduled Off time. (See first script)

and a somewhat more complicated method where Off time is stored in persistent data and script to switch Off device is triggered by a delayed HTTP callback. Using this method it's possible to switch lights Off even after a system reboot or domoticz restart. (see 2nd script)

Both methods use a default maxOnSeconds that can be overruled by putting maxOnSeconds: xxxx in the device description field, where xxxx is the amount of seconds the device should stay max. on.

All devices to be considered can be put in the on = devices {} section; either as a list of devicenames or as (a list) of wildcarded devicenames.

Code: Select all

return {
    on = { devices = {"device1", "device2"}},     -- Devices to be checked (can be wildcarded names)
           
    logging = { level = domoticz.LOG_ERROR,    -- change to Debug when script does not do what's expected
                marker = "maxOn" },    

    execute = function(dz, item)
        local defaultMaxOnSeconds = 3600 -- default max on time in seconds
 
        local function logWrite(str,level)
            dz.log(tostring(str),level or dz.LOG_DEBUG)
        end
        
        local function deviceMaxOnSeconds(device)
            local maxOnSeconds
            if device.description ~= nil and device.description ~= "" then
                _, _, maxOnSeconds = string.find(device.description,"maxOnSeconds:(%s*%d+)")       -- Check if the description field has "maxOnTime: " followed by an integer (max seconds)
                logWrite (device.name .. " max on time: " .. ( maxOnSeconds or defaultMaxOnSeconds ))
            end    
            return ( maxOnSeconds or defaultMaxOnSeconds )
        end

        item.cancelQueuedCommands()
        if item.active then
            logWrite(item.name .. ": " .. deviceMaxOnSeconds(item))
            item.switchOff().silent().afterSec(deviceMaxOnSeconds(item))
        end
    end
}

Code: Select all

scriptVar = "maxOndevices"

return {
    on = { timer   = { "at *:17" },               -- Additional check once every hour to ensure no devices are left behind after system boot or domoticz restart
           httpResponses = { scriptVar .. '*' },
           devices = {"device1", "device2"}},     -- Devices to be checked (can be wildcarded names)
           
    logging = { level = domoticz.LOG_ERROR,    -- change to Debug when script does not do what's expected
                marker = "maxOn" },    

    data = { activeDevices = { initial = {}}},
    
    execute = function(dz, item)
        local defaultMaxOnSeconds = 3600 -- default max on time in seconds
 
        local function logWrite(str,level)
            dz.log(tostring(str),level or dz.LOG_DEBUG)
        end
        
        local function deviceMaxOnSeconds(device)
            local maxOnSeconds
            if device.description ~= nil and device.description ~= "" then
                _, _, maxOnSeconds = string.find(device.description,"maxOnSeconds:(%s*%d+)")       -- Check if the description field has "maxOnTime: " followed by an integer (max seconds)
                logWrite (device.name .. " max on time: " .. ( maxOnSeconds or defaultMaxOnSeconds ))
            end    
            return ( maxOnSeconds or defaultMaxOnSeconds )
        end

        local function scheduleStillActiveCheck(device,delay)
            local url = dz.settings['Domoticz url'] .. "/json.htm?type=command&param=addlogmessage&message=" .. 
                        dz.utils.urlEncode(device.name .. " will be switched Off; if still active")
            dz.openURL({
                        url = url,
                        method = 'GET',
                        callback = scriptVar .. "_" .. device.idx 
                    }).afterSec(delay)    
        end

        if item.isDevice and not item.active then
            dz.data.activeDevices[item.idx] = nil
        elseif item.isDevice and item.active then 
            local maxOnSeconds = deviceMaxOnSeconds(item)
            logWrite(item.name .. " is Active for max " .. maxOnSeconds .. " seconds.")
            scheduleStillActiveCheck(item,maxOnSeconds)
            dz.data.activeDevices[item.idx] = dz.time.dDate + maxOnSeconds
        elseif item.isTimer or item.isHTTPResponse then
            for key, value in pairs(dz.data.activeDevices) do
                if dz.devices(key).active and ( dz.devices(key).lastUpdate.secondsAgo <= dz.data.activeDevices[key] ) then
                    dz.data.activeDevices[key] = nil
                    dz.devices(key).switchOff().silent()
                end
            end
        end
    end
}

Re: Run script if device left on for a period of time?

Posted: Thursday 11 February 2021 20:57
by geertvercamer
@Waaren,

I'm using this script to check if doors/windows are left open longer than wanted.

I don't understand this line of code:

Code: Select all

                if dz.devices(key).active and ( dz.devices(key).lastUpdate.secondsAgo <= dz.data.activeDevices[key] ) then
You add 'dz.time.dDate + maxOnSeconds', so it will take a whole lot of time for lastUpdate.secondsAgo to become bigger, no?

once the device triggers, the program waits for 'delay' seconds to launch the url. Isn't it enough to just check if the device is active?

Thanks

Re: Run script if device left on for a period of time?

Posted: Thursday 11 February 2021 21:09
by waaren
geertvercamer wrote: Thursday 11 February 2021 20:57 once the device triggers, the program waits for 'delay' seconds to launch the url. Isn't it enough to just check if the device is active?
If you are sure that in between the device have not been switched Off and back On that would be enough.

Re: Run script if device left on for a period of time?

Posted: Saturday 13 February 2021 10:04
by geertvercamer
ah, ok, I see why now but still not how.
suppose maxOnSeconds = 60


dz.data.activeDevices[item.idx] = dz.time.dDate + maxOnSeconds

this sets quite big a value, all the seconds since 01/01/1970, so over 51 years in seconds

if dz.devices(key).active and ( dz.devices(key).lastUpdate.secondsAgo <= dz.data.activeDevices[key] ) then

secondsAgo is a duration, right? So this test is only false if my door would be open for more then 51 years, 1 month, 13 days and 10 hours

shouldn't you compare the lastUpdate timestamp in seconds to the dz.data.activeDevices[key]?

Or am i missing something completely here?
Thanks!

Re: Run script if device left on for a period of time?

Posted: Saturday 13 February 2021 11:40
by waaren
geertvercamer wrote: Saturday 13 February 2021 10:04 ah, ok, I see why now but still not how.
Or am i missing something completely here?
Probably because there were still some bugs in the script :oops:

I fixed them in below version and added some debug log lines to make it more clear what happens

Code: Select all

local scriptVar = 'maxOndevices' 

return
{
    on =
    {
        timer  = -- Additional check once every hour to ensure no devices are left behind after system boot or domoticz restart
        {
            'at *:17' ,
            'every minute',  -- for debug only
        },
        httpResponses =
        {
            scriptVar .. '*',
        },
        devices =   -- Devices to be checked (can be wildcarded names)
        {
            'device1' ,
            'device2' ,
        },
    },

    logging =
    {
        level = domoticz.LOG_DEBUG,    -- change to Debug when script does not do what's expected
        marker = 'maxOn',
    },

    data =
    {
        activeDevices =
        {
            initial = {}
        }
    },

    execute = function(dz, item)
        local defaultMaxOnSeconds = 3600 -- default max on time in seconds

        local function deviceMaxOnSeconds(device)
            local maxOnSeconds
            if device.description ~= nil and device.description ~= '' then
               maxOnSeconds = device.description:match('maxOnTime:(%s*%d+)') -- Check if the description field has 'maxOnTime: int' (in seconds)
               dz.log (device.name .. ' max on time: ' .. ( maxOnSeconds or defaultMaxOnSeconds ), dz.LOG_DEBUG)
            end
            return ( maxOnSeconds or defaultMaxOnSeconds )
        end

        local function scheduleStillActiveCheck(device,delay)
            local url = dz.settings['Domoticz url'] .. '/json.htm?type=command&param=addlogmessage&message=' ..
                        dz.utils.urlEncode(device.name .. ' will be switched Off; if still active')
            dz.openURL({
                        url = url,
                        method = 'GET',
                        callback = scriptVar .. '_' .. device.idx
                    }).afterSec(delay + 1)
        end

        if item.isDevice and not item.active then
            dz.data.activeDevices[item.idx] = nil
        elseif item.isDevice and item.active then
            local maxOnSeconds = deviceMaxOnSeconds(item)
            dz.log(item.name .. ' is activated for max ' .. maxOnSeconds .. ' seconds.', dz.LOG_DEBUG)
            scheduleStillActiveCheck(item, maxOnSeconds)
            dz.data.activeDevices[item.idx] = os.time() + maxOnSeconds
        elseif item.isTimer or item.isHTTPResponse then
            for key, value in pairs(dz.data.activeDevices) do

                dz.log('Comparing ' .. dz.devices(key).name .. ' dz.data: ' .. math.floor(dz.data.activeDevices[key]) .. ' against ' .. os.time(), dz.LOG_DEBUG)

                if dz.devices(key).active and dz.data.activeDevices[key] < os.time() then
                    dz.log( 'Switching off ' .. dz.devices(key).name , dz.LOG_DEBUG)
                    dz.data.activeDevices[key] = nil
                    dz.devices(key).switchOff().silent()
                elseif dz.devices(key).active then
                    dz.log(dz.devices(key).name .. ' can be left on for another ' .. math.floor( dz.data.activeDevices[key] - os.time() ) .. ' seconds' , dz.LOG_DEBUG)
                else
                    dz.log( 'No action needed for ' .. dz.devices(key).name .. ' needed. It is already Off. ', dz.LOG_DEBUG)
                end
            end
        end
    end
}

Re: Run script if device left on for a period of time?

Posted: Saturday 13 February 2021 12:19
by EddyG
Tnx, for the script. I have a few issues.
In the logging I find.

Code: Select all

Debug: maxOn: OpenURL: callback = waterpeilGrebbe_38
That callback is from an other script. So it looks like the "scriptVar" is used from an other script.
Also I found that

Code: Select all

 local url = dz.settings['Domoticz url'] .. '/json.htm?type=command&param=addlogmessage&message=' ..
The original is logged as:

Code: Select all

Debug: maxOn: OpenURL: url = http://0.0.0.0:8080/json.htm?
It is not working for me, I had to use hardcoded "http://127.0.0.1:8080" for the url
Any ideas?

Re: Run script if device left on for a period of time?

Posted: Saturday 13 February 2021 18:33
by waaren
EddyG wrote: Saturday 13 February 2021 12:19

Code: Select all

Debug: maxOn: OpenURL: callback = waterpeilGrebbe_38
That callback is from an other script. So it looks like the "scriptVar" is used from an other script.
Change

Code: Select all

scriptVar = 
to

Code: Select all

local scriptVar = 
Also I found that

Code: Select all

 local url = dz.settings['Domoticz url'] .. '/json.htm?type=command&param=addlogmessage&message=' ..
It is not working for me, I had to use hardcoded "http://127.0.0.1:8080" for the url
Did you set your location and
Local Networks (no username/password):

in the settings ?

Re: Run script if device left on for a period of time?

Posted: Saturday 13 February 2021 19:38
by EddyG
Yes, stupid me. The "local" worked of course.
Yes, mylocation is set (from the initial start of my Domoticz), local network like 127.0.0.* and a few in 192.168.2.* even Domoticz it self.
But it comes back with 0.0.0.0

Re: Run script if device left on for a period of time?

Posted: Saturday 13 February 2021 20:26
by waaren
EddyG wrote: Saturday 13 February 2021 19:38 Yes, stupid me. The "local" worked of course.
Yes, mylocation is set (from the initial start of my Domoticz), local network like 127.0.0.* and a few in 192.168.2.* even Domoticz it self.
But it comes back with 0.0.0.0
Can you try and change it to below line? That will take care of an IPv6 type of address.

Code: Select all

127.0.0.*;192.168.2.*;::1

Re: Run script if device left on for a period of time?

Posted: Saturday 13 February 2021 20:30
by waaren
waaren wrote: Saturday 13 February 2021 20:26
EddyG wrote: Saturday 13 February 2021 19:38 Yes, stupid me. The "local" worked of course.
Yes, mylocation is set (from the initial start of my Domoticz), local network like 127.0.0.* and a few in 192.168.2.* even Domoticz it self.
But it comes back with 0.0.0.0
Can you try and change it to below line? That will take care of an IPv6 type of address.

Code: Select all

127.0.0.*;192.168.2.*;::1
If that does not work could it be that you start domoticz with the option -wwwbind 0.0.0.0 ?

Re: Run script if device left on for a period of time?

Posted: Monday 15 February 2021 14:40
by EddyG
Sorry for the delay.
I already have IPv6 added and I do not use -wwwbind 0.0.0.0 to start domoticz.
But my workaround of hardcode 127.0.0.1 works without errors.
Tnx.

Re: Run script if device left on for a period of time?

Posted: Monday 15 February 2021 15:49
by waaren
EddyG wrote: Monday 15 February 2021 14:40 Sorry for the delay.
I already have IPv6 added and I do not use -wwwbind 0.0.0.0 to start domoticz.
But my workaround of hardcode 127.0.0.1 works without errors.
OK. Can you please add

Code: Select all

utils.log(_gv, utils.LOG_FORCE)
at line 38 of <domoticz dir>/dzVents/runtime/EventHelpers.lua for at least a minute and share te results here?

It might help in identifying what causes your issue and if it can be solved it could prevent the same for others. Thx !

Re: Run script if device left on for a period of time?

Posted: Monday 15 February 2021 16:20
by EddyG
I did that and now I see in the logging ->

Code: Select all

 ["domoticz_wwwbind"]="0.0.0.0" 
I restarted Domoticz and I also see:

Code: Select all

Status: WebServer(HTTP) started on address: 0.0.0.0 with port 8080
Status: WebServer(SSL) started on address: 0.0.0.0 with port 443
Status: Camera: settings (re)loaded
Starting shared server on: 0.0.0.0:6144
In /etc/init.d/domoticz.sh there is NO parameter of wwwbind
This is the partial output of 'sudo systemctl status domoticz.service'

Code: Select all

  Process: 836 ExecStart=/etc/init.d/domoticz.sh start (code=exited, status=0/SUCCESS)
    Tasks: 39 (limit: 4915)
   CGroup: /system.slice/domoticz.service
           └─1338 /home/pi/domoticz/domoticz -daemon -www 8080 -loglevel info -log /var/log/domoticz.log -dbase /home/pi/domoticz/domoticz.db

Re: Run script if device left on for a period of time?

Posted: Monday 15 February 2021 16:47
by waaren
EddyG wrote: Monday 15 February 2021 16:20 I did that and now I see in the logging ->

Code: Select all

 ["domoticz_wwwbind"]="0.0.0.0" 
OK I guess somewhere in your config the wwwbind is set to 0.0.0.0 but I have no idea where to search for it.
Until you find it, probably the easiest is to just keep using your work around.

Re: Run script if device left on for a period of time?

Posted: Monday 15 February 2021 17:43
by geertvercamer

Code: Select all

dz.data.activeDevices[key] < os.time()
won't this always give true?

Re: Run script if device left on for a period of time?

Posted: Monday 15 February 2021 18:13
by waaren
geertvercamer wrote: Monday 15 February 2021 17:43

Code: Select all

dz.data.activeDevices[key] < os.time()
won't this always give true?
No because you set it at an earlier stage to
os.time() + maxOnSeconds

So it it will only become true after maxOnSeconds seconds passed by since it was set.

Re: Run script if device left on for a period of time?  [Solved]

Posted: Tuesday 16 February 2021 20:37
by geertvercamer
I think I get it. The function scheduleStillActiveCheck is called which fires the URL after x seconds, but doesn't wait for maxOnSeconds + 1 to return.
Thanks waaren!