12 AT script engine
Many legacy systems drive a modem using an AT command set — the modem command language popularized by telephone modems, where each instruction is a line of text beginning with the letters AT. To make UnetStack-based modems easy to integrate into such systems, UnetStack provides an AT script engine that exposes most of the stack’s functionality through simple text commands.
The AT command interface exists for compatibility with legacy applications, and is necessarily limited and error-prone compared to the full power of the stack. For new applications, prefer the UnetSocket API (Chapter 9) or the Groovy shell, both of which give you complete access to messages, parameters and agents.
The AT script engine is one of the script engines that a shell can use (in place of the Groovy engine). On a modem that exposes an AT interface, you interact with it by sending command lines and reading the textual responses. Every command produces either a result followed by OK, or ERROR if the command could not be processed.
12.1 Bringing up the AT interface
A shell speaks AT when it is created with the ATScriptEngine script engine. This is configured in the modem’s startup.groovy using the iface command. For example, to expose an AT interface on TCP port 5001:
iface ATScriptEngine, 5001or over a serial port at 115200 baud:
iface ATScriptEngine, '/dev/ttyS0', 115200, 'N81'Connect to the TCP port (or open the serial port) with any terminal or socket client, and send AT command lines, each terminated by a newline.
The engine resolves agent names such as phy or ranging, and provides most of its commands, through shell extensions. On a modem, the standard extensions are loaded automatically when the AT interface starts, so the commands in this chapter work out of the box. If you bring up an AT interface on a bare stack (for example, in simulation), you must first load the extensions you need yourself with AT~EXT= (see Calling commands), as shown in the examples below. AT~EXT= loads any shell extension, including ones you write yourself (Section 33.7).
If you are writing software to drive the AT interface, a few contract rules are worth knowing up front:
- Lines that do not begin with
ATare silently ignored (noOK, noERROR). This makes them usable as comments in files of AT commands. The sole exception is while a data block is being loaded (see Transferring binary data), when every line is interpreted as data. - Commands are case-insensitive —
at~phy?andAT~PHY?are equivalent. The exceptions are Java class names (inAT~EXT=andAT~MSG:) and message field names, which must match exactly; message aliases are matched in upper case, so define them in upper case. - Whitespace is significant. Do not add spaces around
=,,or.in commands. - Responses and notifications are atomic. A multi-line response, or an asynchronous notification with its data blocks, is always written out in full; lines from different responses or notifications never interleave.
12.2 Basic commands
| Command | Description |
|---|---|
AT |
Attention — does nothing, replies OK. Used to check the interface is alive. |
AT/ |
Repeat the last command. |
ATE0 / ATE1 |
Turn command echo off / on. |
ATZ |
Shut down the node. On modems that relaunch the stack automatically, this effectively reboots it. |
For example:
AT
OK
ATE1
OK12.3 Reading and writing parameters
Agent parameters (documented per service in Part III) are accessed by agent name. The general forms are:
| Command | Description |
|---|---|
AT~agent? |
List all parameters of agent. |
AT~agent.param? |
Read a single parameter. |
AT~agent.param=value |
Set a parameter (replies with the new value if it changed). |
AT~agent/index? |
List all indexed parameters at index. |
AT~agent/index.param? |
Read a single indexed parameter. |
AT~agent/index.param=value |
Set an indexed parameter. |
Parameter values are reported as AGENT.PARAM=value lines (indexed parameters as AGENT/index.PARAM=value). Numbers are returned bare, booleans as 0 or 1, and strings in double quotes. An agent’s parameters can only be accessed once the shell extension that defines the agent’s name has been loaded (automatically on a modem, or with AT~EXT= otherwise). For example, having loaded the physical-layer extension, we can read and set the transmit power for the modem’s control channel (a per-channel, indexed parameter):
AT~EXT=org.arl.unet.phy.PhysicalShellExt
OK
AT~phy/1.powerLevel?
PHY/1.POWERLEVEL=-10.0
OK
AT~phy/1.powerLevel=-6
OKA successful set is acknowledged with OK; the resulting value is echoed back as an AGENT.PARAM=value line only if the agent adjusted it (for example, by clamping it to the permitted range).
12.4 Calling commands
Shell extension commands and methods are invoked directly by name, with or without arguments:
| Command | Description |
|---|---|
AT~name |
Call a command that takes no arguments. |
AT~name=args |
Call a command with comma-separated arguments. |
Arguments are parsed by type: a value in double quotes is a string, a value containing a . is a floating-point number, and anything else is an integer. Boolean arguments are given as 0 or 1, mirroring how boolean parameter values are reported.
Before extension commands can be called, the shell extension that defines them must be loaded:
AT~EXT=org.arl.unet.localization.RangingShellExt
OK12.5 Sending and receiving messages
To exchange messages with agents, you first define a short alias for the message class and the fields you intend to use:
AT~MSG:RANGE=org.arl.unet.localization.RangeReq:to
OKThis defines an alias RANGE for RangeReq, exposing its to field. You can then send the request to an agent using the < operator, supplying the field values as arguments:
AT~ranging<RANGE=31
OKThe reply determines the response: an AGREE is reported as OK, a REFUSE or FAILURE as ERROR, and any other response is printed.
Notifications are received by subscribing to the relevant topic. Use a defined message alias so the engine knows how to format the incoming notification:
AT~MSG:RNF=org.arl.unet.localization.RangeNtf:from,range
OK
AT~SUB=ranging
OK| Command | Description |
|---|---|
AT~MSG:name=class:fields |
Define a message alias and its fields. |
AT~agent<name=args |
Send the aliased message as a request to agent. |
AT~SUB=topic |
Subscribe to a topic (an agent name, or agent/topic). |
AT~UNSUB=topic |
Unsubscribe from a topic. |
When a subscribed notification arrives, it is delivered as a line of the form ~SENDER>name=values:
~RANGING>RNF=31,152.3Array-valued fields (byte[] or float[]) are not included in the comma-separated values. Instead, one : is appended to the line for each array field, and each array then follows as a block of hexadecimal lines (64 hex characters per line), terminated by a . on its own line. For example, with an alias RX defined for DatagramNtf exposing from and data, an incoming 5-byte datagram is delivered as:
~UWLINK>RX=31:
48656C6C6F
.The trailing : on the first line signals one array block to follow (:: would signal two, and so on); byte[] arrays are encoded two hex characters per byte, and float[] arrays eight hex characters per value (IEEE 754).
Notifications of a message class for which no alias has been defined are silently discarded — the engine has no way to format them. If you subscribe to a topic and see nothing, check that you defined an AT~MSG: alias for the message class you expect before the notification arrives.
12.6 Transferring binary data
Binary payloads (for example, a datagram’s data, or a baseband signal) are transferred as blocks of hexadecimal bytes. A data block is started with AT~DATA:, followed by lines of hex (or decimal floating-point values, which are encoded as 32-bit floats), and terminated by a line containing only a period:
AT~DATA:
48656C6C6F
.
OKEach hex line must contain an even number of hex digits (two per byte). Floating-point data can be given either in decimal form — exactly one number per line, and it must contain a decimal point — or directly as hex (eight hex digits per value, IEEE 754). A line that is neither valid hex nor a decimal number aborts the load with ERROR. AT~DATA? reports the loaded size in bytes, or EMPTY if no data is loaded.
| Command | Description |
|---|---|
AT~DATA: |
Begin loading a binary data block (terminate with .). |
AT~DATA? |
Report the size of the loaded data block (EMPTY if none). |
AT~CLRDATA |
Clear the loaded data block. |
Once loaded, the data block is referenced in a message or command argument using the keyword DATA. For instance, having loaded the link extension and defined a datagram message alias with a data field, the loaded block can be sent as the payload:
AT~EXT=org.arl.unet.link.LinkShellExt
OK
AT~MSG:TX=org.arl.unet.DatagramReq:to,data
OK
AT~DATA:
48656C6C6F
.
OK
AT~uwlink<TX=31,DATA
OK