TaskTonic: Timers without waiting

In TaskTonic, blocking the execution thread is strictly prohibited. You must never block execution using methods like time.sleep(), as this will freeze the Catalyst and stop your application from processing its queue. Instead, you must use the framework's built-in timers to keep the system running smoothly.
Timers manage time continuously in the background and trigger a callback (a "sparkle") when their duration or schedule is met.
1. Using the TaskTonic timers
When using the timers be aware of the basic principles.
-
Create the timer and define the moment of expiration, to fire a timer sparkle.
-
Timers are liquid and thus part of the TaskTonic life cycle (its a
ttLiquidobject). Finishing your tonic will automatically finish the timer. Or call.finish()to stop and finish. -
For timer control you got two base method
.start()and.stop(), starting and stopping the timer. You're able to cascade this methods likeself.tmr = ttTimerRepeat(seconds=1.5).stop(). Stopping the time wil not remove them from memory. When you start a timer, the expiration time is recalculated.
A fundamental rule of TaskTonic timers is that they start immediately upon instantiation, creating the object is enough to begin the countdown.
When a timer fires, it needs to trigger an action within your Tonic. You define this action using the sparkle_back parameter. However, TaskTonic provides powerful, automatic callback generation if you omit this parameter.
Automatic Callback Routing (Name-Based Fallback)
If no sparkle_back is explicitly provided, the framework uses introspection to find the right event handler in the parent Tonic. It determines the method name based on the name property of the timer:
- Named Timers (Specific Callbacks): If you give the timer a specific
name, the framework automatically prependsttse__on_to it. For example, a timer namedtm_lights_offwill automatically launch the sparklettse__on_tm_lights_off. - Unnamed Timers (Generic Fallback): If the timer has no name, it falls back to the generic
ttse__on_timermethod.
from TaskTonic import ttTonic, ttTimerSingleShot
class SmartRoom(ttTonic):
def ttse__on_start(self):
# 1. Explicit callback (Highest priority)
ttTimerSingleShot(2.0, sparkle_back=self.ttsc__specific_action)
# 2. Named Timer (Auto-routes to ttse__on_tm_lights_off)
ttTimerSingleShot(10.0, name="tm_lights_off")
# 3. Unnamed Timer (Auto-routes to generic ttse__on_timer)
ttTimerSingleShot(5.0)
def ttsc__specific_action(self, timer_info):
self.log("The explicit 2-second timer fired!")
# Auto-generated route for the named timer
def ttse__on_tm_lights_off(self, timer_info):
self.log("Turning off the lights!")
# Generic fallback for all unnamed timers without a sparkle_back
def ttse__on_timer(self, timer_info):
self.log("The generic 5-second timer fired!")
Note: The callback method always receives a
timer_infoargument (usually a dictionary), allowing you to identify exactly which timer triggered the event if multiple timers share the generic fallback.
2. Periodic Timers: Relative Time
Standard timers deal with relative durations. You set the seconds to elapse from the moment of creation (or minutes, hours, weeks, months for longer periods).
Beside .start() and .stop() Periodic timers introduce .restart(), to stop the timer, recalculate the expiration time and start again. With .change_period() you can change the period time. This doesn't change the expiration moment, but is used when reloading the time. self.tmr.change_period(seconds=10).restart()
2.1. ttTimerRepeat (Periodic Loops)
Fires periodically based on a relative interval. Ideal for continuous background tasks like polling sensors or refreshing UIs.
# Fires every 1.5 seconds indefinitely
self.poll_timer = ttTimerRepeat(seconds=1.5, name="sensor_poll")
def ttse__on_sensor_poll(self, timer_info):
pass # Read sensor data
2.2. ttTimerSingleShot and The Watchdog Pattern
A ttTimerSingleShot fires only once. Perfect for timeouts and one time events. However, using its .restart() method, it becomes the perfect tool for building a Watchdog Timer to monitor continuous data streams (like IP connections or serial ports).
class ConnectionMonitor(ttTonic):
def ttse__on_start(self):
# Start a 10-second watchdog
self.watchdog = ttTimerSingleShot(10.0, name="connection_timeout")
self.to_state('collect')
def ttse_collect__on_data_received(self, data):
self.log("Data packet received.")
# process the data
# Restart the running timer to 10 seconds.
# As long as data flows within 10s, it never hits 0.
self.watchdog.restart()
def ttse__on_connection_timeout(self, timer_info):
# timer did hit 0, timeout error!!!
self.log("ALARM: Connection timed out!")
self.to_state('error')
Note: when a single shot expires it fires its sparkle_back and gets finished immediately. So an expired
ttTimerSingleShotcan't be used again. You have to make a new one.
2.3. ttTimerPausing
Pausing timers can also be temporarily halted. If your application enters a state where a running timer should stop counting, you can call .pause(). Calling .resume() will continue the countdown from the exact point it was paused. You can also instantiate a timer in a paused state by passing paused=True.
Note: a pausing timer that expires will immediately be paused. You have to
.resume()in your sparkle_back to use it again
3. Scheduled Timers: Absolute Time
While standard timers count seconds, Scheduled Timers deal with absolute, real-world time (calendar and clock).
ttTimerScheduled is the abstract base class and cannot be used directly. Instead, TaskTonic provides subclasses like ttTimerEveryWeek to handle any calendar scenario.
The syntax is extremely flexible, allowing integers, strings ("august", "monday"), precise time strings ("23:59:59"), and even negative indices to target the "last" occurrence (e.g., day=-1 for the last day of the month).
ttTimerEveryYear
Executes once a year at a highly specific moment.
# Absolute dates
ttTimerEveryYear(month=2, day=3, hour=8, minute=30) # fires every year at 8:30 at februari 3
ttTimerEveryYear(month="august", day=2, hour=10)
# The 'last' day of a month using negative index
ttTimerEveryYear(month="august", day=-1, hour=10)
ttTimerEveryYear(month="december", day=-1, time_str="23:59:59")
# Complex relational dates (e.g., The last Monday of February)
ttTimerEveryYear(month="february", day="monday", in_week=-1, time_str="9:56:45")
ttTimerEveryYear(month="september", day="saturday", in_week=-1, time_str="8:00:00")
# Week-number based
ttTimerEveryYear(week=1, day="tuesday", hour=8)
ttTimerEveryYear(week=3, day=6, hour=6)
ttTimerEveryYear(week=52, day=6, time_str="23:59:59")
ttTimerEveryMonth
Executes once a month. Excellent for monthly reports or billing cycles.
# Specific days within specific weeks (e.g., First Wednesday)
ttTimerEveryMonth(day="wednesday", in_week=1, hour=10)
ttTimerEveryMonth(day="wednesday", in_week=2, hour=10)
# Negative indexing for weeks (e.g., Last Wednesday of the month)
ttTimerEveryMonth(day="wednesday", in_week=-1, hour=10)
ttTimerEveryMonth(day="wednesday", in_week=-6, hour=10) # Failsafe negative boundaries
# Overflow/Underflow weeks
ttTimerEveryMonth(day="tuesday", in_week=5, hour=10)
ttTimerEveryMonth(day="tuesday", in_week=6, hour=10)
# Absolute day of the month
ttTimerEveryMonth(day=27, time_str="13:00:00")
# Relative to the end of the month
ttTimerEveryMonth(day=-1, time_str="13:00:00") # The very last day
ttTimerEveryMonth(day=-10, time_str="13:00:00") # 10 days before the end of the month
ttTimerEveryWeek
Executes weekly on a specific day.
# Using string literals (case-insensitive) or integers (0 = Monday)
ttTimerEveryWeek(day=0, time_str="23:59:59")
ttTimerEveryWeek(day="Tuesday", time_str="23:59:00")
ttTimerEveryWeek(day="wednesday", hour=1)
ttTimerEveryWeek(day="friday", hour=2)
ttTimerEveryWeek(day="saturday", hour=23)
ttTimerEveryDay
Executes every day exactly when the system clock hits the target time.
ttTimerEveryDay(hour=0)
ttTimerEveryDay(hour=5, minute=30,seconds=0)
ttTimerEveryDay(time_str="12:15:00")
ttTimerEveryDay(hour=19)
ttTimerEveryHour & ttTimerEveryMinute
Instead of full timestamps, these synchronize with the clock rolling over.
# Triggers at exactly 15 minutes and 0 seconds past every hour (e.g., 01:15, 02:15, 03:15)
ttTimerEveryHour(time_str="15:00")
ttTimerEveryHour(minutes=15)
# Triggers at specific seconds within every minute
ttTimerEveryMinute(second=0) # Exactly at the top of the minute
ttTimerEveryMinute(second=15)
ttTimerEveryMinute(second=30)
ttTimerEveryMinute(second=2.5) # with seconds, floats are allowed
Example: Implementing a Scheduled Timer
from TaskTonic.ttTonicStore import ttTimerEveryWeek
class BackupService(ttTonic):
def ttse__on_start(self):
self.log("Backup service initialized.")
# Schedule the backup for every Sunday at 02:00 AM using a named timer
ttTimerEveryWeek(
day="sunday",
hour=2,
name="weekly_backup"
)
# Automatically called by the timer named 'weekly_backup'
def ttse__on_weekly_backup(self, timer_info):
self.log("Executing weekly database backup...")
# Place backup logic here