Timers & Scheduling
Table of contents
The std module provides three databases for time-based triggering. They serve different use cases:
Choosing the Right Timer
timer.db | countDownTimer.vdb | alarmClock.vdb | |
|---|---|---|---|
| Trigger condition | Elapsed time reaches preset | Count reaches target | Current time reaches set date/time |
| Resolution | 0.1 second | 1 second | 1 second |
| Time reference | Elapsed time (relative) | Count-based (relative) | Wall clock (absolute) |
| Date awareness | No | No | Yes (month, day, year, leap years) |
| Busy record | Yes | No | No |
| Scan integration | Yes (ca_put_callback) | No | No |
| Preset unit | Seconds (floating point) | Hours, minutes, seconds | Date and time fields |
| Count direction | Elapsed + remaining | Up or down (selectable) | N/A (comparison-based) |
| Reset behavior | Resets on Go | Reloads on Start | Re-arms on Enable toggle |
When to use each
-
timer.db– General-purpose timer with scan system integration. Use when you need an action after a precise delay and wantca_put_callbacksupport (e.g., timed delays in scan sequences). ThedoStopforward link is explicitly provided for user actions. -
countDownTimer.vdb– Simple countdown (or count-up) timer. Use when you need a visual hours:minutes:seconds countdown display. Often used as a building block by other databases (e.g.,autoShutter.vdbuses two instances). -
alarmClock.vdb– Scheduled event trigger. Use when an action must occur at a specific date and time (e.g., “close the shutter at 6:00 AM on Monday”). Includes full date/time entry with range validation and leap-year awareness.
timer.db
A general-purpose elapsed/countdown timer with busy record support.
Macros
| Macro | Description |
|---|---|
P | PV prefix |
N | Timer instance number |
Records
| Record | Type | Purpose |
|---|---|---|
$(P)timer$(N):Go | bo | Start (1) / Stop (0) control |
$(P)timer$(N):preset | ao | Timer duration in seconds |
$(P)timer$(N):elapsedSecs | ao | Elapsed time readback |
$(P)timer$(N):remainingSecs | ao | Remaining time readback |
$(P)timer$(N):currentTime | stringin | Current date/time string |
$(P)timer$(N):startTime | stringin | Captured start timestamp |
$(P)timer$(N):doStop | bo | Stop trigger (user should set FLNK) |
$(P)timer$(N):busy | busy | Busy record for scan synchronization |
$(P)timer$(N):resetOnGo | bo | Resets elapsed time on start |
$(P)timer$(N):secsPastEpoch | ai | Epoch time source |
$(P)timer$(N):calcElapsed | transform | Core timing engine |
$(P)timer$(N):doneCalc | calcout | Completion detector |
$(P)timer$(N):update | bo | 0.1s scan driver (while running) |
$(P)timer$(N):updateCurrent | bo | 1s scan driver (while stopped) |
$(P)timer$(N):scanFanout | fanout | Triggers time reads |
$(P)timer$(N):setBusy | calcout | Sets busy on Go |
$(P)timer$(N):clearBusy | bo | Clears busy on done |
$(P)timer$(N):doStop1 | bo | Internal stop chaining |
How It Works
- Writing 1 to
Goresets the timer, captures the start time, and sets thebusyrecord. - The
updaterecord scans at 0.1 seconds (only whileGo=1) and triggers the timing engine. calcElapsedcomputes elapsed time (current epoch - start epoch) and remaining time (preset - elapsed).- When remaining time reaches 0.05 seconds or less,
doneCalcfires:- Processes
doStop(which the user can chain to their action via FLNK) - Clears the
busyrecord - Sets
Goback to 0
- Processes
- The
busyrecord integrates withsscanandca_put_callbackfor timed delays in scan sequences.
User Action on Timer Expiry
The doStop record’s FLNK is intentionally left empty. Set it to the PV you want processed when the timer expires:
caput xxx:timer1:doStop.FLNK "xxx:myAction.PROC"
Or configure via autosave so it persists across IOC reboots.
Example
dbLoadRecords("$(STD)/stdApp/Db/timer.db", "P=xxx:,N=1")
Autosave
Use timer_settings.req (or timer.req) with macros P=$(P), N=$(N). Saves preset, Go state, update scan rate, doStop FLNK, start time, and latched epoch.
countDownTimer.vdb
A count-based timer that increments or decrements a counter every second.
Macros
| Macro | Description |
|---|---|
P | PV prefix |
N | Timer instance number |
Records
| Record | Type | Purpose |
|---|---|---|
$(P)cdt$(N):start | bo | Start (1) / Stop (0) |
$(P)cdt$(N):upDown | bo | Direction: UP (0) / DOWN (1, default) |
$(P)cdt$(N):setTimeHrs | ao | Preset hours |
$(P)cdt$(N):setTimeMin | ao | Preset minutes |
$(P)cdt$(N):setTimeSec | ao | Preset seconds |
$(P)cdt$(N):timeHrs | ao | Current hours (display) |
$(P)cdt$(N):timeMin | ao | Current minutes (display) |
$(P)cdt$(N):timeSec | ao | Current seconds (display) |
$(P)cdt$(N):count | calcout | Core counter (SCAN toggled dynamically) |
$(P)cdt$(N):countReset | calcout | Loads preset on start |
$(P)cdt$(N):stopStart | calcout | Start/stop logic (writes to count.SCAN) |
$(P)cdt$(N):trigger | calcout | Detects stop transition |
$(P)cdt$(N):startReset | calcout | Resets start flag when done |
$(P)cdt$(N):reset | calcout | Converts H:M:S to total seconds |
$(P)cdt$(N):parse | transform | Converts total seconds back to H:M:S |
How It Works
- The user sets hours, minutes, and seconds. The
resetrecord converts these to total seconds. - Writing 1 to
starttriggerscountReset, which loads the total seconds intocount(for countdown) or 0 (for count-up). stopStartsetscount.SCANto"1 second"(scan index 6), enabling periodic processing.- Each second,
countadds -1 (down) or +1 (up) to its current value. parse(linked via CP) converts the current count to hours, minutes, and seconds for display.stopStart(linked via CP) monitors the count. When it reaches 0 (countdown) or the preset (count-up), it writes 0 tocount.SCAN, stopping the timer.triggerdetects the stop transition andstartResetclears thestartflag.
Example
dbLoadRecords("$(STD)/stdApp/Db/countDownTimer.vdb", "P=xxx:,N=1")
alarmClock.vdb
A date/time alarm clock that triggers when the current time reaches a user-specified date and time.
Macros
| Macro | Description |
|---|---|
P | PV prefix |
N | Alarm clock instance number |
Key Records
| Record | Type | Purpose |
|---|---|---|
$(P)AClock$(N):Enable | bo | Master enable (Disable/Enable) |
$(P)AClock$(N):Trigger | calcout | Fires when current time >= set time |
$(P)AClock$(N):curTimeDate | stringin | Current date/time (1-second scan) |
$(P)AClock$(N):Set:mm | ao | Set month (1-12) |
$(P)AClock$(N):Set:dd | ao | Set day (1-31, validated per month) |
$(P)AClock$(N):Set:yy | ao | Set year (0-99, 2-digit) |
$(P)AClock$(N):Set:HH | ao | Set hour (0-24) |
$(P)AClock$(N):Set:MM | ao | Set minute (0-60) |
$(P)AClock$(N):Set:SS | ao | Set second (0-60) |
$(P)AClock$(N):UD:mm | bo | Month up/down button |
$(P)AClock$(N):UD:dd | bo | Day up/down button |
$(P)AClock$(N):UD:yy | bo | Year up/down button |
$(P)AClock$(N):UD:HH | bo | Hour up/down button |
$(P)AClock$(N):UD:MM | bo | Minute up/down button |
$(P)AClock$(N):UD:SS | bo | Second up/down button |
$(P)AClock$(N):Chk:mm … :SS | transform | Range validation with wrap-around |
$(P)AClock$(N):TOD:yy … :SS | scalcout | Time-of-day field parsers/comparators |
$(P)AClock$(N):Set | scalcout | Alarm time display string |
$(P)AClock$(N):SetNow | sseq | Set alarm to current time |
The database contains approximately 33 records total.
How It Works
-
The user sets a target date and time using the
Set:records (directly or via the up/down buttons). -
Range-check transforms (
Chk:) validate each field. Values wrap around at boundaries (e.g., month 13 wraps to 1). The month check dynamically adjusts the day limit based on the month and leap year status. -
Every second,
curTimeDatereads the current time as a formatted string (mm/dd/yy HH:MM:SS). - Six
TOD:scalcout records parse individual fields from the time string and compare them to the corresponding set values. Each produces a cascading comparison result:0= current is before set (alarm not reached for this field)1= current equals set (check the next finer field)2= current is past set (alarm already passed)
If a coarser field (e.g., month) returns
2, all finer fields (day, hour, minute, second) automatically return2. -
The
Triggercalcout ANDs all comparison results with the Enable flag. It fires on “Transition To Non-zero” – exactly once when the alarm time is reached. - Configure
Trigger.OUTorTrigger.FLNKfor the desired alarm action.
Resetting the Alarm
The trigger fires on “Transition To Non-zero”, so it will only fire once. To re-arm:
- Toggle
Enableoff and back on - Change the set time to a future value
- Use
SetNowto reset to the current time, then adjust forward
Example
dbLoadRecords("$(STD)/stdApp/Db/alarmClock.vdb", "P=xxx:,N=1")
Then configure the alarm action at runtime:
caput xxx:AClock1:Set:mm 12
caput xxx:AClock1:Set:dd 25
caput xxx:AClock1:Set:yy 25
caput xxx:AClock1:Set:HH 6
caput xxx:AClock1:Set:MM 0
caput xxx:AClock1:Set:SS 0
caput xxx:AClock1:Enable 1