- Edited
Do you need the current date or a monotonic time since the start of the script? The latter can be obtained with the [timer]
object.
Do you need the current date or a monotonic time since the start of the script? The latter can be obtained with the [timer]
object.
You can also do this:
copy this zip archive to the board, it provides pdsend which allows to send messages to pd from outside Pd.
Then run
unzip pdutils.zip
mv pdsend pdreceive /usr/local/bin
Then create a file like this called get_time.sh
in your project:
#!/bin/bash
# do magic here to get time into variable TIME
TIME=$(date +%Y.%m.%d-%T)
# send TIME to Pd's netreceive
echo $TIME | pdsend 9999 localhost udp 2>/dev/null
Then you can have a patch like the attached one (with a suitable interval in the metro):
This patch executes get_time.sh which in turn sends the current time to pd via pdsend.
Nice workaround!
But since I need a bang on every second, should I write a shell script (or whatever) that sends directly the time on seconds to Pd?
sure that's also easy enough: put something like this in a .sh file and use this procedure to run it in the background: https://learn.bela.io/using-bela/bela-techniques/running-a-program-as-a-service/
#!/bin/bash
while sleep 1; do
# do magic here to get time into variable TIME
TIME=$(date +%Y.%m.%d-%T)
# send TIME to Pd's netreceive
echo $TIME | pdsend 9999 localhost udp 2>/dev/null
done
ok,
I finally wrote a pd object (my first one, be indulgent).
But let's say it immediately: it doesn't work very well, I get an error on each call (bang):
[n] mode switches detected on the audio thread.
(n begin at 1 and is incremented on each error)
Here is the compiled object for Bela, and source code. It is based on time.h. Maybe you have an idea on how to improve. It works on linux.
https://www.dropbox.com/scl/fi/7sxt9fgy1ehd8ex4znzd5/ahora.pd_linux.zip?rlkey=jk5d8he4jh274rxwrxk2vpu9j&dl=0
The object [ahora n] works as follow:
outlets, floats, from left to right:
-year
-month
-day
-hour
-min
-sec
arg: number between 1 - 5. The number of active outlet active when inlet banged, from left to right (ex: 3 means hour,min,sec). None, 0 or >5 means all outlets actives.
inlet:
-bang: outputs time and date following creation arg.
-message year, month, day, hour, min, sec outputs corresponding element, regardless of creation arg
Nice one, it probably works just fine, the issue is that time()
will call into the Linux kernel and as it is called from within the audio thread it breaks the real-time guarantees of Bela, hence the "mode switch" warning.
You have several options:
time()
in there, have the audio thread print the result that was obtained from the other thread. If you don't want the other thread to be polling all the time, you'll need to trigger a time()
call in the other thread when you receive a bang, then set a pd timer (I believe that's what it's called?) a few ms later that reads the up-to-date result of time when it's ready and sends it out of the output. This is the most generally-applicable version, which will also improve real-time friendliness of your external when running outside Bela. It may get complicated because on a multi-core processor you'll need some way of guaranteeing memory coherence so that the audio thread never sees a corrupted version of the time and has a way of knowing for sure whether it's been updated. time()
, call __wrap_time()
. This is a function provided by Xenomai's libcobalt that is safe to call from the audio thread. Before you call it you may have to declare it (depending on your compiler's options):
extern time_t __wrap_time(time_t *tloc);
I don't think you have to change your linker flags. You should be able to build it like this and at runtime it will be resolved because libcobalt is already loaded by the Bela binary.[timer]
object to measure elapsed time while the program is running. I appreciate this may have accuracy implications for long running patches (which you can obviate to by resetting the timer every so often) and if you need the year/month/day/hour/min/sec the previous option may be easier.giuliomoro I appreciate this may have accuracy implications for long running patches
and the running time will be... 5 years!!! ;-D (that is the time we have to guarantee the installation)
I'll check your propositions and try one... Thank you!!
ok, I've just tried __wrap_time(). Doesn't work without extern time_t __wrap_time(time_t *tloc);
. It works with it, but same errors. It seems localtime
is guilty...
I will try the thread way later, but if that one works it would be nice too...
Weird, it should work. Did you replace all instances of time()
? Is there any other function call you are making that could be responsible?
Try to remove localtime and see if the mode switches persist
a summary:
Original code calls time() and localtime(), and generates errors
If I only call time() (with a comment on localtime()), I get the the errors as well, and obviously 0's on outputs.
If I call __wrap_time() without localtime(): NO errors!
But with __wrap_time() with localtime(), errors again.
It means both time() and localtime() are guilty. I couldn't find a way to wrap localtime the same way...
rph-r But with __wrap_time() with localtime(), errors again.
Ok that's what I was suspecting . It looks like xenomai doesn't provide a wrapper foe localtime.
Try using localtime_r() instead and that may avoid the kernel call?
ok yes, no more mode switch!
But I've just noticed time is not right: time() gives 1731277487 and __wrap_time() gives 7912 !?
OK I verified that that's the case. It seems to have to do with the reference clock in use.
I verified that __wrap_clock_gettime(CLOCK_REALTIME, ...)
or __wrap_clock_gettime(CLOCK_MONOTONIC, ...)
give a similar result. However, there seems to be a CLOCK_HOST_REALTIME
defined by Xenomai which seems to return the correct date. That define evaluates to 32, so you could do this:
extern int __wrap_clock_gettime(clockid_t clk_id, struct timespec *tp);
...
struct timespec tp;
__wrap_clock_gettime(32, &tp);
secondes = tp.tv_sec;
...
this should give you the desired result.
BTW, there's plenty of duplicated code in your external that you may want to tidy up all in one place (namely, the calls to time() and localtime_r()). Also, it looks like when you call ahora_bang()
the behaviour is to output values from all outputs starting from the one set in the creation argument. Is that expected/desired or are should you have a break
at the end of each case
statement?
giuliomoro Also, it looks like when you call ahora_bang() the behaviour is to output values from all outputs starting from the one set in the creation argument. Is that expected/desired or are should you have a break at the end of each case statement?
The compiler too warned me about that, but it is the purpose: you choose to trigger the last n outputs. I confess it is a bit redundant since you can also trigger the desired outputs banging messages... But for who wants only the time, most of user a guess, it's convenient.
I'll check about duplicated code, and try CLOCK_HOST_REALTIME...
It's worth mentioning that for any of this to yield accurate results over time you'll need to have Bela connected to the internet or a local ntp server so that it can keep its clock up to date...
giuliomoro It's worth mentioning that for any of this to yield accurate results over time you'll need to have Bela connected to the internet or a local ntp server so that it can keep its clock up to date...
sure... and setup timezone:
#timedatectl set-timezone [timezone name]
found with:
#timedatectl list-timezones
It is working finally, thank you! I confess I'm not sure I understood everything I did with xenomai, but I'll dig that...
here is the code and the bin
you forgot the link?
The factoring out of x->getTime()
looks good.
There are some errors in the file, it won't build as is. Among others, you are forward-declaring __wrap_time()
but then using __wrap_clock_gettime()
.
rph-r I confess I'm not sure I understood everything I did with xenomai, but I'll dig that...
__wrap_SOMEFUNCTION()
calls the Xenomai version of SOMEFUNCTION (if available). The forward declaration (extern someret __wrap_SOMEFUNCTION(someargs)
) is a lazy workaround so that you don't need to find the correct file to include and add it to the include path or modify the #include
statements).
Now the issue we encountered is that Xenomai's time()
or clock_gettime(CLOCK_REALTIME, ...)
gives unexpected results (it probably reports the time since boot?). This is probably a bug in Xenomai (or a feature, but I can't find it docuemented) and propagates to the use of time()
- which also uses CLOCK_REALTIME
under the hood. By trial and error I figured out that CLOCK_HOST_REALTIME
yields correct results. However, time()
doesn't support specifying the clock, so we have to use clock_gettime(CLOCK_HOST_REALTIME, ...)
instead, and extract the seconds from the value it returns. As CLOCK_HOST_REALTIME
is defined in the Xenomai include, we once more work around it by hardcoding its value 32
.
Now that things work, if you wanted to tidy things up in a way that your external can be built on any platform but picks up Xenomai includes and functions if available, you could do the following:
CPPFLAGS=-I/usr/xenomai/include/cobalt -I/usr/xenomai/include
This will tell it to look for the Xenomai includes and it will pick up time.h
from there, declaring __wrap_clock_gettime() and defining CLOCK_HOST_REALTIME
#ifdef CLOCK_HOST_REALTIME
// Xenomai clock, if available
__wrap_clock_gettime(CLOCK_HOST_REALTIME , &tp);
#else
clock_gettime(CLOCK_REALTIME , &tp);
#endif
This is a bit nicer and doesn't require much platform detection: on most systems, /usr/xenomai/
won't exist and so the include paths you set in CPPFLAGS
will be ignored. Note: in principle a change like this would also require to add a library to the list of shared libraries to link the external against. However, we rely on the host (the Bela core in this case) to load that library for us. That's not as neat, but it allows to keep things simple in the Makefile
.
Further refactoring could see using a class_addanything
instead of individual methods for the various year, month, day etc, but as that's so poorly documented (i.e.: undocumented) in Pd I don't blame you.
It would probably look something like:
void ahora_anything(t_ahora *x, t_symbol* s, int argc, t_atom* argv)
{
x->get_time(x);
if(s == gensym("year"))
outlet_float(x->year_out , 1900+x->instant.tm_year) ;
else if(s == gensym("month"))
outlet_float(x->month_out , 1+x->instant.tm_mon) ;
else if ...
//
else
pd_error(x, "no method for %s\n", s->s_name);
}
// in ahora_setup(), instead of all the individual ahora_addmethod() calls:
class_addanything(ahora_class, (t_anymethod)ahora_anything);
Not sure if's worth anyone's time, but it would look nicer -- if it works. I hope the bang
method still works, otherwise you need to add it as a case in the chain of ifs in ahora_anything()
.
Btw, you may also want to make the creation argument a symbol instead of a float and convert that to a number internally before storing it into x->whichout
, that would make it more user-friendly.