Core Service - Scheduler
From Digi Developer
Most DIA devices will do things only from time to time. The DIA framework includes a simple callback event scheduler which enables callbacks to your device object in future times. This enables your device to do some action every X seconds, or react to other events failing to occur within Y seconds.
- Example #1 - periodic polling
- you need to poll a remote device once per 5 minutes.
- at startup, schedule an initial poll_callback in perhaps 30 seconds or 1 minute (delayed to reduce startup congestion)
- during the poll_callback
- reschedule a new poll_callback in 5 minutes
- send your poll - you CANNOT wait for a response or you affect scheduling jitter (see Timing and Jitter below)
- schedule a response_callback in 10 second (note: some means of polling may cause an automation callback upon data receipt. Use of a separate response_callback is situation dependent.
- during the response callback
- handle any response data, or a timeout/no response
- handle any retry?
- Example #2 - detecting when data not being received
- your remote XBee adapter should send a new sample every 1 minute, you want to catch if no data has arrived in over 2 minutes.
- at startup, schedule a no_data_callback 125 seconds (over 2 minutes). Assuming the XBee device is already configured, it should return its first data sample within the next 60 seconds.
- during your data receive callback:
- cancel the old pending no_data_callback
- turn off any no_data alarm or signal that data is arriving
- reschedule a new no_data_callback for 125 seconds in the future
- during the no_data_callback:
- signal your data alarm as desired
- reschedule a new no_data_callback for the future - you may want it to be 30 times the expected data rate, so perhaps in 30 minutes or even several hours. The first good data sample received will clear the alarm condition and restore the no_data_callback to 125 seconds
Timing and Jitter
The DIA is not a hard real-time system. Scheduling a callback in 5 or even 5000 seconds does not mean you'll get a callback at the exact time requested. A single thread is doing these callbacks, plus your callback runs in the context of that thread. That is why in the Example #1 above we are not supposed to wait for the poll. If it takes 6 seconds for a response, then any other DIA code expecting callback during those 6 seconds will NOT be called until after your response.
Your code must be a good-citizen. Never do anything in your callback which doesn't complete immediately.
If you have multiple callbacks due to multiple sources, they may be running in different thread contexts. For example, if you send a poll based on the 'scheduler' callback, but receive the data callback based on the XBee_Serial driver or other mechanism, then you may need to use semaphores (like threading.lock object) or the python queue object. Without this you might find that a new poll callback is called WHILE an older data callback is processing.
The simple DIA device file below shows examples of both uses of the scheduler.
The first 'kid' callback functions like the poll example above. It is rescheduled every 5 seconds for the first 25 seconds.
The second 'mom' callback functions like the no_data example above. It is also rescheduled every 5 seconds, but is to trigger at 10 seconds. This allows the 'kid' callback to cancel, so suppress the triggering of the 'mom' callback until the 'kid' gets tired of hiding from 'mom.
Download a Zip
C:\py\dia\work>python dia.py bin\dia.yml Determining platform type... PC host environment assumed. iDigi Device Integration Application Version 1.3.8 Using settings file: dia.yml Core: initial garbage collection of 0 objects. Core: post-settings garbage collection of 28 objects. Starting Scheduler... Starting Channel Manager... Starting Device Driver Manager... family: Starting at Wed Aug 18 08:59:14 2010 Starting Presentation Manager... Starting Services Manager... Core services started. Kid says: (0, 'call 1') at Wed Aug 18 08:59:19 2010 Mom can't find me! Kid says: (1, 'call N') at Wed Aug 18 08:59:24 2010 Mom can't find me! Kid says: (2, 'call N') at Wed Aug 18 08:59:29 2010 Mom can't find me! Kid says: (3, 'call N') at Wed Aug 18 08:59:34 2010 Mom can't find me! Kid says: (4, 'call N') at Wed Aug 18 08:59:39 2010 Where's Mom? Mom says: that's it - time for bed! family: Stopping at Wed Aug 18 08:59:44 2010
This YML assumes your device code is in the src\devices\experimental directory.
devices: - name: family driver: devices.experimental.blog_sched:BlogSchedDevice
Full Python Code
# blog_sched.py # - simple DIA device to show use the Core Services scheduler # # by Lynn August Linse, 2010-Aug-18 # imports from devices.device_base import DeviceBase from settings.settings_base import SettingsBase, Setting import traceback import time class BlogSchedDevice(DeviceBase): def __init__(self, name, core_services): self.__name = name self.__core = core_services # we don't have any settings or properties ## Initialze the Devicebase interface: DeviceBase.__init__(self, self.__name, self.__core, , ) return def apply_settings(self): """Called when new configuration settings are available.""" SettingsBase.merge_settings(self) accepted, rejected, not_found = SettingsBase.verify_settings(self) SettingsBase.commit_settings(self, accepted) return (accepted, rejected, not_found) def start(self): """Start the device driver. Returns bool.""" print '%s: Starting at %s' % (self.__name, time.ctime()) try: # Fetch the Scheduler Service self.sched = self.__core.get_service('scheduler') # schedule first callback in 5 seconds - args just for show self.kid = self.sched.schedule_after( 5, self.__kid_call, (0,'call 1')) # schedule second callback in 10 seconds # - course, naughty kid cancels poor mom 5 time before letting her run self.mom = self.sched.schedule_after( 10, self.__mom_call, ('time for bed')) self.count = 0 except: traceback.print_exc() print 'Scheduler Failed!' return False return True def stop(self): """Stop the device driver. Does nothing but returns True.""" print '%s: Stopping at %s' % (self.__name, time.ctime()) return True ## Locally defined functions: def __kid_call( self, args): """first callback event""" print '\nKid says: %s at %s' % (args, time.ctime()) self.count += 1 if self.count < 5: print " Mom can't find me!" # cancel the pending second event callback self.sched.cancel( self.mom) # recreate our two new events in future self.kid = self.sched.schedule_after( 5, self.__kid_call, (self.count,'call N')) self.mom = self.sched.schedule_after( 10, self.__mom_call, ('time for bed')) else: # do NOT cancel the pending second/Mom event, which will trigger in a few seconds # do NOT reschedule the first/Kid event - it stops forever at this point! print " Where's Mom?" return def __mom_call( self, args): """second callback event""" print "\nMom says: that's it - %s!" % args self.stop() return