Jenkins integration

The TCP Server requires an Automation Toolbox license.

If you have a valid license for the automation toolbox you can enable a TCP server in Otii, making it possible to control Otii from another application. Using this feature you could e.g. use Otii in a continuous integration environment to automatically keep track of how firmware changes affects the energy consumption.

The following example shows how to add a test job that uses Otii in combination with Jenkins to make sure that a firmware change doesn't affect the energy consumption in a negative way.

Our test system

In this example we are using a ST32 Cortex M4 programmed with a ST-Link debugger. The board is powered by the Otii, and is connected to the ST-Link using the SWD interface to make it possible to flash the device with new firmware. To get a realistic measurement of the energy consumed, the ST-Link needs to be disconnected during the actual measurement.

For this reason we have developed a simple switch board that is connected to the expansion port of the Otii Arc, and is controlled by the GPO of the Arc. This makes it possible to connect the SWD when flashing the device, and then disconnect it when doing the energy measurements.

The device connects to the RX and the GPI1 of the Otii Arc. These are used in this example to mark the start and stop of parts of the measurements we want to verify.

The Python Otii Client

You need to install the Otii python client first, read more about it in the section Scripting with Python.

A first start

Create a test python script named otii_test.py. We will use the built in python unit test framework unittest to run our tests. Add the following code to otii_test.py:

#!/usr/bin/env python3
import unittest

class OtiiTest(unittest.TestCase):
    def test_energy_consumption(self):
        pass

if __name__ == '__main__':
    import xmlrunner
    unittest.main(testRunner=xmlrunner.XMLTestRunner(output='test-reports'))

Running the test script

Make the script executable:

chmod a+x otii_test.py`

And run the script:

./otii_test.py

Running tests...
----------------------------------------------------------------------
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

Configure the Otii TCP Server

See the TCP Server for information about how to configure and start the TCP server, either using the Otii desktop client, or using the Otii command line tool.

If you want to automatically start the server from your test script, read more in the section Scripting with Python.

Connecting to the Otii TCP Server

The next step is to connect to the Otii TCP server from the test script:

#!/usr/bin/env python3
import sys
import unittest
from otii_tcp_client import otii_connection, otii as otii_application

HOSTNAME = '127.0.0.1'
PORT = 1905

class OtiiTest(unittest.TestCase):
    def test_energy_consumption(self):
        connection = otii_connection.OtiiConnection(HOSTNAME, PORT)
        connect_response = connection.connect_to_server()
        if connect_response["type"] == "error":
            print("Exit! Error code: " + connect_response["errorcode"] + ", Description: " + connect_response["payload"]["message"])
            sys.exit()
        otii = otii_application.Otii(connection)

if __name__ == '__main__':
    import xmlrunner
    unittest.main(testRunner=xmlrunner.XMLTestRunner(output='test-reports'))

Configuring the Otii Arc

Now we query Otii for all the available devices, and try to find the correct Arc to use.

By giving each Arc a unique name, you will be sure that you are using the correct one.

...
PORT = 1905
ARC_NAME = "TestArc1"
...
class OtiiTest(unittest.TestCase):
    def test_energy_consumption(self):
        ...
        devices = otii.get_devices()
        if len(devices) == 0:
            print("No Arc connected!")
            sys.exit()
        devices = [device for device in devices if device.name == ARC_NAME]
        if len(devices) != 1:
            print("Expected to find exactly 1 device named {0}, found {1} devices".format(ARC_NAME, len(devices)))
            sys.exit()
        arc = devices[0]

        arc.set_main_voltage(3.3)
        arc.set_exp_voltage(3.3)
        arc.set_max_current(0.5)
        arc.set_uart_baudrate(115200)
        arc.enable_uart(True)
        arc.enable_exp_port(True)
        arc.enable_5v(True)  # The switch board is powerd by the Otii +5V pin.

We configure the main out and the expansion port to 3.3 V, we set the max current to 500 mA and configure the UART to a baudrate of 115000, and then enable the UART and expansion port.

Measurement

Now we are ready to start a measurement.

...
import time
...
MEASUREMENT_DURATION = 5.0
...
class OtiiTest(unittest.TestCase):
    def test_energy_consumption(self):
        ...
        project = otii.get_active_project()

        arc.enable_channel("mc", True)
        arc.enable_channel("i1", True)
        arc.enable_channel("rx", True)
        arc.set_main(True)

        project.start_recording();
        time.sleep(MEASUREMENT_DURATION)
        project.stop_recording();

        arc.set_main(False)

Analyzing the result

The demo system using the UART to send out a start and stop indicator for a part where the system does a temperature measurements and it using the digital input to mark the interval of each temperature measurement.

Using RX to analyze data

In this system, the DUT sends the log message "Getting temperature" when starting a temperature measurement. We use two of these messages to extract the energy consumed for a complete cycle.

...
class OtiiTest(unittest.TestCase):
        ...
        recording = project.get_last_recording()
        index = 0
        count = recording.get_channel_data_count(arc.id, "rx")
        data = recording.get_channel_data(arc.id, "rx", index, count)
        values = data["values"]
        timestamps = [value["timestamp"] for value in values if value["value"] == "Getting temperature"]
        self.assertGreaterEqual(len(timestamps), 2, "Need at least two \"Getting temperature\" timestamps")

        statistics = recording.get_channel_statistics(arc.id, 'mc', timestamps[0], timestamps[1])
        self.assertLess(statistics["energy"], 0.0004, "One interval consumes to much energy")
        self.assertGreater(statistics["energy"], 0.0002, "One interval consumes to little energy, is everything up and running?")

Using GPI1 to analyze data

In this system, the DUT sends a small pulse when it wakes up to start a temperature measurement. We try to find the first two pulses, and extracts the energy consumed for this interval.

...
class OtiiTest(unittest.TestCase):
        ...
        index = 0
        count = recording.get_channel_data_count(arc.id, "i1")
        gpi1_data = recording.get_channel_data(arc.id, "i1", index, count)["values"]
        timestamps = [gpi1_value["timestamp"] for gpi1_value in gpi1_data]
        self.assertGreaterEqual(len(timestamps), 4, "Need at least four GPI1 pulses")

        statistics = recording.get_channel_statistics(arc.id, 'mc', timestamps[0], timestamps[2])
        self.assertLess(statistics["energy"], 0.0004, "One interval consumes to much energy")
        self.assertGreater(statistics["energy"], 0.0002, "One interval consumes to little energy, is everything up and running?")

Building and uploading the firmware

This Jenkins job is going to be triggered when new firmware is merged to the master branch of the code repository. The first thing we need to do is to build the firmware, and then upload the new firmware to the device. We only need to do this once, so we add the class method setUpClass to the OtiiTest class, this will be called once before running the unit tests.

Since we have a switch board that is used to enable the debugger that is controlled by the Otii Arc, we have to move the connection and setup part from the test to this method. We make otii and arc class atrributes, making them accessible by all tests in this class.

...
class OtiiTest(unittest.TestCase):
    otii = None
    arc = None

    @classmethod
    def setUpClass(cls):
        # Connecting to Otii and setup Arc
        connection = otii_connection.OtiiConnection(HOSTNAME, PORT)
        ...
        arc.enable_5v(True)  # The switch board is powered by the Otii +5V pin.

        # Turn on the main power, and give the DUT time to startup.
        arc.set_main(True)
        time.sleep(1.0)

        # Enable the USB, and give the ST-lINK time to startup
        arc.set_gpo(1, True)
        time.sleep(3.0)

        # Upload new firmware
        result = subprocess.call("cd ../firmware; make; make upload", shell=True)
        if result != 0:
            print("Failed to upload new firmware")
            sys.exit()
        time.sleep(3.0)

        # Disable the USB, and turn off the main power
        arc.set_gpo(1, False)
        time.sleep(1.0)
        arc.set_main(False);

        def test_energy_consumption
        ...

Last updated