Calculator Tutorial

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 workflow, action and item, as well as the use of keystroke sequences.

Source Code

Here is the source code:

Description Link
Source Code robot-tutorial-calc.zip

Configuration

Environment

  • Development:
    • IDE: Eclipse
    • JDK 1.8
    • Maven 3
    • Appian RPA modules:
      • Client
  • Resource:
    • OS: Windows
    • JRE 1.8
    • Applications:
      • Windows calculator

Archetype

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 robot 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 robot will download the dependencies.

Once we have created the project structure, we may have to adapt the robot's class name, which initially is MyRobot, to have the same name and belong to the same package as was set in robot settings on the console. We will replace the content of the example robot class with our implementation.

Using the archetype is a good starting point because, apart from providing us the code for an example robot, we will begin from a Maven-adapted initial project setting, which will allow us to work on our robot in an easy and homogeneous way.

Workflow

Appian RPA allows you to graphically design the robot's workflow by using the console's editor. We can draw boxes, connection arrows, as well as assign names and methods to each kind of action.

rpa-calc-workflow.png

You can import this workflow using this file. To import the workflow, you have to click in the import button (2233684.png), and paste the data included in the file.

The workflow 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 workflow 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 robot 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 robot 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.

Implementation

For the robot 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 robot 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 robot 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 robot 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 robot 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 robot 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 Workflow defines a loop, and that the variable currentItemIndex acts as a counter between operations.

Now we have three more methods:

  • hasMoreOperations: this method determines, by using a boolean expression, whether there are more operations or the robot has ended processing all the items.
  • closeCalculator: this method types the shortcut Alt+F4 to close the Calculator window on the Windows desktop.
  • end: the method that ends the robot's execution.
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 robot 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);
    }

Keyboard

The task of this robot 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.

Execution

Once our robot 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 Workflow definition or its relationship with the robot's methods.

During the execution, we can check the resource 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.


This version of the Appian RPA documentation was written for Appian 20.4, and does not represent the interfaces or functionality of other Appian versions.
Open in Github Built: Tue, Sep 14, 2021 (11:54:22 AM)

On This Page

FEEDBACK