This content applies solely to Appian RPA, which must be purchased separately from the Appian base platform. |
The following content is based on the previous version of the Robotic Task Designer.
In this example, we will develop a simple robot, which will be able to use the calculator to perform some operations. This will help you understand several concepts, such as robotic task definition, action and item, as well as the use of keystroke sequences.
Here is the source code:
Description | Link |
---|---|
Source Code | robot-tutorial-calc.zip |
We will create an initial structure of the project from the basic robot archetype provided by the Appian RPA repository. From a terminal session on the developer's machine, the Maven command will be:
1
mvn archetype:generate -B -DarchetypeGroupId=com.novayre.jidoka.robot -DarchetypeArtifactId=robot-archetype -DarchetypeVersion=x.y.z -DgroupId=com.novayre.jidoka.robot.tutorial -DartifactId=robot-tutorial-calc -Dversion=0.0.1 -Drepository=<mydomain>/rpa/repo/ -Pjidoka-repo
To properly launch the command above, the values of group, id and version should match the values assigned to the robotic task in the console.
The parameter –DarchetypeVersion will be set to the Appian RPA version we are using, so we will substitute x.y.z with the appropriate value.
In addition, the property –Drepository should be set to the Maven repository from which the generated robotic task will download the dependencies.
Once we have created the project structure, we may have to adapt the robotic task's class name, which initially is MyRobot, to have the same name and belong to the same package as was set in robotic task settings on the console. We will replace the content of the example robotic task class with our implementation.
Using the archetype is a good starting point because, apart from providing us the code for an example robotic task, we will begin from a Maven-adapted initial project setting, which will allow us to work on our robotic task in an easy and homogeneous way.
Appian RPA allows you to graphically design the robotic task definition by using the console's editor. We can draw boxes, connection arrows, as well as assign names and methods to each kind of action.
You can import this robotic task definition using
this file. To import the robotic task definition, you
have to click in the import button
(), and paste the data included in the
file.
The robotic task definition consists of actions, which are represented as boxes, with a different shape for each kind of action. The rounded grey boxes correspond to the special actions for the robotic task definition start and end. The squared dark orange boxes are robot's actions. And the diamond-shaped grey boxes are decision-making actions, or conditional actions.
In our case, upon starting, the robotic task will open the calculator, it will check whether there are operations to process and then it will perform them sequentially until no more are left. Then it will close the calculator, and finally, it will end.
In this example, we are using each arithmetic operation to be performed by the robotic task as an item.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Robot
public class CalculatorRobot implements IRobot {
@override
public boolean startUp() throws Exception {
}
@override
public String[] cleanUp() throws Exception {
}
@override
public String manageException(String action, Exception exception) throws Exception {
}
public void init() throws Exception {
// TODO Auto-generated method stub
}
public void openCalculator() throws Exception {
// TODO Auto-generated method stub
}
public void processOperation() throws Exception {
// TODO Auto-generated method stub
}
public String moreOperations() throws Exception {
// TODO Auto-generated method stub
}
public void closeCalculator() throws Exception {
// TODO Auto-generated method stub
}
public void end() throws Exception {
// TODO Auto-generated method stub
}
}
In the code snippet above, you can find the method definitions of the robot. Each action corresponds to a method, they must not have parameters and must return void, except for the methods in conditional actions, which will return String.
For the robotic task to compile and work properly, you should first include the class Operation, which will be used by our robot. This class implements the interface Item, specific to this robot, which serves just to highlight each operation's nature.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class Operation implements Item {
/**
* Serial.
*/
private static final long serialVersionUID = 1L;
/**
* First operand.
*/
private String operand1;
/**
* Operator.
*/
private String operator;
/**
* Second operand.
*/
private String operand2;
/**
* Constructor.
*
* @param operand1
* first operand
* @param operator
* operator
* @param operand2
* second operand
*/
public Operation(String operand1, String operator, String operand2) {
this.operand1 = operand1;
this.operator = operator;
this.operand2 = operand2;
}
public String getOperand1() {
return operand1;
}
public void setOperand1(String operand1) {
this.operand1 = operand1;
}
public String getOperator() {
return operator;
}
public void setOperator(String operador) {
this.operator = operador;
}
public String getOperand2() {
return operand2;
}
public void setOperand2(String operand2) {
this.operand2 = operand2;
}
@Override
public String toString() {
return operand1 + " " + operator + " " + operand2;
}
}
Every Appian RPA robotic task starts by declaring a class which implements the interface IRobot and is preceded by the annotation \@Robot. Additionally, the initial method is commonly used to initialize the robotic task and its components.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@Robot
public class CalculatorRobot implements IRobot {
/**
* Pause between interactions imitating the human behavior.
*/
private static final int PAUSE = 500;
/**
* Operations to do. These are the items.
*/
private final List<Operation> operations = Arrays.asList(new Operation("10", "+", "5"),
new Operation("4", "*", "3"));
/**
* Actual name of the application. It changes depending on the platform
* language.
*/
private static final String CALC_APP_NAME = ".*Calc.*";
/**
* Server.
*/
private IJidokaServer<?> server;
/**
* Client module.
*/
private IClient client;
/**
* Current item index
*/
private int currentItemIndex = 1;
@Override
public boolean startUp() throws Exception {
// Initialization of the robot components
server = JidokaFactory.getServer();
client = IClient.getInstance(this);
return IRobot.super.startUp();
}
/**
* Initial action 'Init'.
*/
public void init() {
try {
// Default pause after typing or using the mouse
client.typingPause(PAUSE);
client.mousePause(PAUSE);
// Set the number of items
server.setNumberOfItems(operations.size());
} catch (Exception e) {
throw new JidokaFatalException("Error initializing");
}
}
Remember that the instance server will help the robot to communicate with the console, and the instance windows will help it to control the interfaces of the machine on which the robotic task is running.
It is very important to establish the number of items to be processed by the robot. The statistics will be generated from that number, as well as the estimated time for the robotic task to complete its work. This information is sent to the console through the method setNumberOfItems.
The attribute currentItemIndex is also important in the instance of the robot. It will store the index of the item being processed in each iteration, and will be sent to the console through the method setCurrentItem. It is important to note that this value is 1-based, that is, the index counter starts at 1. Therefore, we should be careful when using this variable with, say, a list of items, for which we must transform it to its corresponding zero-based value by subtracting 1. Another choice would be to use a different attribute.
Below, you can find the code of the rest of methods, where you can see the use of the instance windows to control the keyboard, by constructing different kinds of keystroke sequences, not only for typing text, but also for shortcuts and special keys combinations.
The method openCalculator, which is shown below, opens the Windows Calculator.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* Action 'Open calculator'.
*/
public void openCalculator() {
try {
// Opens the menu "Run" in Windows.
// It is important to release the Win key.
client.typeText(client.getKeyboardSequence().pressWindows().type("r").releaseWindows());
// Executes the calculator application
client.typeText("calc");
client.typeText(client.getKeyboardSequence().typeReturn());
// Smart delay until the calculator application is opened
client.waitCondition(10, 1000, "Waiting for the calculator application to be opened", null, true,
(i, c) -> {
try {
return client.getWindow(CALC_APP_NAME) != null;
} catch (Exception e) {
return false;
}
});
// Activation of the window
client.activateWindow(CALC_APP_NAME);
client.pause(PAUSE);
} catch (JidokaUnsatisfiedConditionException e) {
throw new JidokaFatalException("Error opening the calculator");
}
}
You can see how we are using some of the methods of the instance windows, such as pause. It is used at the beginning of the method to perform an explicit pause during the execution, making the robotic task adapt its working pace to the systems response. After the pause, the robot sends the keyboard shortcut Win+R to open the Windows 'Run' menu. In this sentence, it is important to release the Windows key as the last step of the sequence. Otherwise, this key would remain pressed and any subsequent keyboard sequence would be affected.
The Appian RPA API evolves to make itself easier to use in each version and to cover more functionality. For example, it is even easier to send this key combination this way:
1
client.keyboard().windows("r");
The method typeText is overloaded, allowing you to write texts received as a parameter through String, or more complex keyboard sequences by using IKeyboardSequence.
The method processOperation that you can find below is in charge of retrieving an item, informing the server about the item number to be processed, processing it, notifying the server the result of the process and updating the current item index.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Action 'Process operation'.
*/
public void processOperation() {
try {
Operation operation = operations.get(currentItemIndex - 1);
// Notify to the server the start of this item process
server.setCurrentItem(currentItemIndex, operation.toString());
// Types the operation into the calculator
client.typeText(operation.getOperand1());
client.typeText(operation.getOperator());
client.typeText(operation.getOperand2());
client.typeText(client.getKeyboardSequence().typeReturn());
server.setCurrentItemResultToOK(client.copyAndGet());
currentItemIndex++;
} catch (IOException | UnsupportedFlavorException e) {
throw new JidokaItemException("Error processing the operation");
}
}
It is important to highlight the use of the method setCurrentItem for notifying the console of the start of a new item processing, as well as the method typeText for typing the operation in the calculator input. The operation result should also be reported to the console, for which we can use several methods of the instance server. In our example, we are using setCurrentItemResultToOk and setCurrentItemResultToWarn. The former is used when the result is correct; the latter is used when there is a problem while retrieving the operation result from the calculator with copyAndGet.
The last, but not least, line of the method increases the current item index, so that the right number is sent to the server in case there are no more items to process. Remember that the robotic task definition defines a loop, and that the variable currentItemIndex acts as a counter between operations.
Now we have three more methods:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* Action 'More?'.
*
* @return
*/
public String hasMoreOperations() throws Exception {
return currentItemIndex <= operations.size() ? "yes" : "no";
}
/**
* Action 'Close calculator'.
*/
public void closeCalculator() throws Exception {
// Closes the calculator
client.typeText(client.getKeyboardSequence().typeAltF(4));
}
/**
* Action 'End'.
*/
public void end() throws Exception {
// Continue the process. At this step, the robotic task ends its execution
}
Now, we have to overwrite the methods cleanUp and manageException:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* @see com.novayre.jidoka.client.api.IRobot#cleanUp()
*/
@Override
public String[] cleanUp() throws Exception {
client.killAllProcesses("calc.exe", 1000);
return new String[0];
}
/**
* Manage exception.
*
* @param action the action
* @param exception the exception
* @return the string
* @throws Exception the exception
*/
@Override
public String manageException(String action, Exception exception) throws Exception {
// We get the message of the exception
String errorMessage = ExceptionUtils.getRootCause(exception).getMessage();
// We send a screenshot to the log so the user can see the screen in the moment
// of the error
// This is a very useful thing to do
server.sendScreen("Screenshot at the moment of the error");
// If we have a FatalException we should abort the execution.
if (ExceptionUtils.indexOfThrowable(exception, JidokaFatalException.class) >= 0) {
server.error(StringUtils.isBlank(errorMessage) ? "Fatal error" : errorMessage);
return IRobot.super.manageException(action, exception);
}
// If the error is processing one items we must mark it as a warning and go on
// with the next item
if (ExceptionUtils.indexOfThrowable(exception, JidokaItemException.class) >= 0) {
server.warn(StringUtils.isBlank(errorMessage) ? "Item error" : errorMessage);
server.setCurrentItemResultToWarn(errorMessage);
return "hasMoreOperations";
}
server.warn("Unknown exception!");
// If we have any other exception we must abort the execution, we don't know
// what has happened
return IRobot.super.manageException(action, exception);
}
The task of this robotic task can be resolved almost completely by using the keyboard API, which is fluent-type, easy to use with the assistance of Eclipse auto-complete feature. Remember to check the Appian RPA Javadoc for a more global and complete overview of the operations set.
In this tutorial, we use the typical Windows keyboard shortcuts to simplify the implementation. With the robot's instance windows, we will control the keyboard through the method typeText, and we will chain fluent-type methods by using IKeyboardSequence sequences.
The keyboard API is large and can be used in different ways, depending on our needs. In this example, we focus on the use of the typeText option. In future examples, we will see that we can also use the keyboard through the interface IKeyboard.
Once our robotic task is implemented, we can upload it to the Appian RPA repository.
mvn clean deploy
We will check that there are no errors in the Appian RPA Console regarding the robotic task definition or its relationship with the robot's methods.
During the execution, we can check the host machine performance. This is the machine on which the Appian RPA agent is running. Once finished, we can check the execution log and the result on the console itself.