The main interest is to be able to execute external actions according to the user's actions in EmulationStation.
Of course, it is mainly on Raspberry Pi and other boards allowing easy hardware control that this feature will reveal its full potential.
On a number of EmulationStation events, we can launch a script (or an executable) or send an MQTT message that scripts will wait and process.
At each event, a file is filled with a lot of information that the scripts can use or not.
We start with the exhaustive list of events, when they are triggered, and if they expose additional information that will be passed as parameters to the scripts.
Event | When | Settings |
---|---|---|
Start | Start or restart EmulationStation | Number of starts |
Stop | Number of stops | Number of starts |
Stop EmulationStation | Number of Starts | Shutdown |
Shutdown | System Shutdown | |
Reboot | Restart System | |
Quit | Quit EmulationStation after an external request (e.g. stopping GPI case by on/off button) | |
Relaunch | Restart EmulationStation (e.g. gamelist.xml changed externally, or update game lists) | |
SystemBrowsing | User is on the system list and a new system has been selected. | Short name of the system |
GamelistBrowsing | User is on a game list and a new game (or directory) has been selected. | Path of the rom file |
RunGame | A game will be launched | Path of the rom file |
RunDemo | A game will be launched in demo mode | Path of the rom file |
EndGame | A game has just ended | Path to the rom file |
EndDemo | Demo of a game just ended | Path of the rom file |
Sleep | Start Screen Saver | |
WakeUp | Exit screensaver | |
ScrapStart | A multi-game scrapping session has started | |
ScrapStop | A multi-game scrapping session has ended | Number of scraped games |
ScrapGame | A game has just been scraped. | Path of the rom file |
ConfigurationChanged | Something has changed in the configuration |
Scripts are placed in /recalbox/share/userscripts
or /recalbox/share/userscripts
or in subdirectories if you want to organize them.
EmulationStation itself chooses the best launcher, if any, based on the extension of the scripts:
All other extensions are considered as executables and the files are launched directly.
At the time of writing, Python3 is not yet fully supported in Recalbox !
Each script/executable is launched with the following arguments:
script -action action -statefile statefile [-param parameter]
By default, scripts are launched for each event.
To filter and run the script only for some targeted events, you just have to indicate them between brackets and separated by commas. Case is not important.
For example: /recalbox/share/userscripts/marquee[start,stop].sh
will only run when EmulationStation starts or stops.
Or: /recalbox/share/userscripts/gamesinfo[browsinggamelist,rungame,rundemo,scrapgame].sh
will only be run for events that are directly related to the games, for example to display the game info on a secondary screen.
All scripts are launched asynchronously. This means that EmulationStation continues its execution while the script is running in parallel.
In most cases, it doesn't matter, but there are times when we want to block EmulationStation until our script is finished. A typical use case is a script that would run on system reboot or shutdown events.
In this case, we have to make sure that our script is executed before the system starts its shutdown procedure, otherwise we might lose information or worse.
To make a script run synchronously, you just have to put a (sync)
in the filename.
For example: /recalbox/share/userscripts/backup[reboot,shutdown](sync).sh
will be executed on system shutdown or reboot. EmulationStation will be blocked until its execution is finished and system shutdown will start only after that.
Some scripts may need to run continuously. Mainly if they are used to intercept MQTT messages which we will see a bit later.
Just put (permanent)
in the filename of a script to have it run by EmulationStation at startup.
If EmulationStation restarts, permanent scripts will continue to run and will not be restarted.
Recalbox has a mini MQTT server (Mosquitto) which allows you to do publish/subscribe.
Whenever EmulationStation is going to run scripts on an event, it also publishes the event (in lower case) to the /Recalbox/EmulationStation/Event
topic.
A program that listens on this topic can therefore intercept all events for almost no CPU usage cost.
Mosquitto provides 2 small executables mosquitto_pub
and mosquitto_sub
which allow respectively to publish a message on a topic or to wait for a message from a topic.
So you can use mosquitto_sub
in a script, to listen and wait for EmulationStation events, as follows:
event = $(mosquitto_sub -h 127.0.0.1 -p 1883 -q 0 -t /Recalbox/EmulationStation/Event -C 1)
This command blocks the execution of the script until an event is published. The script then retrieves the type of event in the event
variable.
You can quickly understand the interest of permanent scripts: rather than launching a script each time, for each event, you can launch a permanent script and have it intercept all the events in a loop. It's a much lighter solution for the system.
Mosquitto is launched explicitly on the local loop IP 127.0.0.1, which prevents its access and use outside Recalbox, for obvious security reasons.
If you want to change its configuration, this is the place to do it: https://mosquitto.org/man/mosquitto-conf-5.html
At each event, EmulationStation writes a small file to the ram-disk: /tmp/es_state.inf
.
This file is a simple file of type ini, containing key=value associations.
This file is already at version 2.0. This file contained a fixed number of keys, with empty values depending on the context.
Version 2.0 keeps the compatibility with version 1.0, but adds fixed and optional keys depending on the context.
Here is the list of keys/values available since version 1.0:
Key | Value | Can be empty ? |
---|---|---|
System | Full name of the system involved in the event. | Yes |
SystemId | Short name of the system concerned by the event. | Yes |
Game | Full name of the game involved in the event. | Yes |
GamePath | Full path of the game that is affected by the event. | Yes |
ImagePath | Full path to the game image for the event. | Yes |
State | Contains one of the following values: playing : a game is playing demo : a game is being demoed selected : all other cases. |
No |
And the list of what is available in addition, since the version 2.0 :
Key | Value | Events |
---|---|---|
Action | The name of the event that generated the writing of the state file. | All |
ActionData | Event parameters, possibly empty | All |
Emulator | Default emulator for the concerned system. | BrowsingSystem |
Core | Core used by default for the concerned system. Can have the same value as the emulator for emulators without plugins like Amiberry. | BrowsingSystem |
Emulator | Emulator used to run this game. | Games (*) |
Core | Core used to run this game. Same as for the core system. | Games (*) |
IsFolder | Is 1 if a folder is selected in the game list. 0 if it is a game. | Games (*) |
ThumbnailPath | Full path to the thumbnail corresponding to the game. | Games (*) |
VideoPath | VideoPath Full path to the game video. (**) | Games (*) |
Developer | Name of the developer or development studio. (**) | Games (*) |
Publisher | Name of the publisher. (**) | Games (*) |
Players | Number of players. Number of players. (**) | Games (*) |
Region | Region of the game. Game region. (**) | Games (*) |
Genre | Genre of the game. (**) | Games (*) |
GenreId | Numerical identifier of the game genre. (**) | Games (*) |
Favorite | Is 1 if the game is in favorites, otherwise 0. (**) | Games (*) |
Hidden | Is 1 if the game is hidden, otherwise 0. (**) | Games (*) |
Adult | Is 1 if the game is rated adult, otherwise 0. (**) | Games (*) |
(*) Means in detail: GameBrowsing, RunGame, RunDemo, EndGame, EndDemo and GameScrap.
(**) Each of these information comes from the metadata associated with the game concerned. They can be empty if the game has not been scrapped.
This file is written before the scripts are launched and the MQTT message is sent. It is therefore valid when the scripts run. However... Some events being extremely close, it is possible that this file has already been changed by a second event when you go to read it after a first event. It is therefore advisable to :
- Check the value of the
action
key to be sure that it corresponds to the desired event.- Do not assume that an optional key will necessarily be present or that a fixed key will necessarily have a value.
Here is a series of tips for writing your scripts. You are free to ignore them, but be aware that running scripts at each EmulationStation event can have an impact on the system:
If possible, keep your scripts to a minimum.
Avoid scripts that do not filter events. If Recalbox adds events in future versions, your scripts will be even more stressed.
Avoid synchronous scripts if they are not strictly necessary (during the shutdown phase).
Use as much as possible the ASH
shell instead of SH
, it is much faster and optimized. On the other hand, it has some slight differences with SH
. https://fr.wikipedia.org/wiki/Almquist_shell
If you need to handle a lot of events, use a single permanent script in conjunction with mosquitto_sub
, you will save a lot of system resources.