FindNode is a shell program on top of the core Selector package, the purpose of FindNode is to find the intended one or more UI elements (or Accessibility nodes) and extract information or perform action on them.

FindNode is part of the AAI project (Accessibility and Automation Integration), it consists of the following components:

  1. Query language, simple one line syntax language to search for intended nodes.
  2. FindNode (and Selector core) on each device.
  3. Object mode in one-to-many synchronization, send the node (or UI object) to all devices instead of coordination, click "OK" can run on all devices with different resolutions than click(100,100).
  4. UI Explorer to obtain node information, can visually the query language, leaning and exploration tool.
  5. AAIS, a simple language to perform automation
  6. As part of the existing REST and JS API (e.g."OK")).

Selector sits on top of Accessibility and UI Automator, each UI element or container of UI elements is identified by node or sets of nodes, each node has its ID that is unique on the current screen. Selector provide various ways to search for one or multiple nodes. once the nodes are acquired, you can do interesting things such as obtain the texts/images or perform action such as click on button or entering the text on the text field, all without coordinates. This allow one automation script to run on different resolutions which is not possible using coordinates. The coordinates are acquired during the runtime.

For Example:

JS API:"OK") instead of, 200). OK query with click will be sent to all devices.

MDCC: Users click a button on the main device, the unique query of the button (e.g. "T:OK") will be sent to other devices, to search for the node and click. The intention is to provide various ways to locate nodes without using screen coordinates, there is no page up/down but "scrollToView" to locate a node, this way, the same script can run on different phones with different resolutions and screen size.

FindNode uses the JSON construct of Appium to carry out the JSON commands, the JSON format is:

{"cmd": "action", "action": "findnode", "params": {…}}

Every FindNode commands are carried out in "params" object.

Return value can have one of two types, successful run:

{"status":0, "value":"{…}"}

Or failure:

{"status":13,"value":"{<error message>}"}

TC provide "device.sendAai()" or "devices.sendAai()" to communicate with FindNode:

  • Send the "params" object.
  • Generate timeout error if FindNode does not return in certain time period.
  • For certain commands that takes longer than the default timeout, FindNode will extend the time to avoid timeout error.
  • Handle return value automatically (null on error, non-null on value).
  • For multiple devices "devices", thread to FindNode will be created for each device.

The params object contains 3 types of properties: query, preAction and postAction(s). The commands offered are quite rich, you can write a simple automation with them.

Presumably you can do most of the task by using "device.sendAai()" and "devices.sendAai()", FindNode is a "handler", another handler "invoke" can use Java Reflection to access methods in UiDevice, UiObject2, AccessibilityNodeInfo and InteractionController.


This is the version 1 of AAI, it comes with some limitations:

  • Support portrait mode only, landscape mode will be introduced in the future.
  • Not everything will be able to identify at this time, depending on the design of app, may miss some of the nodes.
  • Horizontal scrolling or certain popup windows are not supported (less nodes or more nodes).
  • Does not support multi-threaded, each execution has to be mutually exclusive: MDCC (object mode), terminal (or script), UI Explorer and part of REST API. This will be resolved in subsequent versions.


Majority of the FindNode usages are about query, this is center piece of the FindNode (and part of AAI). It allows users to locate intended UI elements via different kind of useful queries on the Accessibility nodes. The entire screen is composed by many nodes, a node can be a smallest UI element or a container of smaller nodes, many are invisible. The entire screen is a tree structure starting from the single root node. Query will be performed to obtain the nodes that meet the criteria, the intent is to reduce the large number of nodes to one or few intended nodes, users can obtain information or apply actions to the nodes.

We developed a small query language (for lack of better word) that is portable among devices and powerful enough to locate most of the nodes. The format is JSON:


We accept "||" and "&&" as identical separator, in the future it can be changed. "&&" is AND relationship, that means the query nodes needs to meet all criterion to become "matching nodes". FindNode does not offer OR relationship but the underlying Selector package provides that capability. "templates" use that construct. For certain key, value is not required.

Query String

There are many types of query , some are basic query related and other are derivatives of calculations:

The following are the basic queries that match information of the node itself:

  • C:<class name> Class name (S)
  • R:<resource ID> Resource ID (S)
  • D:<text> Description (S)
  • T:<text> Text (S)
  • IT:<number> Text input type (I)
  • CC:<number> Child count (I)
  • ID:<ID> Node ID in hex (S)
  • BI:[x, y] The nodes contain (x,y)
  • BI:[x1, y1, x2, y2] The nodes enclosed by the rectangle, if x or y is -1, it will be ignored
  • BP:<prop name> Boolean properties (S).

The following are the expanded queries:

  • IX:<number> Obtain the one node from list of matching nodes based on position
  • OX:<number> Offset to neighbor nodes horizontally (positive – right, negative – left)
  • OY:<number> Offset to neighbor nodes vertically (positive – down, negative – up)
  • TP:<template name> Short-form of complex search
  • ON:<type> Different ways to pick one node out of list of matching nodes
  • LT:<line number> Return top nodes on the top of the scrollable nodes
  • LB:<line number> Return bottom nodes of the bottom of scrollable nodes
  • ST:<sort type> Return sorted nodes based on the position of the nodes on screen
  • TX Return nodes that intersect with reference node horizontally (see "intersectX").
  • TY Return nodes that Intersect with reference node vertically (see "intersectY").
  • VG:[level number] Return nodes that Intersect with reference node vertically (see "intersectY").
  • RN Return the optimized nodes from a list of matching nodes (see "reduceNodes")
  • PQ:<query> Apply after everything is done.

All these queries are optional, you can pick and choose any field, if you are doing the query on the screen. The "(S)" stands for String, "I" integer, "[x, y]" array. I/S can accept both integer and String. String is more powerful.

The keys are case insensitive, we use uppercase to differentiate keys from values.

Special Characters in Query

For number such as inputType or childCount, it can either accept number or string:

  • <number>, "<number>" or "=<number>": Match exacting the number.
  • "> <number>": Match number greater than <number>.
  • "< <number>": Match number less than <number>.

String can be one of the following, "\n" to match newline character:

  • "<string>": Exact match on full text, case sensitive.
  • "(?i)<string>": Match in case insensitive match in regular expression.
  • "(ci)<string>": -Match case insensitive in string.
  • "*<string>": Match string at the end, e.g. "*Group".
  • "<string>*": Match string at the beginning. e.g. "android.widget.*".
  • "*<string>*": Match any substring "*widget*".
  • "/<regex>/: Match any regular expression. E.g. "/ImageView|TextView/"

"!" is use at the beginning of string as NOT. "!0" = not zero. "C:!/Layout/", non-layout classes.

For simplicity in examples, the standard prefix of {"cmd": "action","action": "findnode", "params":{…}} will be omitted, we will list the examples in the "param" object and use JavaScript object notations to reduce number of double of quotes.


Two shortcuts are available for class name (C) and resource ID (R), prefix with "." for substitutions of commonly use prefixes:

  • Class name (C): Prefix "." as substitution of "android.widget.". E.g. ".Button" = "android.widget.Button".
  • Resource ID (R): Prefix "." as substitution of "<package name>:id/". E.g. ".text_connect" = "com.sigma_rt.totalcontrol:id/text_connect".

The "." notation can only be used for normal matching and "!" matching, other types of matching (e.g. regular expression and wildcard) needs to use full form.

Bound In (BI):

Bound in takes screen coordinate or bounds and return the nodes based on the criteria below. This mainly for screen manipulation such as UI Explorer or debugging purposes.

  • [x, y]: Will return the nodes with bounds contain (x, y).
  • [x, -1] or [-1,y] where one of them is -1: if x = -1, will ignore x and return nodes with bounds that contains y, similarly for y = -1, it will only match x. As if drawing a horizontal/vertical line on the y and x location, the nodes that touch the line will be returned.
  • [x1, y1, x2, y2]: Will match the nodes bounds within the rectangle specified by (x1, y1) - (x2, y2).

The last 2 modes are supported in 8.0-u40.


{query:"BI:[-1, 1000]&&IX:-1", postAction:"click"}
{query:"BI:[0, 0, 1000, 1000]", postAction:"getBounds"}

Matching List

After the query is performed, the matched nodes will be stored in Matching List (ML), nodes in ML are identified by node ID. Many actions use the nodes in the ML, some actions can change the ML. "save" and "load" can save and retrieve ML. Use action:"getIds" to obtain the ML IDs. TX/TY/VG take one node and generate multiple nodes.

Another way to obtain the ML is to use "elements" array.

Query Precedence

Below are the query precedences:

  1. Line mode (LT/LB).
  2. Template (TP).
  3. Basic query.
  4. Index (IX)
  5. Single node selection (ON).
  6. Offset (OX and OY).
  7. ViewGroup (VG).
  8. Intersect (TX & TY).
  9. reduceNodes (RN).
  10. Sort (ST).
  11. Post Query (PQ).

"elements" property

"elements" accept an array of node IDs, the node IDs must be a valid node ID on the screen or it will generate error. When "elements" is specified, the search will not be performed, however, IX, OX, OY, ON will still work. It is useful for debugging purpose and access the nodes that cannot be retrieved easily (e.g. child/parent nodes). Each query with "elements" will traverse all nodes to locate corresponding node, try to put multiple IDs in the array instead of one at a time.

device.sendAai({elements:["1234a", "5678b"], postAction:"getNodes"})

var ids = device.sendAai({query:"C:.Button"}).ids;
var retval = device.sendAai({elements:ids, postAction:"getNodes"});

Synchronous actions

Great efforts have been put in to ensure the actions are synchronous, when a "click" is called, not only ensure the click is pressed synchronously, it will wait for certain "events" to ensure the rendering "starts" to happen, cannot ensure the rendering is completed. FindNode offers several ways to performed :

  • "click": it should work most of the time.
  • "waitQuery": if the new window takes very long time to complete the rendering, use this command to check if the node is available, add action:"click", will click when the node is found and ready.

There are 2 types of popup windows, they appear the same but they are implemented differently:

  1. The popup window changes the root node of node tree, this will be handled correctly (e.g. Whatsapp).
  2. The popup window created a new branch in the node tree, the resulted nodes will be accumulated nodes of main window and popup window, may likely find the wrong nodes. FindNode will monitor the events when this happens, will correctly predict the top branch of the node tree when popup window is opened and closed. When you start FindNode, make sure the application is not in the popup mode (Google Mail, Google search).

For second type of popup window, FindNode can identify some of the popup windows (Many Google applications use this type of popup), If the popup window breaks the FindNode, it will exhibit with one the following behaviors:

  • Nodes not found (even "TP:all") in query.
  • Very little nodes are displayed in UI Explorer.
  • Accumulate the nodes from popup window and the main window. UI Explorer will show a lot of bounds that do not make sense (nodes from main window).

When this happens, close (remove from memory) and reopen application, if popup windows does not work, please contact support.


To find the nodes via Query is not very difficult, you need is "UI Explorer" and Terminal:

  • Node ID is important, it is usually unique to the application.
  • Terminal: Type: device.sendAai({query:"…"}) to find if it matches the node. There are many search options, there is more than one way to find nodes.
  • UI Explorer:
    • Find bounds from different nodes.
    • Find properties inside the node.
    • The "Code" in UI Explorer is a best effort to locate a node, refer to postAction "getUniqQuery" for more information.
    • Use "Custom" query to try out different options to locate nodes.
  • Terminal: When in doubt, use UI Explorer to find node ID, use device.sendAai({elements:["<ID>","<ID>"…], action:"getNodes"}), this will help you to obtain more information.
  • Start with one device using "device.sendAai" change to multiple devices via "devices.sendAai", you can search the devices by using Device.searchObject() to locate the devices you need (e.g. all devices, devices in a group, specific devices).

Basic query examples

The basic queries are performed before other queries (with the exceptions of LT and LB), the matching nodes are identified by node ID. If the query is empty, the default query is "TP:more", if action is empty, default action is "getIds".

device.sendAai({}) → device.sendAai({query:"TP:more", action:"getIds"})
>> device.sendAai({})
{count: 9, ids: ['95b0','b779','bb3a','9d32','a0f3','a4b4','a875','ac36','aff7']}
>> device.sendAai({query:"TP:more", action:"getIds"})
{count: 9, ids: ['95b0','b779','bb3a','9d32','a0f3','a4b4','a875','ac36','aff7']}
>> device.sendAai({query:"IX:0", action:"getIds"})
{count: 1, ids: ['95b0']}
>> device.sendAai({query:"TP:more&&IX:-1"})
{count: 1, ids: ['aff7']}

Can include more complex query like:

>> device.sendAai({query:"T:/^.$/&&C:.Button&&R:.button_*", action:"getText"})
{retval: ['÷','×','7','8','9','-','4','5','6','+','1','2','3','%','0','.','±','=']}

If the match cannot be found, it will return null, use "lastError()" to display error message:

>> device.sendAai({query:"T:X", action:"getText"})
>> lastError()
No match found
>> device.sendAai({query:"X:X", action:"getText"})
>> lastError()
Error:Cannot find the key for "X"

Programmatically, always check for the return value of null.

Expanded Query

These are the queries that is not specific to a node itself, there are listed in the order of precedence:

Top lines and bottom lines (LT & LB) → M

Line mode: LT:<Integer> and LB:<Integer>. This only applies to app with scrollable container or LT and LB will return null. FindNode will locate the UI elements outside of scrollable region, line mode will group UI elements into a series of lines, each line can have one or more nodes. Many applications have fixed UI elements on the top and bottom of the screen, this can be easily located by using query such as "LT:1||IX:2" (3nd node of the top line) or "LB:-1||T:Chats" bottom of the line, with Chats as text.

  • For non-scrollable apps such as calculator, LT and LB will always return null.
  • Between bottom of the status line and top of the scrollable region is considering as top region, LT starts from 1, "LT:1" is first line. LT:-1 is last line in that region.
  • Between bottom of the scrollable region and top of the navigation bar is considering as bottom region. Similarly, LB starts from 1, LB:1 is first line. LB:-1 is last line in that region (also the last line of the application).
  • Using out of bound line number will get null. (E.g. 2 lines at the bottom, LB:3 will get the null).
  • If one node height is similar to 2 vertical nodes height on the same line, the lower node will be placed in next line.
  • Many applications put the top line within the scrollable area, the LT will always return null.
  • Not interfere with "IX", the small red counter or red dot next to the nodes will be removed (not in a return list).
  • This query is not ideal, will change in the future.
>> device.sendAai({query:"LT:1"})
{count: 6, ids: ['12b9f4','12ccb9','12e33f','1d7d52','12f9c5','1317cd']}
>> device.sendAai({query:"LB:-1"})
{count: 5, ids: ['1ea5e3','1ea9a4','1ead65','1ebc69','1ed2ef']}

For non-scrollable applications, "LB" will return null but it is not hard to get the nodes on the bottom of the app:

>> device.sendAai({query:"IX:-1&&TX", action:"getText"})
{retval: ['Speed','Video','VPN','Map']}

The other way is to use ViewGroup. Each label has a ViewGroup, so need to level 2.

{query: "IX:-1&&VG:2"}

Use "reduce nodes" (RN) to reduce down to 8 nodes:

{query: "IX:-1&&VG:2&&RN"}

You can now click for instance Speed (use nsClick if Speed already selected, click has no visible effect):

>> device.sendAai({query:"IX:-1&&VG:2&&RN&&PQ:'T:Speed'", action:"nsClick"})
{retval: true}
>> device.sendAai({query:"IX:-1&&VG:2&&RN", action:"nsClick(T:Speed)"})
{retval: true}

To click icon:

>> device.sendAai({query:"IX:-1&&VG:2&&RN", action:"nsClick(T:Speed&&OY:-1)"})
{retval: true}

Alternatively, just want to click "Speed":

>> device.sendAai({action:"nsClick(T:Speed&&IX:-1)"})
{retval: true}

Template (TP)

We developed several predefined templates that is built-in FindNode, this is a short-form composed largely from basic queries:

  • all: return all nodes. This is the default if no template is defined.
  • more: "all" option with layout nodes removed. This is the default if query is empty.
  • basic: "more" option with child count of zero.
  • reduced: "more" option with "RN" (or reduceNodes).
  • findText: search for nodes with text or description with certain text. findText needs an extra parameter to specify text to search.
  • anyText: search for nodes with content in text or description (non-null).
  • textInput: search with input type > 0 or ".EditText" class, attempt to find input text field. This is also sorted from top-left to bottom-right. "IX:0" is the first text input, "IX:1" is the second text input, "IX:-1" is the last text input. Default is the first node.


>> device.sendAai({query:"TP:textInput"})
{count: 6, ids: ['15cac','1642e','16f71','176f3','18236','189b8']}
>> device.sendAai({query:"TP:textInput&&IX:-1", action:"setText(John)"})
{retval: true}
Alternatively, you can use hint text to search for node:
>> device.sendAai({query:"T:Person Name", action:"setText(John)"})
{retval: true}

{query:"TP:findText,OK", action:"click"}
// Obtain stock symbols
>> device.sendAai({query:"TP:findText,/^[A-Z]{1,4}$/", action:"getText"})
{retval: ['TGT','INTC','WMT','T','AAPL','SBUX','TSLA']}

>> device.sendAai({query:"TP:all"}).count
>> device.sendAai({query:"TP:more"}).count
>> device.sendAai({query:"TP:basic"}).count
>> device.sendAai({query:"TP:reduced"}).count


>> device.sendAai({actions:[
{count: 4, list: [{count: 250},{count: 245},{count: 75},{count: 56}]}

Index (IX) M → O

IX:<number>. Return a node from ML nodes based on the position starting from zero (IX:0). IX can be negative value in which case the position is in reversed order (e.g. -1 is the last node).


>> device.sendAai({})
{count: 9, ids: ['95b0','b779','bb3a','9d32','a0f3','a4b4','a875','ac36','aff7']}
>> device.sendAai({query:"IX:1"})
{count: 1, ids: ['b779']}
>> device.sendAai({query:"IX:-2"})
{count: 1, ids: ['ac36']}

Single Node Selection (ON) M → O

ON:<option> Return one node from multiple nodes in ML, currently 4 options are available. Only limited options at this time, will expand in the future.

These are the options:

  • first: Take the first node from the ML (Similar to IX:0).
  • last: Take the last node from the ML (Similar to IX:-1).
  • min: Pick the smallest node (in area) from the ML.

ON and IX needs to be mutually exclusive.

Offset (OX & OY) O → O

OX:<integer> and OY:<integer>. Use the offset to find neighboring node base on one reference node, OX find horizontal position, positive integer moves right, negative integer moves left. For OY, positive integer moves down, negative integer moves up. If both options are present, the options will apply based on the position in the query. For example: "OX:1||OY:2" will apply horizontal before vertical offset where "OY:2||OX:1" will apply vertical before horizontal offset. The order is important to locate asymmetrical arrangement of the nodes.

Examples: In About phones:

>> device.sendAai({query:"T:Model name&&OX:1", action:"getText"})
{retval: 'Galaxy S22 Ultra'}
>> device.sendAai({query:"T:Model number&&OX:1", action:"getText"})
{retval: 'SM-S908U1'}
>> device.sendAai({query:"T:Android version&&OY:1", action:"getText"})
{retval: '12'}


>> device.sendAai({query:"T:5&&OX:1", action:"getText"})
{retval: '6'}
>> device.sendAai({query:"T:5&&OY:-1", action:"getText"})
{retval: '8'}

// OX & OY orders may get different results
>> device.sendAai({query:"T:0&&OY:-1&&OX:2", action:"getText"})
{retval: '×'}
>> device.sendAai({query:"T:0&&OX:2&&OY:-1", action:"getText"})
{retval: '−'}

ViewGroup (VG) O → M

ViewGroup: VG or VG:<level>. This command accepts optional level, if level is not specified, 1 will be used as level. It accepts a node (from query), will do parent traversal until a "group" is found, level is used to specify when to stop. A "group" is layout that occupy 85% ("setConfig(selector:viewGroupWidthRatio, )" to change that) of the screen width or "ViewGroup" in the class. ML will contain the nodes within the group, with the first node is ViewGroup/Layout node itself. It is very useful to logically group interesting nodes and use "PQ" or "addQuery" to search the nodes within the group.

Related action: "getViewGroup".

Examples: Pick the right level:

>> device.sendAai({query:"T:Stop Charging&&VG", action:"getIds"})
{count: 2, ids: ['4a1fb','4a5bc']}
>> device.sendAai({query:"T:Stop Charging&&VG:2", action:"getIds"})
{count: 20, ids: ['45326','456e7','45aa8','45e69','4622a','465eb','4712e','469ac','46d6d','474ef','478b0','47c71','487b4','48b75','49e3a','4a1fb','4a5bc','4a97d','4ad3e','4b0ff']

Intersect (TX & TY) O → M

Intersect: TX and TY. These commands take a node as a reference node and change the ML to contain all the nodes that intersect horizontally (intersectX) or vertically (intersectY). The matcher will make use of the bounds (top/bottom for intersectX, left/right for intersectY) of the reference node to search for intersected nodes.

The size (width or height) of the reference node is very important, if a wide reference node is picked, TY will match most of the nodes on screen. Pick reference node wisely.

This command will take first node and generate multiple nodes in ML, you can use "addQuery" to set more constraints on them.

Related actions: "intersectX" and "intersectY".

Examples: On the offset calculator example above:

>> device.sendAai({query:"T:2&&TX"})
{count: 6, ids: ['bb4b','bf0c','c2cd','e0d5','e496','111a2']}
>> device.sendAai({query:"T:2&&TY"})
{count: 6, ids: ['1d115','1d4d6','a886','b3c9','bf0c','ca4f']}

But if you pick result as reference node with wide scroll entire screen, OX will get one node (itself) and OY will get all nodes.

>> device.sendAai({query:"R:.result&&TX"})
{count: 1, ids: ['1d4d6']}
>> device.sendAai({query:"R:.result&&TY"})
{count: 23, ids: ['1d897','1d4d6','d592','f39a','a4c5','a886','ac47','dd14','1029e','b008','b3c9','b78a','e0d5','bb4b','bf0c','c2cd','111a2','e496','c68e','ca4f','ce10','120a6','e857']}

Reduce nodes (RN) M → M

Reduce Nodes: RN. This is the one of the important features of FindNode, this feature accepts a list of node IDs, the intent to reduce the number of visible nodes. "TL", "BL", "OX/OY" and "intersect" all use the reduceNodes. Note that the reduceNodes will pick smaller nodes if bigger nodes are fully contain the smaller nodes. For instance, if smaller ".Button" node is enclosed within ".TextView" node, the ".TextView" node will be selected over ".Button" node, the "click" on the .TextView node will still work. See UI Explorer "Optimize" mode.


>> device.sendAai({}).count
>> device.sendAai({query:"RN"}).count

Related action:"reduceNodes".

Sort (ST) M → M

Sort: ST:X|Y|YX. Sort based on the bounds of the nodes, usually the way three is structure and the search, quite likely it has been ordered from top to bottom. Unless the order is important, there is little reason to sort.

X – compare the left bounds of the nodes

Y – compare the top bounds of the nodes

YX – compare top bounds and then left bounds of the nodes

>> device.sendAai({query:"C:.Button", action:"getText"})
{retval: ['AC','÷','×','DEL','7','8','9','-','4','5','6','+','1','2','3','%','0','.','±','=']}
>> device.sendAai({query:"C:.Button&&ST:Y", action:"getText"})
{retval: ['AC','÷','×','DEL','7','8','9','-','4','5','6','+','1','2','3','%','0','.','±','=']}
>> device.sendAai({query:"C:.Button&&ST:YX", action:"getText"})
{retval: ['AC','÷','×','DEL','7','8','9','-','4','5','6','+','1','2','3','%','0','.','±','=']}
>> device.sendAai({query:"C:.Button&&ST:X", action:"getText"})
{retval: ['AC','7','4','1','0','÷','8','5','2','.','×','9','6','3','±','DEL','-','+','%','=']}

Related action: sort

Post query(PQ) M → M

Post query: PQ:'<query>'. Consider the following query (Obtained from UI Explorer Custom Query):

Query: T:Overall Cpu Usage&&VG

"VG" is used to obtain the group, if we want to obtain the number of "Cpu Cores", the following query will not work:

Query: T:Overall Cpu Usage&&VG&&T:Cpu Cores&&OX:1 → T:Cpu Cores && OX:1 && VG

The basic queries (this example "T" and "OX") will be applied before "VG" (See Precedence). We need a secondary query to make it work. There are 2 ways to do it, "post query" in query and "addQuery" in action.

>> device.sendAai({query:"T:Overall Cpu Usage&&VG&&PQ:'T:Cpu Cores&&OX:1'", action:"getText"})
{retval: '8'}
>> device.sendAai({query:"T:Overall Cpu Usage&&VG", actions:["addQuery(T:Cpu Cores&&OX:1)", "getText"]})
{count: 2, list: [{count: 1},{retval: '8'}]}

Post query applies the query after all queries are done. Parser will only accept query in single quote for PQ.

Related action: addQuery

For line mode, the "intersectY" of the first node is applied of every line, usually it is fine, but in the below cases, it will return all nodes:

So "LT:1" will return all 6 nodes.

For non-scrollable applications, "LB" will return null but it is not hard to get the nodes:

You can use one of the labels as anchor (use "ON:last" or "IX:-1" to make sure query applies to intended nodes, in case screen content has the same text:

>> device.sendAai({query:"T:Speed&&ON:last", actions:["intersectX", "getText"]})
{count: 2, list: [{count: 4},{retval: ['Speed','Video','VPN','Map']}]}

To retrieve all icons:

>> device.sendAai({query:"T:Speed&&ON:last&&OY:-1", postActions:["intersectX", "getIds"]})
{count: 2, list: [{count: 4},{count: 4, ids: ['181ce','19c15','1b65c','1d0a3']}]}

To click the icon on VPN:

>> device.sendAai({query:"T:VPN&&ON:last&&OY:-1", postAction:"click"})
{retval: true}


FindNode offers many action commands, "action" is for single command and "actions" is an array of multiple action commands. Query is optional, if query is not specified, it will default to "TP:more", the commands/actions require nodes will be obtained from underlying ML, use action:"getIds" to obtain ML content.

device/devices.sendAai({query:"…", action:})

device/devices.sendAai({query:"…", actions:[, , …]})

device/devices.sendAai({actions:[, , …]})

When sendAai encounter error from FindNode or timeout, it will return null with "lastError()" contain error message.


Since "Skype" is string, the quote can be omitted:


For action that needs one node (e.g. "click"), the first node in the ML will be chosen (ON, IX, OX and OY can change that). There are many commands in actions, there are incompatibilities between different version, please use preAction:"version" (version 11) or action:"version" (version 12) to ensure the version supports the commands required. Some commands require additional arguments (the arguments will be enclosed in parenthesis). Currently, the following types of arguments are supported:

  • "String" or 'String': String enclosed by single quote or double quote.
  • <no quote>: If string does not contain comma or trailing spaces, no quote is required.
  • Boolean: true/false.
  • Integer: Number with or without decimal.
  • Double: Number with decimal points.

A few examples:


If you want to use variable in the action commands, use one of the following 4 options:

var input = "Hello"
>> "setText('" + input + "')"
>> 'setText("' + input + '")'
>> 'setText(' + input + ')'
>> aaix("setText", input)
actions:[aaix("setText", input), "addQuery('OX:2')", "click"]

If the argument is string, below 3 examples are identical:

actions:["intersectX(OY:1, IX:2)", "getText"]
actions:["intersectX('OY:1', 'IX:2')", "getText"]
actions:[aaix("intersectX", "OY:1", "IX:2"), "getText"]

Most of the actions have optional query string in first argument, for instance:

{actions:["newQuery(T:John)", "click"]}	→ {action:"click(+T:John)"}
{actions:["addQuery(T:Mary)", "click"]}	→ {action:"click(T:Mary)"}

The added query string will make command more concise. "newQuery" prefixed by "+" will clear ML, make the search and reconstruct the ML. "addQuery" will search based on the existing ML.

Get Commands

getBounds (M)

Return the bounds (format: [left, top, right, bottom]) of the nodes in ML, it returns 2 arrays one with all the IDs the other one with all the bounds.

Usage: getBounds [query]

Return: The array of bounds, each bound is an array of 4-tuples: [x1, y1, x2. y2].

>> device.sendAai({query:"T:/[0-9]/", postAction:"getBounds"})
{bounds: [[18,1158,370,1521],[370,1158,722,1521],[722,1158,1074,1521],[18,1521,370,1884],[370,1521,722,1884],[722,1521,1074,1884],[18,1884,370,2247],[370,1884,722,2247],[722,1884,1074,2247],[18,2247,370,2610]], count: 10, ids: ['98d1','9c92','a053','a7d5','ab96','af57','b6d9','ba9a','be5b','c5dd']}

getBoolProp (O)

Return the Boolean property of a node, it will return "editable", "progressible", "clickable", "checkable", "scrollable" or "longClickable". If no property is found, it will return null.

Usage: getBoolProp [query]

Return: The array of string properties.

>> device.sendAai({query:"T:/12:30\\sPM/&&VG", action:"getBoolProp(T:Every day&&OX:1)"})
{retval: ['checkable','clickable']}
>> device.sendAai({query:"T:/12:30\\sPM/&&VG&&PQ:'T:Every day&&OX:1'", action:"getBoolProp"})
{retval: ['checkable','clickable']}
>> device.sendAai({query:"T:ci:calculator", action:"getBoolProp"})
>> lastError()
No boolean property is found

getBoolPropInAncestors (O)

Similar to getBoolProp except it traverse parent to its parent until it reaches the top node tree.

Usage: getBoolPropInAncestors [query]

Return: The array of array of property and node ID or null if not found.

getChecked (O/M)

These commands apply to nodes with "checkable" nodes. Typically, toggle controls, checkboxes or radio buttons are checkable. For instance, class name of ".Switch" or ".Checkbox" are checkable.


Return: If input is one node, it will return true/false in "retval", if multiple nodes, retval will contain the array of true/false, for non-checkable nodes, it will show as "N/A". Return null if none of the checkable node is found.

See also: setChecked

>> device.sendAai({query:"T:/12:30\\sPM/&&VG&&PQ:'T:Every day&&OX:1'", action:"getChecked"})
{retval: true}
>> device.sendAai({query:"T:/12:30\\sPM/&&VG&&PQ:'R:/.day_button_[0-9]/'", action:"getChecked"})
{retval: [false,true,false,true,false,true,false]}
 >> device.sendAai({query:"T:/12:30\\sPM/&&VG", action:"getChecked"})
{retval: ['N/A','N/A','N/A','N/A','N/A',true,'N/A',false,true,false,true,false,true,false,'N/A','N/A',true,'N/A','N/A']}

getDescription/getText (O|M)

Simpler version of "getNodes(T)" or "getNodes(D)", much easier to parse if you do not intent to know the node ID. It will detect one or multiple nodes. getDescription/getText are identical except one get the text the other one get the description.

getDescription [query]

getText [query]

Return: Return an array of text/description in retval if multiple nodes are found, return text/description in retval if one node is found.


>> device.sendAai({query:"LB:-1", postAction:"getText"})
{retval: ['Chats','Calls','Contacts','Notifications']} 
>> device.sendAai({query:"LB:2||IX:1", postAction:"getText"})
{retval: 'Calls'} 


Return the node receiving focus ("focusable" in getNodes(BP)) (usually text field or slider) or null if no node gains focus. If node is found, it will set the ML to the found node.

Return: Return "node" will node ID if found otherwise return null.

Example: To enter the next line from the existing line

>> device.sendAai({actions:["getFocus", aaix("setText", username), "addQuery(OY:1)", aaix("setText", password)]})
{count: 4, list: [{node: '67693'},{retval: true},{count: 1},{retval: true}]}

getIds (M)

This is the default action if action is not defined, this command will return count and array of the ML IDs.

Usage: getids [query]

Return: count and array of IDs.


>> device.sendAai({})
{count: 26, ids: ['7708','d8a2','e7a6','ef28','824b','860c','89cd','8d8e','914f','9510','98d1','9c92','a053','a414','a7d5','ab96','af57','b318','b6d9','ba9a','be5b','c21c','c5dd','c99e','cd5f','d120']}
>> device.sendAai({action:"getIds(C:.Button)"})
{count: 4, ids: ['8dc5','9547','9908','9cc9']}

getNodes (M)

This is used to retrieve the information of nodes. The fields can determine what to display. "getNodes" will refresh the node before retrieving information.




fields: The optional "fields" is used to define the fields to return, multiple fields in "fields" are separated by comma. Since it has comma inside the "field", you need to use the quote. String The valid field identifiers are:

  • P: Package name (S)
  • C: Class name (S)
  • R: Resource ID (S)
  • D: Description (S)
  • T: Text (S)
  • IT: Input type (I)
  • CC: Child count (I)
  • RI: RangeInfo, provide more information about the type, min, max, current of widget such as SeekBar (slider), use "setProgress" command to change the value.
  • BP: All boolean properties in the node.
  • B: Return [left top][right bottom] bounds of the node (S)
  • All: Everything

Return: Count and array of node information.

>> device.sendAai({query:"T:/[0-9]/", action:"getNodes('R,T')"})
{count: 10, list: [{id: '98d1', resourceId: '.button_seven', text: '7'},{id: '9c92', resourceId: '.button_eight', text: '8'},{id: 'a053', resourceId: '.button_nine', text: '9'},{id: 'a7d5', resourceId: '.button_four', text: '4'},{id: 'ab96', resourceId: '.button_five', text: '5'},{id: 'af57', resourceId: '.button_six', text: '6'},{id: 'b6d9', resourceId: '.button_one', text: '1'},{id: 'ba9a', resourceId: '.button_two', text: '2'},{id: 'be5b', resourceId: '.button_three', text: '3'},{id: 'c5dd', resourceId: '.button_zero', text: '0'}]}
>> device.sendAai({query:"C:.SeekBar", action:"getNodes(RI)"})
{count: 1, list: [{id: 'b6f0', rangeInfo: {current: 0, max: 100, min: 0, type: 0, typeString: 'int'}}]}
>> device.sendAai({query:"C:.SeekBar", action:"getNodes(BP)"})
{count: 1, list: [{booleanProperties: {checkable: false, checked: false, clickable: false, editable: false, enabled: true, focusable: true, focused: false, longClickable: false, multiLine: false, scrollable: false, selected: false, visibleToUser: true}, id: 'b6f0'}]}


Return the current running package name.

Return: Package name in "retval".

>> device.sendAai({action:"getPackageName"})
{retval: ''}

getProgress (O)

Usage: getProgress [query]

Return the RangeInfo in "retval" of a progressible node, return null if the node is not progressible node.

>> device.sendAai({action:"getProgress(C:.SeekBar)"})
{retval: {current: 41, max: 100, min: 0, type: 0, typeString: 'int'}}

getQuery/getUniqQuery (O)

Accept one node and attempt to generate a query to match the node (or multiple nodes). The query returned from "getQuery" can match multiple nodes, useful to get the text or description from multiple nodes. The query returned from "getUniqQuery" will match only one node, the query obtained will be sent to other devices to locate the nodes. So far, it is rudimentary, we will develop better query string in the future:

  • Will use the first node of the matching list (query or element) as the reference node.
  • The output is not fully optimized, for instance, it does not use template.
  • If multiple nodes are found, "getUniqQuery" will add "IX" to identify the single node, this is error prone especially for applications that do not use resource ID, IX can be large number, any change in UI may impact the accuracy.
  • UI Explorer’s "Code" button use this command to display the query string.
  • If offset (OX|OY) is used, it will search for neighboring node with text information (e.g. T:<text>).
  • If the text or description information is dynamic, use ignoreText/ignoreDescription.
  • For text and description, if the length is longer than 30 characters, it will list the first 30 characters and add "*" at the end. E.g. If the text of the node is "The quick brown fox jumps over the lazy dog", the query generated will be: "T:The quick brown fox jumps over*".

getUniqQuery/getQuery - Default is to use text and description as part of the query.

getUniqQuery/getQuery(<true/false> to ignore text) – true to ignore text in search query.

getUniqQuery/getQuery(<true/false> to ignore text, <true/false> to ignore description) – true on second argument to ignore description in search query.


true or false: to ignore text in query.

true or false: to ignore description in query.

Return: return query string.

To obtain query for each node:

>> device.sendAai({query:"T:Controls&&TX"}).ids.forEach(
    x => print(device.sendAai({elements:[x],action:"getUniqQuery"}).query)

>> var query = device.sendAai({query:"T:AAPL", action:"getQuery(true)"}).query
>> query
>> device.sendAai({query:query, action:"getText"})
{retval: ['MSFT','INTC','TGT','T','AAPL','SBUX','TSLA','CSCO']}

Query Commands

The following commands with one exception (waitQuery) will affect the ML.

load/save (M)

These commands store or retrieve the ML, this is only useful for multiple commands, save the ML, newQuery + actions, load and continue.

intersectX/intersectY (O → M)

Without argument, this command is same as "TX" and "TY". This command will change the internal matching list, so you can use "addQuery" to set constraints or action to act upon them. This command will take one node and generate more nodes, if internal matching list contains multiple nodes, the first node will be used. "intersect" and "addQuery" are usually tied together, we have added addQuery before and after the intersect.


intersect?(<post query>) = "intersect?", "addQuery(<post Query>)"

intersect?(<pre Query>, <post query>) = "addQuery(<pre query>)", "intersect?", "addQuery(<post query>"


true /false: if the operation is successful. If query is specified, the output of post query (see "addQuery") will be the output value of this command.

See also: "TX" and "TY" queries.


>> device.sendAai({query:"TP:textInput", postActions:["getIds", "intersectX", "getIds"]})
{count: 3, list: [{count: 1, ids: ['379ea6']},{count: 5},{count: 5, ids: ['37845f','379ea6','37a9e9','37c06f','37d6f5']}]}

Obtain 3rd node (IX:2) of the next line (OY:1):

>> device,sendAai({query:"T:7", postActions:["intersectX(OY:1, IX:2)", "getText"]})
{count: 2, list: [{count: 1},{retval: '6'}]}

Without preQuery without postQuery, use empty string:

>> device.sendAai({query:"T:Chats||IX:-1", postActions:["intersectX(OY:-1,'')", "getIds"]})
{count: 2, list: [{count: 4},{count: 4, ids: ['140ab','15eb3','178fa','19341']}]}

getViewGroup (O → M)

This command is similar to "VG" query, will accept a level and query. Query if defined is used to identify the first node to seach for the group. Similar to "VG", it will change ML.



getViewGroup(<level>) // if argument is integer

getViewGroup(<query>) // if argument is string

getViewGroup(<query>, <level>)

Return: If ViewGroup is found, it will return the count in the ML, if ViewGroup is not found, it will return null.

>> device.sendAai({query:"T:Battery&&VG", action:"getText(T:/\\d%/)"})
{retval: '92% available'}
>> device.sendAai({actions:["getViewGroup(T:Battery)","getText(T:/\\d%/)"]})
{count: 2, list: [{count: 6},{retval: '92% available'}]}

reduceNodes (M → M)

It is identical to "RN" query, no argument. Will return "count".

Usage: reduceNodes

Return: Return the number of resulting nodes in "count".

>> device.sendAai({query:"TP:reduced"}).count
>> device.sendAai({action:"reduceNodes"})
{count: 54}

Sort (M → M)

It is identical to "ST" query, same argument as ST.

Usage: sort(X|Y|YX)

Return: Return true/false in "retval".


All accept query string, "newQuery" will discard ML, start the new query and place the resulting nodes in ML. "addQuery" perform search within ML, place the resulting nodes in ML. "waitQuery" will not change the ML, it has a timeout, will make sure the query is fulfilled before timeout expire or it will return null.

For instance: "VG" will usually follow by "addQuery" instead of "newQuery". On the other hand, click to open a new window, will need "newQuery".



waitQuey(<query>) // Default timeout = 2000ms

waitQuery(<query>, <timeout>)

Return: newQuery and addQuery return the size of resulting ML in "count". When timeout expire waitQuery will return null otherwise "retval" will contain true.

Action Commands

Commands in this section perform actions to the nodes.

click, nsclick, click2, longClick

Various kinds of clicks on the node, this is synchronous click, will make sure screen is starting to refreshed when the control is returned.

  • click: click in the center of the node.
  • longClick: hold and click longer (500ms) to simulate a long click.
  • click2: click at the random location in the node.

All the above clicks will monitor the events for the changes of the screen after the click is performed, if the screen is not changed, these clicks will fail and eventually timed out. If the screen is not changed after the click is performed, use "nsClick":

  • nsClick: Performed the click but does not monitor the events.

Return: true or false to indicate if the operation is successful.

click, nsclick, click2, longClick

click(<query>), nsclick(<query>), click2(<query>), longClick(<query>)


>> device.sendAai({query:"T:5", action:"click"})
{retval: true}
>> device.sendAai({action:"nsClick(T:5)"})
{retval: true}

refresh (M)

The accessibility nodes information is cached, if the screen has been updated, the cache information may not be accurate, use "refresh" to force to reread the nodes information.

Return: Return count and true (always return true)


>> device.sendAai({query:"T:Overall Cpu Usage&&OX:1", actions:["getText", "sleep(2000)", "refresh", "getText"]})
{count: 4, list: [{retval: '29'},{retval: true},{retval: true},{retval: '78'}]}


Accept query string and scrolling direction, will try to match the query by scrolling up/down, when one or multiple nodes are found, it will return, if the match cannot be found, it will return null with an error. The scroll will use page up/down with the maximum of 10 pages, the scroll may take some time, this command will automatically lengthen the timeout in "sendAai" until it is done. In order to make script be able to run in any resolutions and sizes, use scrollIntoView is better than PageUp/Down.

scrollIntoView(<query>) // Default down

scrollIntoView(<query>, <direction>)


<direction> can be one of the following, if not specify, the default is "down".

"down": Starting from current location and search by scrolling down.

"up": Starting from current location and search by scrolling up.

"top": Fast scroll to the top of page and search by scrolling down.

"bottom": Fast scroll to the bottom of page and search by scrolling up.

Return: Will return null if the query cannot be found otherwise will return true, ML will be set to the first matched node ID.


sendKey accepts key code or meta state and send the key to the screen (not node), the UI element with the focus will receive the keycode and meta, examples are text field or on screen keyboards. sendKey also offers shortcut, a text string some of the common key codes. Not all key codes will generate characters, several special key codes will bring in new window such as go back, appswitch or go to home screen. The key codes and meta states are displayed in Android KeyEvent class.

The shortcuts is nothing but a mapping to different key codes, following are all the shortcuts (case insensitive): "home", "back", "backspace", "enter", "appswitch" (switch to last app), for old applications, "search" and "menu".

sendKey(<key code>)

sendKey(<key code>, <meta state>)



key code and meta state: are in integer, "shortcut" is in string. Regarding the back key, some time you need to send 2 back keys to go back to previous page, first back key is to dismiss the keyboard (Sigma is invisible on screen but the input frame is visible on top of the window), the second back key is to go back to previous screen.

Return: return the status of the operation in true/false.


tcConst contains key code and meta state information. For example, the following 3 examples will get the same result:


This will type "A":

>> device.sendAai({preAction:aaix("sendKey", tcConst.keyCodes.A, tcConst.keyCodes.META_SHIFT_ON)})
{retval: true}

setChecked (M)

This command apply to nodes with "checkable" nodes. Typically, toggle controls are checkable, checkboxes or radio buttons, example of class can be ".Switch" or ".Checkbox". For radio button, setChecked(false) will not work since FindNode does not know which radio button to click to disable the existing radio button.



"setChecked(true|false)", since there is no permission to set the checked value, it will click the node to toggle the value if value needs to be changed. It accepts multiple nodes, will ignore non-checkable nodes.

Returns: setChecked returns "changedCount", how many checkable nodes have been changed.

setProgress (O)

To set the value of the slider bar, the min, max, type, value can be obtained by "getNodes('RI’)",



<number>: Integer or decimal to set the value.


true/false: if the operation is successful.


Set the display brightness to 50%:

>> device.sendAai({query:"T:Brightness&&VG&&PQ:'C:.SeekBar'", action:"getProgress"})
{retval: {current: 85983232, max: 267386880, min: 0, type: 0, typeString: 'int'}}
>> var max = device.sendAai({query:"T:Brightness&&VG&&PQ:'C:.SeekBar'", action:"getProgress"}).retval.max
>> device.sendAai({query:"T:Brightness&&VG&&PQ:'C:.SeekBar'", action:aaix("setProgress", max/2)})
{retval: true}

In sound setting, set all 4 sound volumes to 50%: 
    x => { 
        var max = device.sendAai({elements:[x], action:"getProgress"}).retval.max;
        device.sendAai({elements:[x], action:aaix("setProgress", max/2)});

setText (O)

If the node is text field, it will enter "input string" value to the input text field.

setText(<input string>)

setText(+<input string>)


Input string: By default the text field will be cleared before entering the string, if the first letter is "+", it will add on to the existing string. For multiple lines of text field, use "\n" to skip to next line.

Return: true or false to indicate if the operation is successful.


>> device.sendAai({query:"TP:textInput&&IX:1", action:"setText('Hello')"})
{count: 1, retval: true}
>> device.sendAai({query:"TP:textInput&&IX:0", action: "setText('+ World')"})
{count: 1, retval: true}

Enter Emoji via String.fromCodePoint:

>> device.sendAai({query:"TP:textInput",action:aaix("setText", String.fromCodePoint(0x1f60a))})
{retval: true}

Or mix with normal message:

>> device.sendAai({query:"TP:textInput", action:aaix("setText", 
"hello" + String.fromCodePoint(0x1f643, 0x1f644) + "world")})
{retval: true}

showOnScreen (O)

This will ensure the node will appear fully on the screen, if associated UI element is partially displayed, it will scroll until the UI element is fully visible on the screen. Depending on how the UI is designed, it may move to the top of the container. So, try before use.



Return: true to indicate the screen has been scrolled, false to indicate the node is already fully visible on screen, nothing is done.


This command will pause execution on the milliseconds specified, it is useful for time related wait before actions.



time: Specify time to wait in milliseconds.

Returns: return true

Other Commands

openApp/restartApp /closeApp version 11

3 commands to start, restart close the application on the device. All command accepts one argument, either a package name or the application display name (name show on the launcher), if multiple applications with the same name is matched, the first application will be used. No partial match is allowed, it is case insensitive match for application name. "closeApp" with no argument will close the existing application.

"openApp" will run the application, if the application is resided on the memory, it will resume the execution.

"restartApp" will first close the application before start the application, if automations need to start application in a known state. "closeApp" will close the application (force-close). To open an application to reach a "stable known state", use "restartApp" and "waitQuery".






retval: true for success or null with lastError() on failure.


Will open the android system setting window based the setting name specified, the setting name will be converted to uppercase letter, " " will be converted to "_" and name will add "_SETTINGS" to suffix if it is not already there:

Provide part of the setting in, use "listAndroidSetting" for list of settings available in this command. This command will open the setting window. If the setting is not found, it will return null.

openAndroidSetting(<Setting name>)

For instance: The following command open wireless setting:


The following open Apps setting:

>> device.sendAai({action:"openAndroidSetting(manage applications)"})
{retval: true}

"MANAGE_APPLICATIONS_SETTINGS", "manage applications setting ", "Manage Application" are the same.


This command returns the list of settings as an array.


>> device.sendAai({action:"listAndroidSetting"})
>> device.sendAai({action:"listAndroidSetting"}).list.length

config:set, config:get, config:unset

Change the value of the FindNode (or Selector), various values will be identified by name, use "config:get" to obtain the value, "config:set" to change the value and "config:unset" to revert back to the original value.

config:get(<name>) → name: <name>, value:<value>

config.set(<name>, <value>) → retval: true

config.unset(<name>) → name: retval: true

If the name is not found, it will return null.

Command Reference

Name Opt Query Arguments Input Output ML Change Prop
Get commands  
getBounds Yes All Nodes ids:[<id>], bounds:[<bounds>]
getBoolPropYes First Node retval:[<prop>]
getBoolPropInAncestorsYes First Noderetval:[<prop>]
getCheckedNoOne Node
All Nodes
getDescriptionYes One Node
All Nodes
getIdsYesAll Nodesids:[<ID>]
getNodesNo[fieldList(S)]All Nodeslist:[<info>]
getProgressYesFirst Noderetval:<rangeInfo>
First Node query:<query String>
getTextYesOne Node
All Nodes
First Nodequery:<query String>
Query Commands  
saveNoAll Nodes retval:<boolean>
getViewGroupYes [level(I)] First Nodecount:<count>YesVG
intersectXYes[preQ(S)] [postQ(S)]First Nodecount:<count> YesTX
intersectYYes [preQ(S)] [postQ(S)]First Nodecount:<count>Yes TY
reduceNodesNoAll Nodes count:<count>YesRN
sortNo<type(S)> All Nodesretval:<boolean>Yes ST
addQueryYesAll Nodescount:<count>Yes
newQueryYesNonecount:<count> Yes
waitQueryYes[duration(I)] Noneretval:<boolean>
Action commands  
clickYes First Noderetval:<boolean>
click2YesFirst Noderetval:<boolean>
longClickYesFirst Noderetval:<boolean>
nsClickYes First Noderetval:<boolean>
refreshYesAll Nodes retval:<boolean>
sendKeyNo<code(I)> [meta(I)]Noneretval:<boolean>
setCheckedNo<boolean>All Nodes changedCount:<count>
setProgressYes<value(F/I)>First Noderetval:<boolean>
setTextNo<text(S)>First Noderetval:<boolean>
showOnScreenYesFirst Noderetval:<boolean>
sleepNo<duration in ms(I)>Noneretval:<boolean>
Other commands  
versionNoNoneretval:<version number>
closeAppNo[<name(S)>]None retval:<boolean>
config:getNo<name(S)>Nonename: <name>, value: <value>
(S) - String, (I) - Integer, (B) - Boolean, (F) - Float, (*) - All types [Optional Arguments] or <name>:[<eturn array>]
device/devices.sendAai({query:<query>, action:<command>})
device/devices.sendAai({query:<query>, actions:[<command>]})


Please send email to for support and feedbacks.