Welcome to Watering My Digital Plant with Emacs Timers,
a talk by Christopher Howard for Emacs Conference 2024.
The goal of this talk is to give
a brief introduction to Emacs timers
using the illustration of how I created
a bot for the Astrobotany service.
What is Astrobotany?
Let me jump to the home page.
Astrobotany is a botany game or a simulation
that is played using the Gemini protocol
and gemtext documents.
What is Gemini?
The Gemini protocol is a small web protocol,
similar to the HyperText Transfer Protocol,
but with differing goals of simplicity,
non-extensibility, and protecting privacy.
Gemtext is a simple hyperlinking document format,
the analog of the HyperText Markup Language, or HTML.
Much more could be said
about the design and goals
of the Gemini project,
but that is not the focus of this talk.
And how do you play Astrobotany?
First, you need to get a Gemini client
or what you might call a browser.
Many clients are available,
but I am using Elpher, a Gemini client for Emacs.
Once you have your client running,
navigate to the home page for Astrobotany,
which is shown in this window.
You'll see the URL for the home page
displayed at the top of the window.
On your first visit to Astrobotany,
you will need to create a client certificate,
which will be used instead of a password.
Your Gemini client will help you
to create the certificate.
Then you will go to the Visit Your Plant page
in order to view your plant,
to water it, and to collect things from it,
including money. So here you see
the plant that I'm currently growing
in glorious ASCII graphics.
There's also a color version available from this page.
Back at the home page,
you can do other things
like go to the item shop,
to buy items like badges, fertilizer,
or post on the message board.
In Astrobotany, gardener bots are fully legal.
And to do an action on your plant,
like watering the plant,
all your bot needs to do is
to access the appropriate Gemini URL or page
while presenting the appropriate certificate
for your plant.
And this brings us to Emacs timers.
So the main function of interest to us
is the run-at-time function.
Here is the help documentation,
which is available in any recent Emacs installation.
As you see, the purpose of the function
is to perform an action at a specific time
to repeat it after a specific number of seconds.
And so basically, all you have to do is
pass in a function to run-at-time,
telling Emacs how soon you want to run the function,
and then how often you want to run the
function after that.
The function has a variety of options for
specifying the time parameter,
that is, how soon you want the function to run.
For our application, in which we'll be
running our functions
once or twice a day at specific times,
it is most useful to
specify the number of seconds until the event.
This does, however, require
calculating the number of seconds
until a specific time of day. I will
provide code for this shortly.
The run-at-time function does allow you to
specify the time parameter as a string,
representing the hours and minutes.
For example, 05:40.
However, there is an oddity in the
design of run-at-time,
such that if the specified time of day has
already passed,
then the timer will run immediately,
rather than in the future,
as you might expect.
This can be problematic, for example,
if run-at-time is being called from your init file,
since the timer will run immediately
every time you restart Emacs for any reason.
I noticed recently that run-at-time
also allows you to pass in a value
from encode-time, which maybe does what we want,
but I never bothered with testing that.
Actually, I have a vague memory of
once looking into it and it didn't seem to do what I
wanted, but honestly I can't clearly remember,
so you may want to look into that yourself.
What I ended up using was just
passing in a number of seconds.
So now we'll move over to the code.
So I'll skip down here first
to the code that I wrote for calculating the number of
seconds.
It's a function that calculates the number of seconds
until a particular time of day in the future.
You can see that you pass in the hour as a number from
0 to 23 and the minutes as a number from 0 to 59. And
here's the code, which will also be available later.
I wrote another function, secs-until-weekly,
which we do not need for this talk,
but which is useful if you're running
events which need to happen once per week.
This function also requires a target hour
and a target minute, but also requires passing in a
target day. And while we're on the subject of timers
specifically, I should mention that
Emacs has a very useful function called list-timers.
So if I call the interactive function list-timers,
it will give me a list of all the timers
currently running.
This page shows not only which timers exist,
but also how long it will be until they run again,
along with the periodic repeat value that you
specified.
Furthermore, any timer can be canceled by moving point
over the timer and running timer-list-cancel,
which on my system is bound to the letter c by default.
This is very helpful while you are developing
some timer function.
So I could cancel the timer that I already have running
for shaking the plant,
as well as the one for watering the plant.
and back to the code.
So now we'll talk about the actual code for
managing the plant.
So you see I have a variable set up here
that specifies where the certificate file,
the public certificate file, as well as
the secret key file is located.
This is where it is in my system.
Of course, depending on your specific Gemini client,
it may be in a different space and will likely have a
different name.
And here is the code for watering the plant,
which I can call interactively.
And the core of it here is that it uses the
gmni utility, a command line utility to
call a particular URL while also loading up
or presenting the required certificate.
So in this case, you can see it is the URL that is
required for watering the plant.
This idea is very simple and the gmni client
or gmni command line program
makes this very simple to do.
Here's another function for shaking the plant.
Again it is almost identical except that we
use a different URL,
one for shaking the plant instead of watering it.
And again we want to shake the plant
in order to get money to fall off of it.
You need to water your plant at least once per day or
it'll die.
I usually water mine twice and just in case something
happens where Emacs was turned off because of
power outage or something like that
that I'm more likely to get it watered,
and I shake it once per day because there isn't
any purpose to shaking it more than that.
If you try to shake it more than that,
then money no more money will fall off,
or not much.
So you see down here, I have the code that
actually calls run-at-time.
I left here commented my original forms of this
which used the just specify directly the time of day.
As I mentioned the problem with that
was that it would...
these functions would also get called
whenever I restarted Emacs for any
reason and that was kind of annoying.
So instead we have here the functions down here
which uses secs-until-daily
to water the plant
and then secs-until-daily to shake the plant.
You see, I've specified the plant to get watered
at 4 in the morning and then the function is run again
after that, every 43,200 seconds, which translates to
every 12 hours,
and then I shake the plants, shake the plant
every morning at 4.15 a.m. and once every [24] hours.
With a little bit more
sophistication, a little bit more work on the code,
I could actually have multiple plants be watering and
shaking multiple plants with multiple certificates,
but I never got around to that.
Didn't seem worth the bother to me.
So thank you for watching my video,
Watering My Digital Plant with Emacs Timers.
You'll see at the bottom of this page
links to the code for this talk
as well as other things that I mentioned
like the source code for the
Elpher Gemini client,
the URL for the Astrobotany capsule,
as well as a link to more information about
Project Gemini and my own personal Gemini capsule
that's being run off my own server at home.
Thank you very much.