Solved Kodi addon service interval time question
#1
Hi I'm trying to add a background service to my addon. I'm trying to understand how the service in script.embuary.helper works in order to understand. 

The thing I am unsure of is... in Embuary helper for example, you can set a service_interval within the script settings between a range of 0.1 and 2.0 - it goes up in 0.1 increments I believe and the default is 0.5. 

Is this basically a mulitplier that is applied to some sort of core Kodi interval value used to define how regularly addons monitoring services are run? I assume there must be some sort of bottom floor to assume addons can't abuse system resources. For example, I know skin timers are evaluated every 500ms.

From what I can see in the code, the service_interval is used to control how many times the something will be looped before something happens. But what is the relation between this and real-world time?

E.g. I see this sort of logic in the code.

Apologies if this is very basic or I've misunderstood terminology/code here - I'm still learning the basics of python and coding having previously only learned Kodi skinning.

python:

if variable >= 300:
    function()
    variable = 0
else:
    variable += service_interval

I understand this to mean that, if service_interval is 0.5, function() will be executed every 600 times the code is run through. 

Is there an underlying assumption that the code will be run every 500ms? Or am I supposed to define this somehow in my addon?

PS apologies if this is very basic or I've misunderstood terminology or code here. I'm still learning the basics of coding and python having only previous experience of kodi skinning.
Reply
#2
That script might be a little complicated as a first look at how service addons work.  The bottom line is that there is no core function for timing of service intervals.  It's all on the addon to define.  That script is likely defining some base internal that the user can change so that it works OK in the background even on low power devices.  You should really take a look at the Kodi Monitor class documentation to get a sense of it:

https://codedocs.xyz/xbmc/xbmc/classXBMC...nitor.html

Basically a service addon is started and needs to stay active while listening for other Kodi events.

Here's how Audio Profiles starts up and stays active:
python:

class apMonitor(xbmc.Monitor):

    def __init__(self):
        """Starts the background process for automatic audio profile switching."""
        while not self.abortRequested():
            if self.waitForAbort(10):
                break

This basically says to keep looping as log as a Kodi abort hasn't been requested (i.e. the application is quitting) and wait 10 seconds between checks (although waitForAbort will also stop early if an abort request is received).

The addon then listens for certain Kodi events and processes them (this is also in the apMonitor class I started above)
python:

    def onNotification(self, sender, method, data):
        data = json.loads(data)
        if 'System.OnWake' in method:
            self._change_profile(self.SETTINGS['auto_default'])
        if 'Player.OnStop' in method:
            self.waitForAbort(1)
            if not self.KODIPLAYER.isPlaying():
                self._change_profile(self.SETTINGS['auto_gui'])
        if 'Player.OnPlay' in method:
            self._auto_switch(data)

And so on.
Reply
#3
(2023-01-26, 01:58)pkscout Wrote: That script might be a little complicated as a first look at how service addons work.  The bottom line is that there is no core function for timing of service intervals.  It's all on the addon to define.  That script is likely defining some base internal that the user can change so that it works OK in the background even on low power devices.  You should really take a look at the Kodi Monitor class documentation to get a sense of it:

https://codedocs.xyz/xbmc/xbmc/classXBMC...nitor.html

Basically a service addon is started and needs to stay active while listening for other Kodi events.

Here's how Audio Profiles starts up and stays active:
python:

class apMonitor(xbmc.Monitor):

    def __init__(self):
        """Starts the background process for automatic audio profile switching."""
        while not self.abortRequested():
            if self.waitForAbort(10):
                break

This basically says to keep looping as log as a Kodi abort hasn't been requested (i.e. the application is quitting) and wait 10 seconds between checks (although waitForAbort will also stop early if an abort request is received).

The addon then listens for certain Kodi events and processes them (this is also in the apMonitor class I started above)
python:

    def onNotification(self, sender, method, data):
        data = json.loads(data)
        if 'System.OnWake' in method:
            self._change_profile(self.SETTINGS['auto_default'])
        if 'Player.OnStop' in method:
            self.waitForAbort(1)
            if not self.KODIPLAYER.isPlaying():
                self._change_profile(self.SETTINGS['auto_gui'])
        if 'Player.OnPlay' in method:
            self._auto_switch(data)

And so on.

Thanks for the detailed explanation. So is it the self.waitForAbort() that dictates how often the code will be run?
Reply
#4
(2023-01-29, 11:16)realcopacetic Wrote:
(2023-01-26, 01:58)pkscout Wrote: That script might be a little complicated as a first look at how service addons work.  The bottom line is that there is no core function for timing of service intervals.  It's all on the addon to define.  That script is likely defining some base internal that the user can change so that it works OK in the background even on low power devices.  You should really take a look at the Kodi Monitor class documentation to get a sense of it:

https://codedocs.xyz/xbmc/xbmc/classXBMC...nitor.html

Basically a service addon is started and needs to stay active while listening for other Kodi events.

Here's how Audio Profiles starts up and stays active:
python:

class apMonitor(xbmc.Monitor):

    def __init__(self):
        """Starts the background process for automatic audio profile switching."""
        while not self.abortRequested():
            if self.waitForAbort(10):
                break

This basically says to keep looping as log as a Kodi abort hasn't been requested (i.e. the application is quitting) and wait 10 seconds between checks (although waitForAbort will also stop early if an abort request is received).

The addon then listens for certain Kodi events and processes them (this is also in the apMonitor class I started above)
python:

    def onNotification(self, sender, method, data):
        data = json.loads(data)
        if 'System.OnWake' in method:
            self._change_profile(self.SETTINGS['auto_default'])
        if 'Player.OnStop' in method:
            self.waitForAbort(1)
            if not self.KODIPLAYER.isPlaying():
                self._change_profile(self.SETTINGS['auto_gui'])
        if 'Player.OnPlay' in method:
            self._auto_switch(data)

And so on.

Thanks for the detailed explanation. So is it the self.waitForAbort() that dictates how often the code will be run?
I guess you could use it that way.  Most of the service add-ons I've seen respond to events, not run at a certain interval.  So that code snippet really just keeps the add-on running by listening for 10 seconds for an Abort and then just doing that over and over again.  But you could add a chunk of code that would run after each waitForAbort cycle like this:

python:

class apMonitor(xbmc.Monitor):

    def __init__(self):
        """Starts the background process for automatic audio profile switching."""
        while not self.abortRequested():
            if self.waitForAbort(10):
                break
            else:
                # your code here

waitForAbort is in seconds, so if you want to run the code every 5 minutes, you would change that 10 to 300.  You could also have a setting for that, but it gets more complicated because you have to listen for a settings change action and then reload the settings.  It might help to understand what you are trying to do rather than doing this in the abstract.  As I said, most service add-ons listen for events and do things.  This isn't a great way to run something periodically.
Reply
#5
Another example might be to have a background timer to do things on certain intervals.  I leverage it something like this:

count = 0

monitor = xbmc.Monitor()

while not monitor.abortRequested():

    count += 1
    if count % 30 == 0:                      # Do something every 30 seconds
        do something
   
    if monitor.waitForAbort(1):             # Sleep/wait for abort for 1 second
        do some cleanup, if needed
        break


As mentioned there are a lot of things you can do with the monitor class.


Thanks,

Jeff
Running with the Mezzmo Kodi addon.  The easier way to share your media with multiple Kodi clients.
Service.autostop , CBC Sports, Kodi Selective Cleaner and Mezzmo Kodi addon author.
Reply
#6
thanks @pkscout and @jbinkley60 - so my use case is basically what you have Jeff.  A monitor that is running every x seconds to fetch fresh fanarts for a global slideshow and updating a window property with a new fanart path every y seconds.

What I don't understand from your code or this code I'm using from @sualfred is how relation between count as an integer and count as a time equal to one second. I see the logic of yours Jeff, that you're using monitor.waitForAbort(1) to basically create a pause equal to 1 second between each loop of the code. But I still don't really understand how @sualfred is doing it in this code. It's slightly adapted from his original as I stripped out a lot of the extra stuff I didn't need.

This is the monitor.py

python:

import xbmc
import random
from resources.lib.helper import *

NOTIFICATION_METHOD = ['VideoLibrary.OnUpdate',
                       'VideoLibrary.OnScanFinished',
                       'VideoLibrary.OnCleanFinished',
                       'AudioLibrary.OnUpdate',
                       'AudioLibrary.OnScanFinished'
                       ]

class Monitor(xbmc.Monitor):

    def __init__(self):
        self.restart = False
        self.screensaver = False
        self.service_enabled = True
        if self.service_enabled:
            self.start()
        else:
            self.keep_alive()

    def onNotification(self, sender, method, data):
        if ADDON_ID in sender and 'restart' in method:
            self.restart = True

    def onSettingsChanged(self):
        log('Monitor: Addon setting changed', force=True)
        self.restart = True

    def onScreensaverActivated(self):
        self.screensaver = True

    def onScreensaverDeactivated(self):
        self.screensaver = False

    def stop(self):
        if self.service_enabled:
            log('Monitor: Stopped', force=True)
        if self.restart:
            log('Monitor: Applying changes', force=True)
            # Give Kodi time to set possible changed skin settings. Just to be sure to bypass race conditions on slower systems.
            xbmc.sleep(500)
            DIALOG.notification(ADDON_ID, ADDON.getLocalizedString(32006))
            self.__init__()
    def keep_alive(self):
        log('Monitor: Disabled', force=True)
        while not self.abortRequested() and not self.restart:
            self.waitForAbort(5)
        self.stop()

    def start(self):
        log('Monitor: Started', force=True)
        service_interval = 0.5
        background_interval = 7
        get_backgrounds = 300
        while not self.abortRequested() and not self.restart:
            ''' Only run timed tasks if screensaver is inactive to avoid keeping NAS/servers awake
            '''
            if not self.screensaver:
                ''' Get fanarts
                '''
                if get_backgrounds >= 300:
                    log('Monitor: Get fanart', force=True)
                    arts = self.grabfanart()
                    get_backgrounds = 0
                else:
                    get_backgrounds += service_interval
                ''' Set background properties
                '''
                if background_interval >= 7:
                    if arts.get('all'):
                        self.setfanart('Fanart_Slideshow_Global', arts['all'])
                    if arts.get('movies'):
                        self.setfanart('Fanart_Slideshow_Movies', arts['movies'])
                    if arts.get('tvshows'):
                        self.setfanart('Fanart_Slideshow_TVShows', arts['tvshows'])
                    if arts.get('videos'):
                        self.setfanart('Fanart_Slideshow_Videos', arts['videos'])
                    if arts.get('artists'):
                        self.setfanart('Fanart_Slideshow_Artists', arts['artists'])
                    if arts.get('musicvideos'):
                        self.setfanart('Fanart_Slideshow_MusicVideos', arts['musicvideos'])
                    background_interval = 0
                else:
                   
                    background_interval += service_interval
            self.waitForAbort(service_interval)
        self.stop()

    def grabfanart(self):
        arts = {}
        arts['movies'] = []
        arts['tvshows'] = []
        arts['artists'] = []
        arts['musicvideos'] = []
        arts['all'] = []
        arts['videos'] = []
        for item in ['movies', 'tvshows', 'artists', 'musicvideos']:
            dbtype = 'Video' if item != 'artists' else 'Audio'
            query = json_call('%sLibrary.Get%s' % (dbtype, item),
                              properties=['art'],
                              sort={'method': 'random'}, limit=40
                              )
            try:
                for result in query['result'][item]:
                    if result['art'].get('fanart'):
                        data = {'title': result.get('label', '')}
                        data.update(result['art'])
                        arts[item].append(data)
            except KeyError:
                pass
        arts['videos'] = arts['movies'] + arts['tvshows']
        for cat in arts:
            if arts[cat]:
                arts['all'] = arts['all'] + arts[cat]
        return arts

    def setfanart(self, key, items):
        arts = random.choice(items)
        window_property(key, arts.get('fanart', ''))


And it's triggered from service.py

python:

from resources.lib.monitor import *


if __name__ == "__main__":
    Monitor()


and as you can see, I have service_time as 0.5, background_interval as 7 and get_backgrounds as 300. And basically each time through the code, the count is incremented by service_time. But what I don't understand is what is actually controlling the pace of the code here.
Reply
#7
There are three things going on here which may cause some confusion.  I know it took a while for me to fully understand this.  I'll try to explain.
 
  • The outer loop is formed by lines 57 and 88 which loops every .5 seconds based upon line 54 service_interval = 0.5.  This loop is checking to see if the user aborts the addon (i.e. user shuts it down) and running lines 60-87 every .5 seconds.
  • An "inner loop / counter" is formed between lines 71 and 84 which counts for 8 (0 to 7) outer loops, then runs and resets the outer loop counter back to 0.  So this runs every 4 seconds (8 * .5 seconds)
  • Then there are real time monitor events taking place with lines 23, 27, 31 etc..  which are real time events from Kodi and react immediately since they are part of the monitor class and outside of any timer loops. 


I hope this helps.


Jeff
Running with the Mezzmo Kodi addon.  The easier way to share your media with multiple Kodi clients.
Service.autostop , CBC Sports, Kodi Selective Cleaner and Mezzmo Kodi addon author.
Reply
#8
Thanks for perservering with my ignorance @jbinkley60 . The thing I'm struggling to understand is why service_time = 0.5 is equal to 0.5 seconds and not just a float of 0.5. Because I would assume from what little I have learned so far that this code should loop much quicker than once per 0.5 seconds. That's the bit I can't get my head around. Sorry if I'm not explaining well.
Reply
#9
(2023-01-30, 00:39)realcopacetic Wrote: Thanks for perservering with my ignorance @jbinkley60 . The thing I'm struggling to understand is why service_time = 0.5 is equal to 0.5 seconds and not just a float of 0.5. Because I would assume from what little I have learned so far that this code should loop much quicker than once per 0.5 seconds. That's the bit I can't get my head around. Sorry if I'm not explaining well.

The seconds value is coming from the Kodi waitForAbort() Monitor class method.  You can make it faster by going to .4s, .3s, .1s, .01s etc.  When the Kodi folks created the Mionitor class in Kodi they had to give is some time increment value or be able to accept some naming standard time increment being passed (i.e. 1s, 1m, 1h etc..).  Using seconds and not requiring an increment value seems like an easy way to write solid code for them.

The question is why do you want to go faster ?  There is a downside of going faster in that it chews up more Kodi and processing resources.  Note that #3 events above are real time  but waitForAbort() activities are determined by the loop time.   It really depends upon what you are trying to do.


Thanks,

Jeff
Running with the Mezzmo Kodi addon.  The easier way to share your media with multiple Kodi clients.
Service.autostop , CBC Sports, Kodi Selective Cleaner and Mezzmo Kodi addon author.
Reply
#10
Thanks Jeff, now I get it. I don't want it to go faster, I just wanted to understand how it was working. And I see now how service_time is used as a multiplier on that for value.

Really appreciate your helping me to understand this
Reply

Logout Mark Read Team Forum Stats Members Help
Kodi addon service interval time question0