The SHELL fjåge service provides a way to run commands and access files on a node. A shell agent is one of the “doors” into a UnetStack node (Section 9.1): it lives in the node’s container and executes commands on behalf of whoever is connected — a terminal, the web interface, or another program. While shell agents typically provide interactivity using a terminal/console or web interface, they also support messages that let other agents request execution of commands or access to files. This is how the REMOTE service (Chapter 24) executes commands received from remote nodes, and how agents running in slave containers or on gateways — which have no direct access to the node’s filesystem — read and write files on the node.
30.2 Messages
Agents providing this service honor the following requests:
ShellExecReq – execute a command
Field
Type
Default
Remarks
ans
boolean
false
true to request output of execution
command
string
command to execute
script
object
name of script to execute
scriptArgs
string[]
arguments to the script
GetFileReq – read a file or directory
Field
Type
Default
Remarks
filename
string
name of file or directory to read
length
long
0
number of bytes to read
offset
long
0
offset in bytes to start reading from
PutFileReq – write contents to a file or delete a file
Field
Type
Default
Remarks
contents
byte[]
contents to write (null to delete file)
filename
string
name of file to write to or delete
offset
long
0
offset in bytes to start writing from
If the request is invalid, a REFUSE response may be generated. If the request fails, a FAILURE response may be generated. On success ShellExecReq and PutFileReq generate an AGREE response, while GetFileReq generates a GetFileRsp response. If ans is requested, a ShellExecReq may generate a GenericMessage response with an AGREE performative and an ans field containing the output of the command execution.
GetFileRsp – response for GetFileReq
Field
Type
Default
Remarks
contents
byte[]
file or directory contents
directory
boolean
false
true for directory
filename
string
name of file or directory
offset
long
0
offset in bytes from start of file
For directories, the contents of GetFileRsp consists of a list of files (one file per line). Each line starts with the filename (with a trailing '/' if it is a directory), '\t', file size in bytes, '\t', and last modification date (as Unix epoch time).
30.3 Parameters
An agent providing the SHELL service exposes the following parameter (org.arl.fjage.shell.ShellParam):
language – the language supported by the shell (Groovy for the default shell)
NoteScript engines
The language in which the commands are written is not defined by the service, but depends on the shell agent. fjåge supports a pluggable mechanism for a ShellAgent to use any ScriptEngine. Various script engines are available in fjåge and UnetStack, including the GroovyScriptEngine (default), EchoScriptEngine, and ATScriptEngine (Chapter 12).
30.4 Commands
The commands available in a shell come from three places: a set of fjåge built-ins available in every Groovy shell, the basic Unet commands contributed on every UnetStack node, and the service commands contributed by each service’s shell extension (documented in the “Commands” section of the corresponding chapter in this part). You can contribute your own commands the same way — see Section 33.7.
The fjåge built-ins are:
help — provide help on a specified topic
ps — list all the agents
services — list all services provided by agents
who — display list of variables in workspace
shutdown — shutdown the local platform
run — run a Groovy script
println — display message on console
href — make a clickable URL (on terminals that support URLs)
delay — delay execution by the specified number of milliseconds
logLevel — set log level (optionally for a named logger)
subscribe / unsubscribe — subscribe to (unsubscribe from) notifications from a named topic
export — add specified package/classes to list of default imports
agent — return an agent id for the named agent
agentForService / agentsForService — find an agent id (or a list of all agent ids) providing the specified service
send — send the given message
request — send the given request and wait for a response
receive — wait for a message
input — get user input
help with no arguments lists all available help topics, and help <command> gives usage details for any of these. Note that shutdown stops the node it is issued on — and, on a simulated node, the entire simulation — so it is also the polite way to end a simulation session (the impatient alternative being Ctrl-C in the terminal running it).
Every UnetStack node additionally provides the following basic commands (org.arl.unet.UnetShellExt):
ver – version information
time – current platform time
ls – list files in scripts directory
dashboards – show list of dashboards
iface – display/enable interfaces
Usage:
iface [iftype, port[, baud] [, settings]]
Examples:
// show all interfaces
iface
// enable AT commands on TCP port 5001
iface ATScriptEngine, 5001
// enable Groovy shell on TCP port 5002
iface GroovyScriptEngine, 5002
// enable AT commands on serial port /dev/ttyS0 at 9600 baud
iface ATScriptEngine, '/dev/ttyS0'
// enable Groovy shell on serial port /dev/ttyS0 at 115200 baud
iface GroovyScriptEngine, '/dev/ttyS0', 115200
// enable API connector on serial port /dev/ttyS0 at 115200 baud
// with settings: no partity, 8 bits, 1 stop bit
iface API, '/dev/ttyS0', 115200, 'N81'
// alternate syntax to enable AT commands on TCP port 5001
iface new ATScriptEngine(), 5001
distance – compute distance between two points
Example:
distance([0,0], [100,100]) // distance between origin and (100,100)
logs – list log files
Only log files with the default settings (logs/*.txt) are listed.
Example:
logs // list log files
clrlogs – clear old log files
Old log files (logs/*.txt) are deleted. The currently active log files (logs/*-0.txt) are not deleted.
Example:
clrlogs // clear old log files
tail – show the last few lines of the current log file
Examples:
tail // show last 10 lines of the current log file
tail 20 // show last 20 lines of the current log file
file – file in the scripts folder
Example:
file('a.groovy').size() // get size of script file a.groovy
file('a.groovy').delete() // delete script file a.groovy
file('a.groovy').text // show contents of file a.groovy
reboot – restart network stack
queues – show the state of internal queues
Examples:
queues // show the state of internal queues
hist – show shell command history
clrhist – clear shell command history
Of these, iface is the practical first step when connecting an application to an unfamiliar node — it lists the live API connectors (TCP, websocket) and shell URLs (see Chapter 9). The distance command computes the geometric distance between two locations in the node’s local coordinate frame (Chapter 14), which makes for a satisfying cross-check of a measured acoustic range (Chapter 25) against surveyed node locations:
We find that websh is the agent that provides us the SHELL service.
2
We send a command to websh to execute, and it agrees to do so. Since the commands we type are also executed by the websh agent, we need to be careful to not block the execution. Hence we use a send rather than a request (or equivalently <<).
3
The command was to create a foobar file, so we check that the file is created.
4
We read the contents of the foobar file to confirm that FOOBAR was correctly written to it.
Next, let’s try the GetFileReq and PutFileReq messages to read, write and delete this file:
1> websh.send new GetFileReq(filename: 'scripts/foobar')websh >> INFORM: GetFileRsp2> ntf.contents[70, 79, 79, 66, 65, 82]3> new String(ntf.contents)FOOBAR4> websh.send new PutFileReq(filename: 'scripts/foobar', contents: 'fobaaaar')websh >> AGREE5> file('foobar').textfobaaaar6> websh.send new PutFileReq(filename: 'scripts/foobar', contents: null)websh >> AGREE> lsREADME.md [759 bytes]7> websh.send new GetFileReq(filename: 'scripts')websh >> INFORM: GetFileRsp> new String(ntf.contents)8README.md 759 1710690140149
1
The file foobar was created in the scripts folder, which is the default location for the file() function. We ask to read the file, and get a GetFileRsp response back.
2
The file contents are read back as a list of bytes.
3
We convert the list of bytes to a String to get our FOOBAR contents.
4
We send a PutFileReq to change the contents of the file.
5
We verify that the file contents were indeed changed, as requested.
6
Sending a PutFileReq with contents set to null deletes the file.
7
Asking for the contents of a directory using GetFileReq gets us the directory listing back.
8
The listing consists of all files in the directory, one file per line. Each line has a filename, file size and file modification timestamp (epoch time). If a file is a directory, the filename is suffixed by a '/'.
We interacted with the SHELL service provider using a shell! That’s not very useful in practice, but it serves to show you how these messages work. Typically, these messages are sent by other agents that wish to get the shell to run commands and access files for them (e.g. the caddy agent, Chapter 24). The agents may be running remotely in a fjåge slave container or on a gateway (via the UnetSocket API), where they may not have direct access to the filesystem of the node.
30.6 Implementation
30.6.1 ShellAgent (shell, websh)
Class
Services
Capabilities
Availability
ShellAgent
SHELL (fjåge)
—
default stack
The SHELL service is provided by fjåge’s ShellAgent, not by a UnetStack-specific agent. A node typically runs one or more shell agents — a console shell (shell) attached to the terminal, and/or a web shell (websh) serving the browser-based shell — each pairing a script engine (Groovy by default) with a user interface. The Unet-specific commands available in the shell (ps, tell, plvl, and all the service commands in this part) are contributed by shell extensions, loaded into the shell agent based on the services available on the node.