Clocks and Timers
Applies to:
All variants
Introduction
The VBOX Touch, and its variants, all have an inbuilt Real-Time Clock (RTC) that can be adjusted using your own python program.
From this there are also multiple types of timers that can be used for callbacks or more simply to record how long it has taken to do something. One example is using them to stop an operation if this operation has not had a response in a long amount of time.
Clock
Get the time
We can receive the internal time from the RTC using the clock_get() function in the vts module. This returns a dictionary of the data it currently has for date and time.
import vts
vts.clock_get()
# Returns:
# {'day of year': 265, 'minutes': 21, 'day of month': 22, 'year': 2022, 'day of week': 4, 'month': 9, 'seconds': 52, 'hours': 10}
Most of these data values are self-explanatory but they have been listed below in case:
day of year = Amount of days passed this year + a day (for today)
minutes = Current minute
day of month = Current day that you would have in a date.
year = Current year
day of week = Current day of the week (Monday=1, …, Sunday=7). However, this is often not set.
month = Current month
seconds = Current second
hours = Current hour
Note:
The internal RTC is not a perfect clock and is not capable of correcting data that is wrong on its own and relies on user input. Hence, the data you get is often inaccurate. This is normally due to a slow loss of accuracy (as RTCs can gain inaccuracy of 1.7 to 8.6 seconds per day). However, it may have been set wrong last time it was used.
Set the time
As mentioned above the RTC time can often skew, and as such you will want to set it again to a more accurate value. In the vts module there is the clock_set() function to make this easier to do. It takes an input parameter of a dictionary in the same format as the one received from the clock_get() function. Because of this, it is often easiest to get the clock and then change the terms by referencing them using the keys. For example:
clock_time = vts.clock_get()
clock_time["hours"] = 14
vts.clock_set(clock_time)
This would change the hours time to be in 14 on the RTC. You can also hard code the values as seen below:
clock_time = {'day of year': 265, 'minutes': 21, 'day of month': 22, 'year': 2022, 'day of week': 4, 'month': 9, 'seconds': 52, 'hours': 10}
vts.clock_set(clock_time)
These values are an approximation, if you want to set a more accurate time you should take data from a GNSS sample.
However, the data returned is in a different format to the dictionary you need to set the RTC, so you are going to need to convert some parts. Below are examples of functions that you can use to do the conversions, you may choose to write your own instead if you want.
The first conversion function will convert from time of day (tod) in milliseconds to hours, minutes, seconds and milliseconds.
def tod_to_hmsm(ms):
hmsm = 4 * [None]
hmsm[0] = ms // 3600000
ms -= (hmsm[0] * 3600000)
hmsm[1] = ms // 60000
ms -= (hmsm[1] * 60000)
hmsm[2] = ms // 1000
ms -= (hmsm[2] * 1000)
hmsm[3] = ms
return hmsm
This returns a list in the order [hours, minutes, seconds, milliseconds].
The next conversion will find the day of the week. This uses a mathematical equation called Zeller’s Rule.
def day_of_week(q, m, J, K):
# This uses Zeller's Rule
h = (q + ((13*(m+1))//5)+ K + (K//4) + (J//4) - (2*J))%7
day = ((h+5)%7)+1
return day
This will return the day of the week in where: Monday=1, …, Sunday=7.
Finally to convert the date to the current day of the year use the below function. This function does account for leap years.
def day_of_year(k, m, y):
# This uses Zeller's Rule
day = 0
month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
# check if leap year
if ((y%4) == 0):
if ((y%100) == 0) and ((y%400) != 0):
pass
else:
day+=1
# add the number of days as months there have been
for num in range(0,m-1):
day += month_days[num]
# add the number of days that have passed this month
day+=k
return day
This returns the number of days that have passed this year.
These can all be combined and used to set the RTC. You should only do this once more than 3 sats are connected as this will ensure your data is accurate.
import vts
import vbox
import gnss
# ---------- Used to convert from time of day in ms after midnight to hours, minutes and seconds ---------- #
def tod_to_hmsm(ms):
hmsm = 4 * [None]
hmsm[0] = ms // 3600000
ms -= (hmsm[0] * 3600000)
hmsm[1] = ms // 60000
ms -= (hmsm[1] * 60000)
hmsm[2] = ms // 1000
ms -= (hmsm[2] * 1000)
hmsm[3] = ms
return hmsm
# ---------- Used to find the day of the week ---------- #
def day_of_week(q, m, J, K):
# This uses Zeller's Rule
h = (q + ((13*(m+1))//5)+ K + (K//4) + (J//4) - (2*J))%7
day = ((h+5)%7)+1
return day
# ---------- Used to find what the number of the current day is relative to the start of the year ---------- #
def day_of_year(k, m, y):
# This uses Zeller's Rule
day = 0
month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
# check if leap year
if ((y%4) == 0):
if ((y%100) == 0) and ((y%400) != 0):
pass
else:
day+=1
# add the number of days as months there have been
for num in range(0,m-1):
day += month_days[num]
# add the number of days that have passed this month
day+=k
return day
# ---------- return value in dictionary form ---------- #
def clock_from_sats():
sample = vbox.get_sample_hp()
while sample.sats_used < 3:
vts.delay_ms(100)
sample = vbox.get_sample_hp()
month = sample.month
year = sample.year
if sample.month <= 2:
month+=12
year-=1
time = tod_to_hmsm(sample.tod_ms)
day = day_of_week(sample.day, month, int(str(year)[0:2]), int(str(year)[2:4]))
year = day_of_year(sample.day, sample.month, sample.year)
return {'day of year': year, 'minutes': time[1], 'day of month': sample.day, 'year': sample.year, 'day of week': day, 'month': sample.month, 'seconds': time[2], 'hours': time[0]}
while (gnss.init_status() > 0):
pass
try:
vbox.init(vbox.VBOX_SRC_GNSS_BASIC)
except Exception as e:
if str(e) == 'VBox source already configured':
pass
else:
raise Exception(str(e))
print("CLOCK BEFORE: {}".format(vts.clock_get()))
vts.clock_set(clock_from_sats())
print("CLOCK AFTER: {}".format(vts.clock_get()))
Note:
If the amount of sats connected to the VBOX Touch does not surpass 3 then this program will not finish running and never set the time.
Datetime
This is used with the clock functions to convert, calculate and adjust times. This is very useful when you are trying to calculate the difference between 2 times that are on different days. This most often comes up when there is a UTC offset as this can often lead to the time going over the midnight threshold and into the next day.
Note:
The datetime it takes is in a different structure to that of vts.clock_get(). It takes a list or tuple of (year, month, day, hour, minute, second)
Julian days conversion
Doing calculations in datetime can be complex and create some complex equations so instead converting back between Gregorian and Julian is preferable.
This is done using the following 2 functions:
vts.datetime_to_jdn(datetime)
This takes a datetime dictionary as input and returns the equivalent in Julian days.
vts.jdn_to_datetime(Julian)
This takes Julian days as input and returns the equivalent in a datetime dictionary.
Calculations
2 calculations can be done using datetime functions. These find the difference between 2 datetimes and adjust a datetime by a certain amount of seconds.
vts.datetime_diff(datetime1, datetime2)
This will return the difference between these 2 values in seconds
vts.datetime_adjust(datetime, milliseconds)
This will return the new datetime with adjusted values.
Note:
If you put in a value equal to a second it will only adjust it by -1 of that. For example: 2000 will adjust by 1 second but 2001 will adjust by 2 seconds
This function is especially useful when calculating leap seconds and changes from time zones. This is because you can use it with the clock functions like this:
import vts
clock_datetime = vts.clock_get()
new_datetime = vts.datetime_adjust([clock_datetime["year"], clock_datetime["month"], clock_datetime["day of month"], clock_datetime["hours"], clock_datetime["minutes"], clock_datetime["seconds"]], 100001)
clock_datetime["year"] = new_datetime[0]
clock_datetime["month"] = new_datetime[1]
clock_datetime["day of month"] = new_datetime[2]
clock_datetime["hours"] = new_datetime[3]
clock_datetime["minutes"] = new_datetime[4]
clock_datetime["seconds"] = new_datetime[5]
vts.clock_set(clock_datetime)
This would set the RTC on the VBOX Touch to be 100 seconds ahead of where it was before then.
Delays and Timers
Delays
Sometimes you will need to have a temporary delay in your program. For this we can use the delay functions. These are used to stop the micropython application for the length of time specified. This does not stop callbacks unless you are delay whilst in a callback.
Warning
Callbacks will not be called if you are delaying within a callback. Hence it is not advised to do this in a loop and only to do a very short delay if needed as otherwise you may get stuck in the callback and miss other callbacks.
There are 2 delay functions in the vts library. They both take an integer input as a parameter and however the length of the delay from that integer depends on the scale of time they are using.
delay_ms
This function delays by milliseconds(ms). It is the more frequently used one out of the 2 functions as delaying for microseconds is a very short amount of time.
vts.delay_ms(time_in_ms)
delay_us
This function delays by microseconds(us).
vts.delay_us(time_in_us)
Timers
Chrono timers
Similarly to delay, there are 2 types of Chrono Timer: MChrono and Chrono. They are different by the unit of time they use with MChrono using ms (milliseconds) and Chrono using us (microseconds).
MChrono also has an extra function called time_since_ms().
To use a Chrono timer you have to create a Chrono timer object by instantiating it.
Chrono_Timer = vts.Chrono() # in microseconds (us)
MChrono_Timer = vts.MChrono() # in milliseconds (ms)
From here you can start this timer. This will start it recording time from 0.
# This starts the timer from 0
Chrono_Timer.start()
MChrono_Timer.start()
You can then restart this timer whenever you wish using the restart() function. This will also return the current value of the Timer.
# This starts the timer from 0 and returns the current value of the timer.
Chrono_Timer.restart()
MChrono_Timer.restart()
You can get the current time passed since a start/restart using the read() function which returns the time in the unit of the Timer.
Chrono_Timer.read()
MChrono_Timer.read()
There is also a function to show how long the VBOX Touch has been running which is the time() function. This returns how long the VBOX Touch has been running:
Chrono_Timer.time()
MChrono_Timer.time()
You can also compare 2 times using the time_since_ms() function.
num = 1
MChrono_Timer.time_since_ms(num)
This returns how long it has been since num. This value is not affected by restart/start and instead uses (time() - (start_time+num)).
Timer
The Timer object is used to check how many occasions a period of time has passed. You can have a maximum of 32 Timers, with a maximum of 8 Timers that have a callback.
To create a Timer object you need to instantiate it and pass it 2 parameters, the first is an int value which is a time in milliseconds (ms), every time it reaches this time it will increment a counter. The second is whether the time should stop triggering callbacks after the first time it reaches the value.
Example_Timer = vts.Timer(3000, True)
You have now created a Timer object. Timer objects have the ability to callback when their time is reached. To get it to callback to a function of your choice use the set_callback() function.
Example_Timer.set_callback(func)
This will trigger each timer counter is incremented which occurs whenever the time specified in your initialisation is met. In this case that is every 3000ms or every 3s. You can get how many times this counter has been incremented too, using the counter() function.
Example_Timer.counter()
This returns how often the time value has been reached since the Example_Timer was created.
Sometimes when you are stuck in other callbacks you will miss an Timer callback. Hence, there is the pending() function to double check this:
Example_Timer.pending()
This will return a boolean of whether or not it is pending.
When you want to no longer use this Timer or you want to reset it you need to remember to destroy it first using the destroy() function.
Example_Timer.destroy()
Warning:
If you do not destroy a timer before overriding the variable then you will cause a core panic when garbage collection next occurs.
You can also destroy all the Timer objects using the destroy_all() function which is a class function so do not call this from an object.
vts.Timer.destroy_all()
Further examples
Face Ranging - Uses both MChrono (for automatically stopping) and delays (to account for processing time). Displays data from a serial stream from a separate device.