diff --git a/examples/ci-tests/README.md b/examples/ci-tests/README.md new file mode 100644 index 000000000..396b907b5 --- /dev/null +++ b/examples/ci-tests/README.md @@ -0,0 +1,58 @@ +Hardware CI Tests +================= + +This folder holds tests that run as part of Tock's +[hardware-in-the-loop CI](https://github.com/tock/tock/blob/master/doc/CI_Hardware.md). + +These are applications that are automatically loaded onto test hardware. + +Each test has an application which is loaded onto the Tock hardware platform +under test (`main.c`) and a test harness runner (`test.py`) which is run on the +supporting test-runner platform (currently expected to be a Raspberry Pi – +this is because the test runner must have low-level hardware access, i.e. GPIO, +I2C, SPI, etc support, and the test harnesses all use Raspberry Pi-specific +libraries to support this). + +While these tests are written to be run by the automated CI infrastructure, +they should generally also be abe to be run standalone as well (again, if run +on a Raspberry Pi). The tests also assume physical pin connections (i.e. a wire +between a pin on the board under test and the driving Raspberry Pi header) as +documented in each board's test harness description in the main Tock repo. + +To run a test, enter the test directory and invoke the unit test corresponding +to the hardware platform attached to the Raspberry Pi you are logged into: + + [rpi01] $ cd uart-rx-tx + [rpi01] $ python3 test.py Nrf52840Test + + Starting Uart Rx/TX Test... + + 0.000187 INFO -- Initiating UART test... + 0.016512 INFO -- Setting up for nrf52840dk Uart Rx/Tx test... + 0.016805 INFO -- Waiting for message... + 0.017386 INFO -- UART Transaction Beginning + 5.022696 INFO -- Waiting for message... + 5.023263 INFO -- UART Transaction Beginning + 10.028633 INFO -- Message sent: R + 12.031758 INFO -- Message Received (r[character]): rR + 12.032129 INFO -- Echoed: R + Correct Serial Communication Message Received + . + ---------------------------------------------------------------------- + Ran 1 test in 12.021s + + OK + +Tests use the standard Python [`unittest`](https://docs.python.org/3/library/unittest.html) framework. + +To the extent possible, tests SHOULD be hardware-agnostic. The only +per-platform specification should be pin assignment. Board configurations in +the main Tock repo specify supported hardware and tests. For this reason, if a +test attempts to run on a platform that inherently cannot support it (e.g. BLE +tests on a platform with no Bluetooth radio), the test MUST fail. + +Tests may assume full ownership of the hardware. Each folder is a logical unit +of testing and is designed to be run in isolation. Currently, there are no +multitenant hardware-CI tests. If and when these are added, current thinking is +to put concurrent test configurations into a folder such that the folder +remains the atomic unit of a test, but this is certainly subject to change. diff --git a/examples/ci-tests/ble/Makefile b/examples/ci-tests/ble/Makefile new file mode 100644 index 000000000..35dae882c --- /dev/null +++ b/examples/ci-tests/ble/Makefile @@ -0,0 +1,17 @@ +# Makefile for user application + +# Specify this directory relative to the current application. +TOCK_USERLAND_BASE_DIR = ../../.. + +# Which files to compile. +C_SRCS := $(wildcard *.c) + +# External libraries used +EXTERN_LIBS += $(TOCK_USERLAND_BASE_DIR)/simple-ble + +# Include userland master makefile. Contains rules and flags for actually +# building the application. +include $(TOCK_USERLAND_BASE_DIR)/AppMakefile.mk + +# Include simple-ble's Makefile so it's rebuilt automatically +include $(TOCK_USERLAND_BASE_DIR)/simple-ble/Makefile diff --git a/examples/ci-tests/ble/README.md b/examples/ci-tests/ble/README.md new file mode 100644 index 000000000..33169db38 --- /dev/null +++ b/examples/ci-tests/ble/README.md @@ -0,0 +1,68 @@ +# BLE Scan Test + +## Test Description + +`test.py` provides the following tests: + +### BleTest + +This test utilizes the test harness (RPi) capabilities to run a ble scan of the device-under-test to verfiy ble connectivity works as intended. The test harness simply runs a scan of all bluetooth devices in the vacinity, and locates the device-under-test (tock device) for ble connection. + +## Example + 0.000152 INFO -- Initiating BLE test... + 0.016245 INFO -- Setting up for nrf52840dk BLE test... + 0.016528 INFO -- Bluetooth Status on RPi Harness + + ● bluetooth.service - Bluetooth service + Loaded: loaded (/lib/systemd/system/bluetooth.service; enabled; vendor preset: enabled) + Active: active (running) since Fri 2021-08-27 18:17:17 UTC; 4min 38s ago + Docs: man:bluetoothd(8) + Main PID: 1912 (bluetoothd) + Status: "Running" + Tasks: 1 (limit: 973) + CGroup: /system.slice/bluetooth.service + └─1912 /usr/lib/bluetooth/bluetoothd + + Aug 27 18:17:17 ubuntu systemd[1]: Starting Bluetooth service... + Aug 27 18:17:17 ubuntu bluetoothd[1912]: Bluetooth daemon 5.53 + . (other information..) + . + . + Aug 27 18:17:17 ubuntu systemd[1]: Started Configure Bluetooth Modems connected by UART. + 6.719571 INFO -- Scan result error: + LE Scan ... + 08:99:10:53:EB:97 (unknown) + 40:8E:59:DA:D9:F0 (unknown) + F0:00:00:00:02:F0 TockOS + 65:83:87:B5:B6:22 (unknown) + . (more devices..) + . + . + + 6.720389 INFO -- BLE scan ended. + 6.720653 INFO -- Restarting test harness bluetooth. + . + ---------------------------------------------------------------------- + Ran 1 test in 6.899s + + + OK + +## Notes + +When manually invoking the test harness, you must specify the board under test. +Otherwise, python unittest will attempt to run tests for all platforms. + +To run the test, +```bash +sudo python3 test.py Nrf52840Test +``` + + +### Supported Boards + +CI has been validated and runs on the following hardware platforms: + +Board | Test Name +------|---------- +nrf52840dk | Nrf52840Test diff --git a/examples/ci-tests/ble/main.c b/examples/ci-tests/ble/main.c new file mode 100644 index 000000000..9b4b0a5d1 --- /dev/null +++ b/examples/ci-tests/ble/main.c @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include + +// Sizes in bytes +#define DEVICE_NAME_SIZE 6 +#define UUIDS_SIZE 4 +#define MANUFACTURER_DATA_SIZE 2 +#define FAKE_TEMPERATURE_DATA_SIZE 2 + +/******************************************************************************* + * MAIN + ******************************************************************************/ + +int main(void) { + int err; + printf("[Tutorial] BLE Advertising\n"); + + // declarations of variables to be used in this BLE example application + uint16_t advertising_interval_ms = 20; + uint8_t device_name[] = "TockOS"; + uint16_t uuids[] = {0x1800, 0x1809}; + uint8_t manufacturer_data[] = {0x13, 0x37}; + uint8_t fake_temperature_data[] = {0x00, 0x00}; + + static uint8_t adv_data_buf[ADV_DATA_MAX_SIZE]; + + // configure advertisement interval to 300ms + // configure LE only and discoverable + printf(" - Initializing BLE... %s\n", device_name); + AdvData_t adv_data = gap_adv_data_new(adv_data_buf, sizeof(adv_data_buf)); + + gap_add_flags(&adv_data, LE_GENERAL_DISCOVERABLE | BREDR_NOT_SUPPORTED); + + // configure device name as TockOS + printf(" - Setting the device name... %s\n", device_name); + err = gap_add_device_name(&adv_data, device_name, DEVICE_NAME_SIZE); + if (err < RETURNCODE_SUCCESS) + printf("ble_advertise_name, error: %s\r\n", tock_strrcode(err)); + + // configure list of UUIDs */ + printf(" - Setting the device UUID...\n"); + err = gap_add_service_uuid16(&adv_data, uuids, UUIDS_SIZE); + if (err < RETURNCODE_SUCCESS) + printf("ble_advertise_uuid16, error: %s\r\n", tock_strrcode(err)); + + // configure manufacturer data + printf(" - Setting manufacturer data...\n"); + err = gap_add_manufacturer_specific_data(&adv_data, manufacturer_data, + MANUFACTURER_DATA_SIZE); + if (err < RETURNCODE_SUCCESS) + printf("ble_advertise_manufacturer_specific_data, error: %s\r\n", + tock_strrcode(err)); + + // configure service data + printf(" - Setting service data...\n"); + err = gap_add_service_data(&adv_data, uuids[1], fake_temperature_data, + FAKE_TEMPERATURE_DATA_SIZE); + if (err < RETURNCODE_SUCCESS) + printf("ble_advertise_service_data, error: %s\r\n", tock_strrcode(err)); + + // start advertising + printf(" - Begin advertising! %s\n", device_name); + err = ble_start_advertising(ADV_NONCONN_IND, adv_data.buf, adv_data.offset, advertising_interval_ms); + if (err < RETURNCODE_SUCCESS) + printf("ble_start_advertising, error: %s\r\n", tock_strrcode(err)); + + // configuration complete + printf("Now advertising every %d ms as '%s'\n", advertising_interval_ms, + device_name); + return 0; +} diff --git a/examples/ci-tests/ble/test.py b/examples/ci-tests/ble/test.py new file mode 100644 index 000000000..7557e1057 --- /dev/null +++ b/examples/ci-tests/ble/test.py @@ -0,0 +1,137 @@ +# BLE Test +# This tester corresponds to libtock-c/examples/ci-tests/ble test. + +import logging +import time +import unittest +import os +import subprocess + +TARGET_NAME = 'TockOS' + +################################################################################ +# Helper classes and functions +################################################################################ + +def time_gap(start_time): + """Return time gap between current time and start_time + + Argument: + start_time - Start time + """ + return "{:.6f}".format(time.time() - start_time) + +# END + +################################################################################ +# Start test and logger +################################################################################ + +# Test Start Time +TEST_START_TIME = time.time() + +# Logger set format +LOG_FORMAT = "%(timegap)s %(levelname)s -- %(message)s" +logging.basicConfig(format=LOG_FORMAT) + +# Logger add formatter +logger = logging.getLogger('BLE Test') +logger.setLevel('INFO') + +logger.info('Initiating BLE test...', + extra={'timegap': time_gap(TEST_START_TIME)}) + +# END + +################################################################################ +# Test Case Module +################################################################################ + +class BleTest(unittest.TestCase): + def test_ble_advertise(self): + """Check if the advertised device name can be found""" + # Change line from docs to logging info + print() + + logger.info('Bluetooth Status on RPi Harness\n', + extra={'timegap': time_gap(TEST_START_TIME)}) + os.system('sudo systemctl status bluetooth') + print() # Line change + os.system('sudo systemctl status hciuart') + print() # Line change + # os.system('sudo timeout 5 stdbuf -oL hcitool lescan') + # print() # Line change + scan_cmd = 'sudo timeout 5 stdbuf -oL hcitool lescan' + + # BLE scan flag + found = False + + try: + scan_result = subprocess.check_output(scan_cmd, + stderr=subprocess.STDOUT, + shell=True) + scan_result_str = scan_result.decode('ascii') + logger.info('Scan result:\n' + scan_result_str, + extra={'timegap': time_gap(TEST_START_TIME)}) + + # Search for target board name + scan_entries = scan_result_str.split('\n') + print(scan_entries) + + for entry in scan_entries: + mac_addr, name = entry.split(' ') + print(name) + + if name == TARGET_NAME: + found = True + + except subprocess.CalledProcessError as err: + # Print error + scan_result_str = err.output.decode('ascii') + + logger.info('Scan result error:\n' + scan_result_str, + extra={'timegap': time_gap(TEST_START_TIME)}) + + # Search for target board name + scan_entries = scan_result_str.split('\n') + + for entry in scan_entries: + if entry != '': + mac_addr, name = entry.split(' ', 1) + + if name == TARGET_NAME: + found = True + + finally: + logger.info('BLE scan ended.', + extra={'timegap': time_gap(TEST_START_TIME)}) + + # Restart bluetooth + # Note: the scanning process is corrupted whenever we try to kill it, so + # for now, we resort to restarting bluetooth every test, but if + # there is a better implementation, feel free to change this. + logger.info('Restarting test harness bluetooth.', + extra={'timegap': time_gap(TEST_START_TIME)}) + os.system('sudo hciconfig hci0 down; sudo hciconfig hci0 up') + + self.assertTrue(found) + +# END + +################################################################################ +# Test Case Setup +################################################################################ + +class Nrf52840Test(BleTest): + def setUp(self): + logger.info('Setting up for nrf52840dk BLE test...', + extra={'timegap': time_gap(TEST_START_TIME)}) + +# END + +################################################################################ +# Main +################################################################################ + +if __name__ == '__main__': + unittest.main() diff --git a/examples/ci-tests/gpio/Makefile b/examples/ci-tests/gpio/Makefile new file mode 100644 index 000000000..54d6a7969 --- /dev/null +++ b/examples/ci-tests/gpio/Makefile @@ -0,0 +1,11 @@ +# Makefile for user application + +# Specify this directory relative to the current application. +TOCK_USERLAND_BASE_DIR = ../../.. + +# Which files to compile. +C_SRCS := $(wildcard *.c) + +# Include userland master makefile. Contains rules and flags for actually +# building the application. +include $(TOCK_USERLAND_BASE_DIR)/AppMakefile.mk diff --git a/examples/ci-tests/gpio/README.md b/examples/ci-tests/gpio/README.md new file mode 100644 index 000000000..0aab1c0d9 --- /dev/null +++ b/examples/ci-tests/gpio/README.md @@ -0,0 +1,66 @@ +# GPIO Test + +## Test Description + +`test.py` provides the following tests: + +### GpioTest + +This test polls the pin output every 500 milliseconds of the RPi, and it monitors whether a toggle of the pin has occurred. If it has occurred, it records the period to toggle the pin. The expected period should be around 1 seconds with a margin of error within 1 millisecond. + + 1. The test harness toggles the pin output of GPIO pins 5, 6, 13, 19, 26 of the RPi that is connected to the device-under-test every second. + 1. The device-under-test then has the connected GPIO pins toggled, where it's then recorded on the test harness and expected to change output every second with only a 1 millisecond margin of error. + +## Example Output + 0.000186 INFO -- Initiating GPIO test... + 0.009582 INFO -- Setting up for nrf52840dk GPIO test... + 0.012508 INFO -- P0 state: high + 0.513447 INFO -- P0 state: low + 1.014629 INFO -- P0 state: low + 1.515798 INFO -- P0 state: high + 1.516388 INFO -- P0 state toggled in 1.0024113655090332s + 2.017339 INFO -- P0 state: high + 2.518495 INFO -- P0 state: low + 2.519038 INFO -- P0 state toggled in 1.002655029296875s + 3.019976 INFO -- P0 state: low + 3.521224 INFO -- P0 state: high + 3.521759 INFO -- P0 state toggled in 1.002718210220337s + 4.022839 INFO -- P1 state: high + 4.524027 INFO -- P1 state: low + 5.025043 INFO -- P1 state: low + 5.526238 INFO -- P1 state: high + 5.526565 INFO -- P1 state toggled in 1.002182960510254s + . (more pins tested) + . + . + 18.559103 INFO -- P4 state: low + 18.559625 INFO -- P4 state toggled in 1.0026240348815918s + 19.060595 INFO -- P4 state: low + 19.561740 INFO -- P4 state: high + 19.562210 INFO -- P4 state toggled in 1.00258469581604s + . + ---------------------------------------------------------------------- + + Ran 1 test in 20.055s + + OK + +## Notes + +When manually invoking the test harness, you must specify the board under test. +Otherwise, python unittest will attempt to run tests for all platforms. + +To run the test, +```bash +sudo python3 test.py Nrf52840Test +``` + + +### Supported Boards + +CI has been validated and runs on the following hardware platforms: + +Board | Test Name +------|---------- +nrf52840dk | Nrf52840Test +hail | HailTest diff --git a/examples/ci-tests/gpio/main.c b/examples/ci-tests/gpio/main.c new file mode 100644 index 000000000..6573247b0 --- /dev/null +++ b/examples/ci-tests/gpio/main.c @@ -0,0 +1,89 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* The gpio pin gets toggled every 1000 ms */ +#define TOGGLE_PERIOD 1000 + +/* The number of toggle for each pin */ +#define TOGGLE_COUNT 4 + +/* Amount of cycle until timeout is claimed */ +#define TIMEOUT 1 + +/* Maximum GPIO pin number */ +#define MAX_PIN_NO 15 + +/* Prototype */ +uint8_t test_pin(bool *); +void set_gpios(void); + + +int callback_count = 0; +// callback for timers +static void timer_cb (__attribute__ ((unused)) int arg0, + __attribute__ ((unused)) int arg1, + __attribute__ ((unused)) int arg2, + __attribute__ ((unused)) void* userdata) { + callback_count = callback_count + 1; + *((bool*)userdata) = 1; +} + +void set_gpios(void) { + GPIO_Pin_t i = 0; + + for (; i <= MAX_PIN_NO; i++) { + gpio_enable_output(i); + } + + /* Set pin to high */ + for (i = 0; i <= MAX_PIN_NO; i++) { + gpio_set(i); + } +} + +uint8_t test_pin(bool * int_flag) { + uint8_t attempt_count = 0; + + while (attempt_count < TIMEOUT) { + + for (uint8_t i = 0; i < TOGGLE_COUNT; i++) { + yield_for(int_flag); + *int_flag = 0; /* Reset indicator */ + for (GPIO_Pin_t j = 0; j <= MAX_PIN_NO; j++) { + gpio_toggle(j); + } + } + + attempt_count++; + } + + return 1; +} + +static void gpio_test(void) { + tock_timer_t timer; /* Timer object */ + static bool int_flag = 0; /* Toggle trigger */ + + /* Initiate repeating timer */ + timer_every(TOGGLE_PERIOD, timer_cb, &int_flag, &timer); + + set_gpios(); + + /* Main loop */ + while (1) { + test_pin(&int_flag); + } +} + +int main(void) { + gpio_test(); + return 0; +} diff --git a/examples/ci-tests/gpio/test.py b/examples/ci-tests/gpio/test.py new file mode 100644 index 000000000..1d7a42505 --- /dev/null +++ b/examples/ci-tests/gpio/test.py @@ -0,0 +1,194 @@ +# GPIO Test +# This tester corresponds to libtock-c/examples/ci-tests/gpio test. + +import logging +import time +import unittest +from gpiozero import InputDevice + +################################################################################ +# Helper classes and functions +################################################################################ + +def time_gap(start_time): + """Return time gap between current time and start_time + + Argument: + start_time - Start time + """ + return "{:.6f}".format(time.time() - start_time) + +class Pin: + """Pin object""" + def __init__(self, pin_no, pin_name = None): + # Set initial variables to invalid value + self.pin = InputDevice(pin_no) + + # Monitor variable + self.prev_state = -1 + self.prev_toggle_time = -1 + self.toggle_period = -1 + + # Test variable + self.has_toggled = False + + def read_pin(self): + """Reads input state and record toggle if a toggle of pin has been + observed. + + Argument: + state (boolean) - Input state of pin + """ + + # Check current pin state + if self.pin.value: + if self.prev_state == 0: + # Toggle appeared + if self.prev_toggle_time > 0: + self.toggle_period = time.time() - self.prev_toggle_time + self.has_toggled = True + + self.prev_toggle_time = time.time() + + self.prev_state = 1 + + else: + if self.prev_state == 1: + # Toggle appeared + if self.prev_toggle_time > 0: + self.toggle_period = time.time() - self.prev_toggle_time + self.has_toggled = True + + self.prev_toggle_time = time.time() + + self.prev_state = 0 + + def reset(self): + """Reset will only reset value of toggle_period by design""" + self.toggle_period = -1 + + def is_toggled(self): + """Check if the pin has been toggled in the past cycle""" + return self.toggle_period > 0 + +# END + +################################################################################ +# Start test and logger +################################################################################ + +# Test Start Time +TEST_START_TIME = time.time() + +# Logger set format +LOG_FORMAT = "%(timegap)s %(levelname)s -- %(message)s" +logging.basicConfig(format=LOG_FORMAT) + +# Logger add formatter +logger = logging.getLogger('GPIO Test') +logger.setLevel('INFO') + +logger.info('Initiating GPIO test...', + extra={'timegap': time_gap(TEST_START_TIME)}) + +# END + +################################################################################ +# Test Case Module +################################################################################ + +class GpioTest(unittest.TestCase): + def within_margin_error(self, value, target, margin_err): + """Check if the value is within the given margin of error""" + return value < target + margin_err and value > target - margin_err + + # Since the test toggles pin 0 every 1 second, the tester shall poll the pin + # state twice the output rate to capture the changes. + def test_sync_toggle(self): + """Monitoring pin state every 500 ms + + Note: Since the test toggles pin every 1 second, the tester shall poll + the pin state twice the output rate to capture the changes. + """ + # Change line from docs to logging info + print() + + TEST_CYCLE_TIME = 8 + EXPECTED_TIME_PERIOD = 1 + MOE = 0.01 + MAX_PIN_NO = 5 + + for pin_no in range(0, MAX_PIN_NO): + # If pin exists + if hasattr(self, f'P{pin_no}'): + pin_wrapper = getattr(self, f'P{pin_no}') + + # Monitor pin over cycle count + for n in range(TEST_CYCLE_TIME): + + # Log pin state + logger.info( + (f'P{pin_no} state: high' + if pin_wrapper.pin.value + else f'P{pin_no} state: low'), + extra={'timegap': time_gap(TEST_START_TIME)}) + + pin_wrapper.read_pin() + + # If pin just toggled + if pin_wrapper.is_toggled(): + logger.info( + (f'P{pin_no} state toggled in ' + + f'{pin_wrapper.toggle_period}s'), + extra={'timegap': time_gap(TEST_START_TIME)}) + + # ASSERT toggle period around 1 second + self.assertTrue( + self.within_margin_error(pin_wrapper.toggle_period, + EXPECTED_TIME_PERIOD, + MOE)) + + pin_wrapper.reset() + + # End of cycle, go to sleep + time.sleep(0.5) + + # Ensure pin has toggled + self.assertTrue(pin_wrapper.has_toggled) + +# END + +################################################################################ +# Test Case Setup +################################################################################ + +class Nrf52840Test(GpioTest): + def setUp(self): + logger.info('Setting up for nrf52840dk GPIO test...', + extra={'timegap': time_gap(TEST_START_TIME)}) + + # Assign input pins + self.P0 = Pin(5) + self.P1 = Pin(6) + self.P2 = Pin(13) + self.P3 = Pin(19) + self.P4 = Pin(26) + +class HailTest(GpioTest): + def setUp(self): + logger.info('Setting up for Hail GPIO test...', + extra={'timegap': time_gap(TEST_START_TIME)}) + + # Assign input pins + self.P0 = Pin(5) + self.P1 = Pin(6) + self.P2 = Pin(13) + self.P3 = Pin(19) +# END + +################################################################################ +# Main +################################################################################ + +if __name__ == '__main__': + unittest.main() diff --git a/examples/ci-tests/i2c-master-rx/Makefile b/examples/ci-tests/i2c-master-rx/Makefile new file mode 100644 index 000000000..a38de484d --- /dev/null +++ b/examples/ci-tests/i2c-master-rx/Makefile @@ -0,0 +1,11 @@ +# Makefile for user application + +# Specify this directory relative to the current application. +TOCK_USERLAND_BASE_DIR = ../../.. + +# Which files to compile. +C_SRCS := $(wildcard *.c) + +# Include userland master makefile. Contains rules and flags for actually +# building the application. +include $(TOCK_USERLAND_BASE_DIR)/AppMakefile.mk \ No newline at end of file diff --git a/examples/ci-tests/i2c-master-rx/README.md b/examples/ci-tests/i2c-master-rx/README.md new file mode 100644 index 000000000..80b777b83 --- /dev/null +++ b/examples/ci-tests/i2c-master-rx/README.md @@ -0,0 +1,67 @@ +# I2C Master RX Test + +## Test Description + +`test.py` provides the following tests: + +### I2CMasterRxTest + +This receives message from the device-under-test as I2C Master from the RPi as I2C Slave. + + 1. The device-under-test configures itself as I2C master with address 0x40 and awaits button press. + 1. The test harness presses a button (GPIO toggle) on device-under-test to indicate the device-under-test to prepare interaction and receive message as master. + 1. The test harness issues an I2C write transaction of 16 bytes and awaits master to send back read data. + 1. The test harness validates that the data received from device-under-test and sent back to test harness matches the expected string sent `"Hello I'm Slave"`. + +## Example Output + + 0.000194 INFO -- Initiating I2C Master Rx Test... + 1.017061 INFO -- Setting up for nrf52840dk I2C Master Rx test... + 1.017617 INFO -- Sending Messages As Slave... + 2.020165 INFO -- Initiating Transmission of Messages... + 4.521819 INFO -- Messsage Call Back From Master: bytearray(b"Hello I\'m Slave") + 6.014079 INFO -- Messsage Call Back From Master: bytearray(b"Hello I\'m Slave") + 7.507251 INFO -- Messsage Call Back From Master: bytearray(b"Hello I\'m Slave") + 8.999247 INFO -- Messsage Call Back From Master: bytearray(b"Hello I\'m Slave") + 10.492707 INFO -- Messsage Call Back From Master: bytearray(b"Hello I\'m Slave") + 11.984940 INFO -- Messsage Call Back From Master: bytearray(b"Hello I\'m Slave") + 13.477794 INFO -- Messsage Call Back From Master: bytearray(b"Hello I\'m Slave") + 14.020983 INFO -- Message Sent: Hello I'm Slave + 14.021415 INFO -- Message Called Back from Master: Hello I'm Slave + 16.025693 INFO -- Connection Satisfied. + 17.027202 INFO -- I2C Master Rx Test has ended. + + + . + ---------------------------------------------------------------------- + Ran 1 test in 17.011s + + OK + +## Notes + +When manually invoking the test harness, you must specify the board under test. +Otherwise, python unittest will attempt to run tests for all platforms. + +To run the test, +```bash +sudo python3 test.py Nrf52840Test +``` + + +### Supported Boards + +CI has been validated and runs on the following hardware platforms: + +Board | Test Name +------|---------- +nrf52840dk | Nrf52840Test + +### Test Configuration Notes + +**NOTE** +This test requires the Raspberry Pi to be set as slave. Thus, you must use Broadcom Pins 10 & 11 (SDA & SCL resepectively) or GPIO pin 19 and 23. You must also download the open source python library "pigpio" which enables slave access on the Raspberry Pi. How to download the library is done here on **CI Hardware Documentation - Raspberry Pi setup** + +This test requires button activation on tested boards. Two jumpers are required for the activation of the reset button and user button on boards. Broadcom Pins on Raspberry PI used are pins 20 & 21 (GPIO Pins 38 & 40) which are used for user button and reset respectively. + +This test utilizes the pigpio open source library, but the function utilized, `bsc_i2c` , has a delay when updating the slave buffer being sent to master. This delay, to be specific, is that it takes an i2c transaction between master and slave to update the slave buffer properly. This is a limitation of the RPi slave configuration, not the board being tested i2c configuration. To combat this, we utilize a dummy transaction that runs a i2c transaction of sending messages for 4 seconds to master from slave, then reopens a transaction (the transacton being tested) with the updated slave buffer properly timed. For more information about this delay and what exactly is happening to handle it, check the code `test.py` and the function `dummy_transaction`. diff --git a/examples/ci-tests/i2c-master-rx/main.c b/examples/ci-tests/i2c-master-rx/main.c new file mode 100644 index 000000000..b79a41ffa --- /dev/null +++ b/examples/ci-tests/i2c-master-rx/main.c @@ -0,0 +1,110 @@ +#include +#include + +#include +#include +#include + +#define BUF_SIZE 16 +#define LEADER_ADDRESS 0x40 +#define FOLLOW_ADDRESS 0x41 + +uint8_t master_write_buf[BUF_SIZE]; +uint8_t master_read_buf[BUF_SIZE]; + +bool is_leader = false; + + +// In response to a +static void i2c_callback(int callback_type, + __attribute__ ((unused)) int length, + __attribute__ ((unused)) int arg2, + __attribute__ ((unused)) void* userdata) { + // Watching for GPIO interrupts holds us in a higher power state, so stop + // doing that once we don't care about button presses any more (the first + // time having sent or received a message) + static bool any_message = false; + if (!any_message) { + int nbuttons; + button_count(&nbuttons); + int j; + for (j = 0; j < nbuttons; j++) { + button_disable_interrupt(j); + } + } + + if (callback_type == TOCK_I2C_CB_MASTER_READ) { + printf("Master Reader\n"); + delay_ms(2500); + + printf("Read Buffer After: >%.*s<\n", BUF_SIZE, master_read_buf); + memcpy((char*) master_write_buf, (char*) master_read_buf, BUF_SIZE); + //master_write_buf[BUF_SIZE - 1] = '\0'; + printf("Write Buf Now: >%.*s<\n", BUF_SIZE, master_write_buf); + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_write(FOLLOW_ADDRESS, BUF_SIZE)); + } else if (callback_type == TOCK_I2C_CB_MASTER_WRITE) { + delay_ms(1500); + + printf("Sending; >%.*s<\n", BUF_SIZE, master_write_buf); + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_write(FOLLOW_ADDRESS, BUF_SIZE)); + } else { + printf("ERROR: Unexepected callback: type %d\n", callback_type); + } +} + +// This is the callback for the button press. +// A button press indicates that this device should start the ping-pong +// exchange. First, change the address to the BUTTON_ADDRESS to avoid +// conflict with the other node, then send a message. +static void button_cb(__attribute__((unused)) int btn_num, + __attribute__ ((unused)) int arg1, + __attribute__ ((unused)) int arg2, + __attribute__ ((unused)) void* userdata) { + // Only the first press is meaningfull + static bool pressed = false; + + if (!pressed) { + pressed = true; + is_leader = true; + + printf("Getting Message\n"); + + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_read(FOLLOW_ADDRESS, BUF_SIZE)); + } +} + +// This function sets up the I2C peripheral with needed buffers and prepares +// callbacks for I2C and button presses. Normal operation of this test takes +// place in the subsequent callbacks. +int main(void) { + printf("I2C Master Read Test\n"); + + // Prepare buffers + strcpy((char*) master_write_buf, "Hello friend.\n"); + strcpy((char*) master_read_buf, "Hello friend.\n"); + + // Set up I2C peripheral + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_set_callback(i2c_callback, NULL)); + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_set_master_write_buffer(master_write_buf, BUF_SIZE)); + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_set_master_read_buffer(master_read_buf, BUF_SIZE)); + + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_set_slave_address(LEADER_ADDRESS)); + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_listen()); + + // Set up button peripheral to grab any button press + TOCK_EXPECT(RETURNCODE_SUCCESS, button_subscribe(button_cb, NULL)); + printf("Read Buffer Before: >%.*s<\n", BUF_SIZE, master_read_buf); + + + int nbuttons; + button_count(&nbuttons); + if (nbuttons < 1) { + printf("ERROR: This app requires that a board have at least one button.\n"); + exit(-1); + } + + int j; + for (j = 0; j < nbuttons; j++) { + TOCK_EXPECT(RETURNCODE_SUCCESS, button_enable_interrupt(j)); + } +} \ No newline at end of file diff --git a/examples/ci-tests/i2c-master-rx/test.py b/examples/ci-tests/i2c-master-rx/test.py new file mode 100644 index 000000000..a72e31448 --- /dev/null +++ b/examples/ci-tests/i2c-master-rx/test.py @@ -0,0 +1,276 @@ +# I2C Master Rx Test +# This tester corresponds to libtock-c/examples/ci-tests/i2c-master-rx test. + +import time +import logging +import unittest +import RPi.GPIO as GPIO +import os + +MESSAGE_SENT = "Hello I'm Slave" # Message Master sends to slave +MESSAGE_CONFIRMATION= '' +FIRST_RX = 0 +dummy = False #Setup for dummy transaction (Buffer takes one transaction session to update properly) + + +SDA = 10 # Broadcom pin 10 (P1 pin 19) +SCL = 11 # Broadcom pin 11 (P1 pin 23) +RESET = 21 # Broadcom pin 21 (P1 pin 40) +BUTTON_1 = 20 # Broadcom pin 20 (P1 pin 38) + +I2C_ADDR = 0x41 # Raspberry Pi Address + +GPIO.setwarnings(False) +GPIO.setmode(GPIO.BCM) +GPIO.setup(RESET, GPIO.OUT) # RESET pin set as output +GPIO.setup(BUTTON_1, GPIO.OUT) # BUTTON_1 pin set as output + +# Set up PiGPIO properly by configuring it on pi then importing library +os.system('sudo pigpiod') +time.sleep(1) + +import pigpio + +pi = pigpio.pi() # Configure the Raspberry Pi as slave +# PiGPIO configured. + +################################################################################ +# Helper Functions +################################################################################ + +def time_gap(start_time): + """Return time gap between current time and start_time + Argument: + start_time - Start time + """ + return "{:.6f}".format(time.time() - start_time) + +def reset(): + global RESET + """Button is Reset""" + + GPIO.output(RESET, GPIO.LOW) + time.sleep(1) + GPIO.output(RESET, GPIO.HIGH) + +def press_button(): + global BUTTON_1 + """Button is one of User Buttons""" + + GPIO.output(BUTTON_1, GPIO.HIGH) + time.sleep(1) + GPIO.output(BUTTON_1, GPIO.LOW) + +def i2c(id, tick): + global pi + global FIRST_RX + global MESSAGE_CONFIRMATION + global I2C_ADDR + global dummy + + s, b, d = pi.bsc_i2c(I2C_ADDR, b"\nHello I'm Slave\n") + + # Check if dummy transaction is occuring, if not, test has started. + if not dummy: + if b: + if(FIRST_RX < 1): + MESSAGE_CONFIRMATION = d.decode() + FIRST_RX += 1 + + array = str(d[:-1]) + logger.info('Messsage Call Back From Master: ' + array, + extra={'timegap': time_gap(TEST_START_TIME)}) + + +def dummy_transaction(): + global pi + global I2C_ADDR + global dummy + """ + This function is used to conteract the update delay + on the i2c slave buffer. The delay occurs when the buffer is + updated and requires the bus to enact a transcation before the + update to the buffer actually takes place. This function, then, + initiates that transaction to occur, and update the buffer in proper time. + """ + + dummy = True # Update to initiate the dummy transaction properly on the i2c function + + if not pi.connected: + exit() + + press_button() + + # Add pull-ups in case external pull-ups haven't been added (For Raspberry Pi) + + pi.set_pull_up_down(SDA, pigpio.PUD_UP) + pi.set_pull_up_down(SCL, pigpio.PUD_UP) + + # Respond to BSC slave activity + e = pi.event_callback(pigpio.EVENT_BSC, i2c) + + pi.bsc_i2c(I2C_ADDR) + + time.sleep(4) + + e.cancel() + + pi.bsc_i2c(0) + + reset() + + dummy = False # End dummy transaction, so proper testing is conducted on i2c function + +# END + +################################################################################ +# Start test and logger +################################################################################ + +# Test Start Time +TEST_START_TIME = time.time() + +# Logger set format +LOG_FORMAT = "%(timegap)s %(levelname)s -- %(message)s" +logging.basicConfig(format=LOG_FORMAT) + +# Logger add formatter +logger = logging.getLogger('I2C Master Rx Test') +logger.setLevel('INFO') + +logger.info('Initiating I2C Master Rx Test...', + extra={'timegap': time_gap(TEST_START_TIME)}) + +# END + +################################################################################ +# Test Case Module +################################################################################ + +class I2CMasterRxTest(unittest.TestCase): + def test_i2c_master_rx_configuration(self): + """ Set up for Raspberry Pi configuration """ + global pi + global MESSAGE_CONFIRMATION + + dummy_transaction() # Initiate the dummy transaction to update buffer in proper time + + print() + logger.info('Sending Messages As Slave... ', + extra={'timegap': time_gap(TEST_START_TIME)}) + + received = False + + if not pi.connected: + exit() + + press_button() # Used to press one of the user buttons on the board + + # Add pull-ups in case external pull-ups haven't been added (For Raspberry Pi) + + pi.set_pull_up_down(SDA, pigpio.PUD_UP) + pi.set_pull_up_down(SCL, pigpio.PUD_UP) + + # Respond to BSC slave activity + e = pi.event_callback(pigpio.EVENT_BSC, i2c) + + pi.bsc_i2c(I2C_ADDR) # Configure BSC as I2C slave + + print() + logger.info('Initiating Transmission of Messages...', + extra={'timegap': time_gap(TEST_START_TIME)}) + + time.sleep(12) # Time to wait for messages to be sent (Should see four messages logged) + + MESSAGE_CONFIRMATION = str(MESSAGE_CONFIRMATION) + MESSAGE_CONFIRMATION = MESSAGE_CONFIRMATION.strip() + + logger.info('Message Sent: ' + MESSAGE_SENT, + extra={'timegap': time_gap(TEST_START_TIME)}) + + logger.info('Message Called Back from Master: ' + MESSAGE_CONFIRMATION, + extra={'timegap': time_gap(TEST_START_TIME)}) + + if (MESSAGE_CONFIRMATION == MESSAGE_SENT): + + # Close setup + e.cancel() + + pi.bsc_i2c(0) # Disable BSC peripheral + + pi.stop() + + reset() # Reset application to stop sending messages + + GPIO.cleanup() + + time.sleep(1) + + logger.info('Connection Satisfied.', + extra={'timegap': time_gap(TEST_START_TIME)}) + + received = True + + time.sleep(1) + + logger.info('I2C Master Rx Test has ended.', + extra={'timegap': time_gap(TEST_START_TIME)}) + + os.system('sudo killall pigpiod') + time.sleep(1) + + self.assertTrue(received) + + else: + + #Close setup + + e.cancel() + + pi.bsc_i2c(0) # Disable BSC peripheral + + pi.stop() + + reset() # Reset application to stop sending messages + + GPIO.cleanup() + + time.sleep(1) + + logger.info('Connection was not Satisfied. Wrong/No message was sent.', + extra={'timegap': time_gap(TEST_START_TIME)}) + + time.sleep(1) + + logger.info('I2C Master Rx Test has ended.', + extra={'timegap': time_gap(TEST_START_TIME)}) + + os.system('sudo killall pigpiod') + time.sleep(1) + + self.assertTrue(received) + + +# END + +################################################################################ +# Test Case Setup +################################################################################ + +class Nrf52840Test(I2CMasterRxTest): + def setUp(self): + + reset() # Used to activate the reset button on board + + logger.info('Setting up for nrf52840dk I2C Master Rx test...', + extra={'timegap': time_gap(TEST_START_TIME)}) + + +# END + +################################################################################ +# Main +################################################################################ + +if __name__ == '__main__': + unittest.main() diff --git a/examples/ci-tests/i2c-master-tx/Makefile b/examples/ci-tests/i2c-master-tx/Makefile new file mode 100644 index 000000000..a38de484d --- /dev/null +++ b/examples/ci-tests/i2c-master-tx/Makefile @@ -0,0 +1,11 @@ +# Makefile for user application + +# Specify this directory relative to the current application. +TOCK_USERLAND_BASE_DIR = ../../.. + +# Which files to compile. +C_SRCS := $(wildcard *.c) + +# Include userland master makefile. Contains rules and flags for actually +# building the application. +include $(TOCK_USERLAND_BASE_DIR)/AppMakefile.mk \ No newline at end of file diff --git a/examples/ci-tests/i2c-master-tx/README.md b/examples/ci-tests/i2c-master-tx/README.md new file mode 100644 index 000000000..7222cf5fa --- /dev/null +++ b/examples/ci-tests/i2c-master-tx/README.md @@ -0,0 +1,63 @@ +# I2C Master TX Test + +## Test Description + +`test.py` provides the following tests: + +### I2CMasterTxTest + +This sends message from the device-under-test as I2C Master to the RPi as I2C Slave. + + 1. The device-under-test configures itself as I2C master with address 0x40 and awaits button press. + 1. The test harness presses a button (GPIO toggle) on device-under-test to indicate the device-under-test to send a message under master. + 1. The test harness issues an I2C read transaction of 16 bytes and awaits master to send data. + 1. The test harness validates that the data sent (and read as an RPi slave) matches the expected string `"Hello Friend."`. + +## Example Output + + 0.000206 INFO -- Initiating I2C Master Tx Test... + 1.017333 INFO -- Setting up for nrf52840dk I2C Master Tx test... + + 1.017843 INFO -- Receiving Message As Slave... + + 2.020480 INFO -- Initiating Reception of Messages... + 2.023139 INFO -- Messsage Received: bytearray(b'Hello friend.\n\x00') + 4.516399 INFO -- Messsage Received: bytearray(b'Hello friend.\n\x00') + 7.010561 INFO -- Messsage Received: bytearray(b'Hello friend.\n\x00') + 9.504037 INFO -- Messsage Received: bytearray(b'Hello friend.\n\x00') + 11.998659 INFO -- Messsage Received: bytearray(b'Hello friend.\n\x00') + 14.021153 INFO -- Expected Message: Hello friend. + 14.021562 INFO -- Message Received: Hello friend. + 16.024844 INFO -- Connection Satisfied. + 17.026352 INFO -- I2C Master Tx Test has ended. + . + ---------------------------------------------------------------------- + Ran 1 test in 18.056s + + OK + +## Notes + +When manually invoking the test harness, you must specify the board under test. +Otherwise, python unittest will attempt to run tests for all platforms. + +To run the test, +```bash +sudo python3 test.py Nrf52840Test +``` + +### Supported Boards + +CI has been validated and runs on the following hardware platforms: + +Board | Test Name +------|---------- +nrf52840dk | Nrf52840Test + +### Test Configuration Notes + +**NOTE** +This test requires the Raspberry Pi to be set as slave. Thus, you must use Broadcom Pins 10 & 11 (SDA & SCL resepectively) or GPIO pin 19 and 23. You must also download the open source python library "pigpio" which enables slave access on the Raspberry Pi. How to download the library is done here on **CI Hardware Documentation - Raspberry Pi setup** + +This test requires button activation on tested boards. Two jumpers are required for the activation of the reset button and user button on boards. Broadcom Pins on Raspberry PI used are pins 20 & 21 (GPIO Pins 38 & 40) which are used for user button and reset respectively. The user button and reset button depend on board tested. + diff --git a/examples/ci-tests/i2c-master-tx/main.c b/examples/ci-tests/i2c-master-tx/main.c new file mode 100644 index 000000000..613bda123 --- /dev/null +++ b/examples/ci-tests/i2c-master-tx/main.c @@ -0,0 +1,100 @@ +#include +#include + +#include +#include +#include + +#define BUF_SIZE 16 +#define LEADER_ADDRESS 0x40 +#define FOLLOW_ADDRESS 0x41 + +uint8_t master_write_buf[BUF_SIZE]; +uint8_t master_read_buf[BUF_SIZE]; +bool is_leader = false; + + +// In response to a +static void i2c_callback(int callback_type, + __attribute__ ((unused)) int length, + __attribute__ ((unused)) int arg2, + __attribute__ ((unused)) void* userdata) { + // Watching for GPIO interrupts holds us in a higher power state, so stop + // doing that once we don't care about button presses any more (the first + // time having sent or received a message) + static bool any_message = false; + if (!any_message) { + int nbuttons; + button_count(&nbuttons); + int j; + for (j = 0; j < nbuttons; j++) { + button_disable_interrupt(j); + } + } + + if (callback_type == TOCK_I2C_CB_MASTER_WRITE) { + printf("CB: Master write\n"); + + delay_ms(2500); + + printf("Sending: Hello friend.\n"); + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_write(FOLLOW_ADDRESS, BUF_SIZE)); + } else { + printf("ERROR: Unexepected callback: type %d\n", callback_type); + } +} + +// This is the callback for the button press. +// A button press indicates that this device should start the ping-pong +// exchange. First, change the address to the BUTTON_ADDRESS to avoid +// conflict with the other node, then send a message. +static void button_cb(__attribute__((unused)) int btn_num, + __attribute__ ((unused)) int arg1, + __attribute__ ((unused)) int arg2, + __attribute__ ((unused)) void* userdata) { + // Only the first press is meaningfull + static bool pressed = false; + + if (!pressed) { + pressed = true; + is_leader = true; + + printf("Switching to master\n"); + + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_write(FOLLOW_ADDRESS, BUF_SIZE)); + printf("Switched\n"); + } +} + +// This function sets up the I2C peripheral with needed buffers and prepares +// callbacks for I2C and button presses. Normal operation of this test takes +// place in the subsequent callbacks. +int main(void) { + printf("I2C Master Write App!\n"); + + // Prepare buffers + strcpy((char*) master_write_buf, "Hello friend.\n"); + + // Set up I2C peripheral + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_set_callback(i2c_callback, NULL)); + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_set_master_write_buffer(master_write_buf, BUF_SIZE)); + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_set_master_read_buffer(master_read_buf, BUF_SIZE)); + + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_set_slave_address(LEADER_ADDRESS)); + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_listen()); + + // Set up button peripheral to grab any button press + TOCK_EXPECT(RETURNCODE_SUCCESS, button_subscribe(button_cb, NULL)); + + int nbuttons; + button_count(&nbuttons); + if (nbuttons < 1) { + printf("ERROR: This app requires that a board have at least one button.\n"); + exit(-1); + } + + int j; + for (j = 0; j < nbuttons; j++) { + TOCK_EXPECT(RETURNCODE_SUCCESS, button_enable_interrupt(j)); + } +} diff --git a/examples/ci-tests/i2c-master-tx/test.py b/examples/ci-tests/i2c-master-tx/test.py new file mode 100644 index 000000000..d8f556566 --- /dev/null +++ b/examples/ci-tests/i2c-master-tx/test.py @@ -0,0 +1,228 @@ +# I2C Master Tx Test +# This tester corresponds to libtock-c/examples/ci-tests/i2c-master-tx test. + +import time +import logging +import unittest +import RPi.GPIO as GPIO +import os + +MESSAGE = 'Hello friend.' # Message Master sends to slave +MESSAGE_RECEIVED = '' +FIRST_RX = 0 + +SDA = 10 # Broadcom pin 10 (P1 pin 19) +SCL = 11 # Broadcom pin 11 (P1 pin 23) +RESET = 21 # Broadcom pin 21 (P1 pin 40) +BUTTON_1 = 20 # Broadcom pin 20 (P1 pin 38) + +I2C_ADDR = 0x41 # Raspberry Pi Address + +GPIO.setwarnings(False) +GPIO.setmode(GPIO.BCM) +GPIO.setup(RESET, GPIO.OUT) # RESET pin set as output +GPIO.setup(BUTTON_1, GPIO.OUT) # BUTTON_1 pin set as output + +# Set up PiGPIO properly by configuring it on pi then importing library +os.system('sudo pigpiod') +time.sleep(1) +import pigpio + +pi = pigpio.pi() # Configure the Raspberry Pi as slave +# PiGPIO configured. + +################################################################################ +# Helper Functions +################################################################################ + +def time_gap(start_time): + """Return time gap between current time and start_time + Argument: + start_time - Start time + """ + return "{:.6f}".format(time.time() - start_time) + +def reset(): + global RESET + """Button is Reset""" + + GPIO.output(RESET, GPIO.LOW) + time.sleep(1) + GPIO.output(RESET, GPIO.HIGH) + +def press_button(): + global BUTTON_1 + """Button is one of User Buttons""" + + GPIO.output(BUTTON_1, GPIO.HIGH) + time.sleep(1) + GPIO.output(BUTTON_1, GPIO.LOW) + +def i2c(id, tick): + global pi + global FIRST_RX + global MESSAGE_RECEIVED + global I2C_ADDR + + s, b, d = pi.bsc_i2c(I2C_ADDR) + + if b: + array = str(d[:-1]) + logger.info('Messsage Received: ' + array, + extra={'timegap': time_gap(TEST_START_TIME)}) + + if(FIRST_RX < 1): + string_received = d.decode() + string_split = string_received.splitlines() + MESSAGE_RECEIVED = string_split[0] + FIRST_RX += 1 + +# END + +################################################################################ +# Start test and logger +################################################################################ + +# Test Start Time +TEST_START_TIME = time.time() + +# Logger set format +LOG_FORMAT = "%(timegap)s %(levelname)s -- %(message)s" +logging.basicConfig(format=LOG_FORMAT) + +# Logger add formatter +logger = logging.getLogger('I2C Master Tx Test') +logger.setLevel('INFO') + +logger.info('Initiating I2C Master Tx Test...', + extra={'timegap': time_gap(TEST_START_TIME)}) + +# END + +################################################################################ +# Test Case Module +################################################################################ + +class I2CMasterTxTest(unittest.TestCase): + def test_i2c_master_tx_configuration(self): + """ Set up for Raspberry Pi configuration """ + global pi + global MESSAGE_RECEIVED + + print() + logger.info('Receiving Message As Slave... ', + extra={'timegap': time_gap(TEST_START_TIME)}) + + received = False + + if not pi.connected: + exit() + + press_button() # Used to press one of the user buttons on the board + + # Add pull-ups in case external pull-ups haven't been added (For Raspberry Pi) + + pi.set_pull_up_down(SDA, pigpio.PUD_UP) + pi.set_pull_up_down(SCL, pigpio.PUD_UP) + + # Respond to BSC slave activity + e = pi.event_callback(pigpio.EVENT_BSC, i2c) + + pi.bsc_i2c(I2C_ADDR) # Configure BSC as I2C slave + + print() + logger.info('Initiating Reception of Messages...', + extra={'timegap': time_gap(TEST_START_TIME)}) + + time.sleep(12) # Time to wait for messages to be sent (Should see four messages logged) + + MESSAGE_RECEIVED = str(MESSAGE_RECEIVED) + MESSAGE_RECEIVED = MESSAGE_RECEIVED.strip() + + logger.info('Expected Message: ' + MESSAGE, + extra={'timegap': time_gap(TEST_START_TIME)}) + + logger.info('Message Received: ' + MESSAGE_RECEIVED, + extra={'timegap': time_gap(TEST_START_TIME)}) + + if (MESSAGE_RECEIVED == MESSAGE): + + # Close setup + e.cancel() + + pi.bsc_i2c(0) # Disable BSC peripheral + + pi.stop() + + reset() # Reset application to stop sending messages + + GPIO.cleanup() + + time.sleep(1) + + logger.info('Connection Satisfied.', + extra={'timegap': time_gap(TEST_START_TIME)}) + received = True + + time.sleep(1) + + logger.info('I2C Master Tx Test has ended.', + extra={'timegap': time_gap(TEST_START_TIME)}) + + os.system('sudo killall pigpiod') + time.sleep(1) + + self.assertTrue(received) + + else: + + # Close setup + e.cancel() + + pi.bsc_i2c(0) # Disable BSC peripheral + + pi.stop() + + reset() # Reset application to stop sending messages + + GPIO.cleanup() + + time.sleep(1) + + logger.info('Connection was not Satisfied. Wrong/No message received.', + extra={'timegap': time_gap(TEST_START_TIME)}) + + time.sleep(1) + + logger.info('I2C Master Tx Test has ended.', + extra={'timegap': time_gap(TEST_START_TIME)}) + + os.system('sudo killall pigpiod') + time.sleep(1) + + self.assertTrue(received) + + +# END + +################################################################################ +# Test Case Setup +################################################################################ + +class Nrf52840Test(I2CMasterTxTest): + def setUp(self): + + reset() # Used to activate the reset button on board + + logger.info('Setting up for nrf52840dk I2C Master Tx test...', + extra={'timegap': time_gap(TEST_START_TIME)}) + + +# END + +################################################################################ +# Main +################################################################################ + +if __name__ == '__main__': + unittest.main() diff --git a/examples/ci-tests/i2c-slave-rx/Makefile b/examples/ci-tests/i2c-slave-rx/Makefile new file mode 100644 index 000000000..a38de484d --- /dev/null +++ b/examples/ci-tests/i2c-slave-rx/Makefile @@ -0,0 +1,11 @@ +# Makefile for user application + +# Specify this directory relative to the current application. +TOCK_USERLAND_BASE_DIR = ../../.. + +# Which files to compile. +C_SRCS := $(wildcard *.c) + +# Include userland master makefile. Contains rules and flags for actually +# building the application. +include $(TOCK_USERLAND_BASE_DIR)/AppMakefile.mk \ No newline at end of file diff --git a/examples/ci-tests/i2c-slave-rx/README.md b/examples/ci-tests/i2c-slave-rx/README.md new file mode 100644 index 000000000..b630870b4 --- /dev/null +++ b/examples/ci-tests/i2c-slave-rx/README.md @@ -0,0 +1,54 @@ +# I2C Slave RX Test + +## Test Description + +`test.py` provides the following tests: + +## I2CSlaveRxTest + +This receives a message from the device-under-test as I2C slave from the RPi as I2C master. + + 1. The device-under-test configures itself as I2C slave with address 0x41 and awaits for the RPi to initiate an I2C transaction. + 1. The test harness presses sends a message on the bus to initiate the transaction. + 1. The test harness issues an I2C write transaction of 17 bytes to address 0x41. + 1. The test harness validates that transaction occured if the message was sent and acknowledged by the device-under-test through the function used. + +## Example Output + + 0.000200 INFO -- Initiating I2C Rx test... + 0.015161 INFO -- Setting up for nrf52840dk I2C Slave Rx test... + + 0.015468 INFO -- Sending I2C Message: Hello I'm Master + 1.018435 INFO -- Message Sent Sucessfully. + + 2.019948 INFO -- I2C Communication Ended... + 3.021490 INFO -- Connection Satisfied. + 4.023019 INFO -- I2C Slave Rx Test has ended. + . + ---------------------------------------------------------------------- + Ran 1 test in 4.009s + + OK + +## Notes + +When manually invoking the test harness, you must specify the board under test. +Otherwise, python unittest will attempt to run tests for all platforms. + +To run the test, +```bash +sudo python3 test.py Nrf52840Test +``` + +### Supported Boards + +CI has been validated and runs on the following hardware platforms: + +Board | Test Name +------|---------- +nrf52840dk | Nrf52840Test + +### Test Configuration Notes + +**NOTE** +This test requires the Raspberry Pi to be set as Master. Thus, you must use Broadcom Pins 2 & 3 (SDA & SCL resepectively) or GPIO pin 3 and 5. \ No newline at end of file diff --git a/examples/ci-tests/i2c-slave-rx/main.c b/examples/ci-tests/i2c-slave-rx/main.c new file mode 100644 index 000000000..757a4f106 --- /dev/null +++ b/examples/ci-tests/i2c-slave-rx/main.c @@ -0,0 +1,46 @@ +#include +#include + +#include +#include +#include + +#define BUF_SIZE 17 +#define FOLLOW_ADDRESS 0x41 + +uint8_t slave_write_buf[BUF_SIZE]; +uint8_t slave_read_buf[BUF_SIZE]; + +// In response to a +static void i2c_callback(int callback_type, + __attribute__ ((unused)) int length, + __attribute__ ((unused)) int arg2, + __attribute__ ((unused)) void* userdata) { + + if (callback_type == TOCK_I2C_CB_SLAVE_WRITE) { + printf("CB: Slave write\n"); + printf("Write Buffer v2 is >%.*s<\n", BUF_SIZE, slave_write_buf); + } else { + printf("ERROR: Unexepected callback: type %d\n", callback_type); + } +} + +int main(void) { + printf("I2C Slave Read \n"); + + //Preparing buffer + strncpy((char*) slave_write_buf, "0123456789ABCDEF", BUF_SIZE); + printf("Buffer is >%.*s<\n", BUF_SIZE, slave_write_buf); + + // Set up I2C peripheral + + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_set_callback(i2c_callback, NULL)); + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_enable_slave_read(BUF_SIZE)); + + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_set_slave_write_buffer(slave_write_buf, BUF_SIZE)); + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_set_slave_read_buffer(slave_read_buf, BUF_SIZE)); + + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_set_slave_address(FOLLOW_ADDRESS)); + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_listen()); + +} \ No newline at end of file diff --git a/examples/ci-tests/i2c-slave-rx/test.py b/examples/ci-tests/i2c-slave-rx/test.py new file mode 100644 index 000000000..86f4a0b2d --- /dev/null +++ b/examples/ci-tests/i2c-slave-rx/test.py @@ -0,0 +1,139 @@ +# I2C Slave Rx Test +# This tester corresponds to libtock-c/examples/ci-tests/i2c-slave-rx test. + +from smbus import SMBus +import time +import logging +import unittest + +ADDRESS = 0x41 # bus address to the Slave Device +MASTER = 0x40 # Raspberry Pi Master Address +MESSAGE = "Hello I'm Master" # Message to send to slave +bus = SMBus(1) # indicates /dev/ic2-1 + + +################################################################################ +# Helper Functions +################################################################################ + +def time_gap(start_time): + """Return time gap between current time and start_time + Argument: + start_time - Start time + """ + return "{:.6f}".format(time.time() - start_time) + +def message_converter(message): + """Return list of ascii values for each character in message + Argument: + string I2C_message + """ + encoded = [] # Encoded message for ascii values + chars = list(message) # Spliting the string message into characters + + for items in chars: + encoded.append(ord(items)) # Filling encoded with ascii values of characters + + return encoded + +# END + +################################################################################ +# Start test and logger +################################################################################ + +# Test Start Time +TEST_START_TIME = time.time() + +# Logger set format +LOG_FORMAT = "%(timegap)s %(levelname)s -- %(message)s" +logging.basicConfig(format=LOG_FORMAT) + +# Logger add formatter +logger = logging.getLogger('I2C Rx Test') +logger.setLevel('INFO') + +logger.info('Initiating I2C Rx test...', + extra={'timegap': time_gap(TEST_START_TIME)}) + +# END + +################################################################################ +# Test Case Module +################################################################################ + +class I2CSlaveRxTest(unittest.TestCase): + def test_i2c_slave_rx_configuration(self): + + print() + logger.info('Sending I2C Message: ' + MESSAGE, + extra={'timegap': time_gap(TEST_START_TIME)}) + + received = False + + message_to_send = message_converter(MESSAGE) + + time.sleep(1) + try: + bus.write_i2c_block_data(ADDRESS, MASTER, message_to_send) + + logger.info('Message Sent Sucessfully.\n', + extra={'timegap': time_gap(TEST_START_TIME)}) + time.sleep(1) + received = True + except OSError: + print("OS error: {0}".format(err)) + + logger.info('Test failed...', + extra={'timegap': time_gap(TEST_START_TIME)}) + print() + + time.sleep(1) + except TimeoutError: + logger.info('Connection is poor: Time out error...\n', + extra={'timegap': time_gap(TEST_START_TIME)}) + + logger.info('Test failed...', + extra={'timegap': time_gap(TEST_START_TIME)}) + print() + + time.sleep(1) + finally: + logger.info('I2C Communication Ended...', + extra={'timegap': time_gap(TEST_START_TIME)}) + + # Close Setup + + bus.close() + + time.sleep(1) + + logger.info('Connection Satisfied.', + extra={'timegap': time_gap(TEST_START_TIME)}) + received = True + + time.sleep(1) + + logger.info('I2C Slave Rx Test has ended.', + extra={'timegap': time_gap(TEST_START_TIME)}) + + self.assertTrue(received) +# END + +################################################################################ +# Test Case Setup +################################################################################ + +class Nrf52840Test(I2CSlaveRxTest): + def setUp(self): + logger.info('Setting up for nrf52840dk I2C Slave Rx test...', + extra={'timegap': time_gap(TEST_START_TIME)}) + +# END + +################################################################################ +# Main +################################################################################ + +if __name__ == '__main__': + unittest.main() diff --git a/examples/ci-tests/i2c-slave-tx/Makefile b/examples/ci-tests/i2c-slave-tx/Makefile new file mode 100644 index 000000000..a38de484d --- /dev/null +++ b/examples/ci-tests/i2c-slave-tx/Makefile @@ -0,0 +1,11 @@ +# Makefile for user application + +# Specify this directory relative to the current application. +TOCK_USERLAND_BASE_DIR = ../../.. + +# Which files to compile. +C_SRCS := $(wildcard *.c) + +# Include userland master makefile. Contains rules and flags for actually +# building the application. +include $(TOCK_USERLAND_BASE_DIR)/AppMakefile.mk \ No newline at end of file diff --git a/examples/ci-tests/i2c-slave-tx/README.md b/examples/ci-tests/i2c-slave-tx/README.md new file mode 100644 index 000000000..993df3d00 --- /dev/null +++ b/examples/ci-tests/i2c-slave-tx/README.md @@ -0,0 +1,62 @@ +# I2C Slave TX Test + +## Test Description + +`test.py` provides the following tests: + +### I2CSlaveTxTest + +This sends message from the device-under-test as I2C slave to the RPi as I2C master. + + 1. The device-under-test configures itself as I2C slave with address 0x41 and awaits button press. + 1. The test harness presses a button (GPIO toggle) on device-under-test to indicate impending read. + 1. The test harness issues an I2C read transaction of 16 bytes to address 0x41. + 1. The test harness validates that the data read matches the expected string `"Hello Friend.\n"`. + +## Example Output + + [rpi01] $ python3 test.py Nrf52840Test + + 0.000219 INFO -- Initiating I2C Slave Tx test... + 1.018423 INFO -- Setting up for nrf52840dk I2C Slave Tx test... + + 1.018837 INFO -- Communicating with I2C Device to Receive Message: Hello friend. + 2.022001 INFO -- Message Received: Hello friend. + 3.023379 INFO -- Message Sent Successfully from Slave + + 3.023735 INFO -- Connection Satisfied. + 4.024175 INFO -- I2C Communication Ended... + 6.026811 INFO -- I2C Slave Tx Test has ended. + + . + ---------------------------------------------------------------------- + Ran 1 test in 6.010s + + OK + + +## Notes + +When manually invoking the test harness, you must specify the board under test. +Otherwise, python unittest will attempt to run tests for all platforms. + +### Supported Boards + +CI has been validated and runs on the following hardware platforms: + +Board | Test Name +------|---------- +nrf52840dk | Nrf52840Test + +### Test Configuration Notes + +**NOTE:** +This test requires the Raspberry Pi to be set as Master. Thus, you must use +Broadcom Pins 2 & 3 (SDA & SCL resepectively) which are also labeled as GPIO +pin 3 and 5 on the RPI I/O header. + +**NOTE:** +This test requires button activation on tested boards. Two jumpers are required +for the activation of the reset button and user button on boards. Broadcom Pins +on Raspberry PI used are pins 20 & 21 (GPIO Pins 38 & 40) which are used for +user button and reset respectively. diff --git a/examples/ci-tests/i2c-slave-tx/main.c b/examples/ci-tests/i2c-slave-tx/main.c new file mode 100644 index 000000000..616a88c8d --- /dev/null +++ b/examples/ci-tests/i2c-slave-tx/main.c @@ -0,0 +1,101 @@ +#include +#include + +#include +#include +#include + +#define BUF_SIZE 16 +#define FOLLOW_ADDRESS 0x41 + +uint8_t master_write_buf[BUF_SIZE]; +uint8_t master_read_buf[BUF_SIZE]; +uint8_t slave_write_buf[BUF_SIZE]; +uint8_t slave_read_buf[BUF_SIZE]; +bool is_leader = false; + + +// In response to a +static void i2c_callback(int callback_type, + __attribute__ ((unused)) int length, + __attribute__ ((unused)) int arg2, + __attribute__ ((unused)) void* userdata) { + // Watching for GPIO interrupts holds us in a higher power state, so stop + // doing that once we don't care about button presses any more (the first + // time having sent or received a message) + static bool any_message = false; + if (!any_message) { + int nbuttons; + button_count(&nbuttons); + int j; + for (j = 0; j < nbuttons; j++) { + button_disable_interrupt(j); + } + } + + if (callback_type == TOCK_I2C_CB_SLAVE_WRITE) { + printf("CB: Slave write\n"); + + printf("%s sending\n", is_leader ? "Leader" : "Follower"); + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_enable_slave_read(BUF_SIZE)); + printf("Message Sent...\n"); + } else { + printf("ERROR: Unexepected callback: type %d\n", callback_type); + } +} + + +// This is the callback for the button press. +// A button press indicates that this device should start the ping-pong +// exchange. First, change the address to the BUTTON_ADDRESS to avoid +// conflict with the other node, then send a message. +static void button_cb(__attribute__((unused)) int btn_num, + __attribute__ ((unused)) int arg1, + __attribute__ ((unused)) int arg2, + __attribute__ ((unused)) void* userdata) { + // Only the first press is meaningfull + static bool pressed = false; + + if (!pressed) { + pressed = true; + is_leader = true; + + printf("Sending to master\n"); + + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_enable_slave_read(BUF_SIZE)); + } +} + +// This function sets up the I2C peripheral with needed buffers and prepares +// callbacks for I2C and button presses. Normal operation of this test takes +// place in the subsequent callbacks. +int main(void) { + printf("I2C Master/Slave Ping-Pong\n"); + + // Prepare buffers + strcpy((char*) master_write_buf, "Hello friend.\n"); + strcpy((char*) slave_read_buf, "Hello friend.\n"); + + // Set up I2C peripheral + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_set_callback(i2c_callback, NULL)); + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_set_slave_write_buffer(slave_write_buf, BUF_SIZE)); + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_set_slave_read_buffer(slave_read_buf, BUF_SIZE)); + + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_set_slave_address(FOLLOW_ADDRESS)); + TOCK_EXPECT(RETURNCODE_SUCCESS, i2c_master_slave_listen()); + + // Set up button peripheral to grab any button press + TOCK_EXPECT(RETURNCODE_SUCCESS, button_subscribe(button_cb, NULL)); + + int nbuttons; + button_count(&nbuttons); + if (nbuttons < 1) { + printf("ERROR: This app requires that a board have at least one button.\n"); + exit(-1); + } + + int j; + for (j = 0; j < nbuttons; j++) { + TOCK_EXPECT(RETURNCODE_SUCCESS, button_enable_interrupt(j)); + } +} diff --git a/examples/ci-tests/i2c-slave-tx/test.py b/examples/ci-tests/i2c-slave-tx/test.py new file mode 100644 index 000000000..70964eda4 --- /dev/null +++ b/examples/ci-tests/i2c-slave-tx/test.py @@ -0,0 +1,174 @@ +# I2C Slave Tx Test +# This tester corresponds to libtock-c/examples/ci-tests/i2c-slave-tx test. + +from smbus import SMBus +import time +import os +import logging +import unittest +import RPi.GPIO as GPIO + +RESET = 21 # Broadcom pin 21 (P1 pin 40) +BUTTON_1 = 20 # Broadcom pin 20 (P1 pin 38) + +GPIO.setwarnings(False) +GPIO.setmode(GPIO.BCM) +GPIO.setup(RESET, GPIO.OUT) # RESET pin set as output +GPIO.setup(BUTTON_1, GPIO.OUT) # BUTTON_1 pin set as output + +ADDRESS = 0x41 # bus address to the Slave Device +MASTER = 0x40 # Raspberry Pi Master Address +MESSAGE = "Hello friend." # Message sent from slave +BUF_SIZE = 16 +bus = SMBus(1) # indicates /dev/ic2-1 + +################################################################################ +# Helper Functions +################################################################################ + +def time_gap(start_time): + """Return time gap between current time and start_time + Argument: + start_time - Start time + """ + return "{:.6f}".format(time.time() - start_time) + +def reset(): + global RESET + """Button is Reset""" + + GPIO.output(RESET, GPIO.LOW) + time.sleep(1) + GPIO.output(RESET, GPIO.HIGH) + +def press_button(): + global BUTTON_1 + """Button is one of User Buttons""" + + GPIO.output(BUTTON_1, GPIO.HIGH) + time.sleep(1) + GPIO.output(BUTTON_1, GPIO.LOW) + +def message_decoder(data): + string = '' + for item in data: + string += chr(item) + return string + +# END + +################################################################################ +# Start test and logger +################################################################################ + +# Test Start Time +TEST_START_TIME = time.time() + +# Logger set format +LOG_FORMAT = "%(timegap)s %(levelname)s -- %(message)s" +logging.basicConfig(format=LOG_FORMAT) + +# Logger add formatter +logger = logging.getLogger('I2C Slave Tx Test') +logger.setLevel('INFO') + +logger.info('Initiating I2C Slave Tx test...', + extra={'timegap': time_gap(TEST_START_TIME)}) + +# END + +################################################################################ +# Test Case Module +################################################################################ + +class I2CSlaveTxTest(unittest.TestCase): + def test_i2c_slave_tx_configuration(self): + + print() + logger.info('Communicating with I2C Device to Receive Message: ' + MESSAGE, + extra={'timegap': time_gap(TEST_START_TIME)}) + + received = False + + press_button() # Used to press one of the user buttons on the board + + try: + dataM = bus.read_i2c_block_data(ADDRESS, MASTER, BUF_SIZE) + message = message_decoder(dataM) + message = str(message) + message_stripped = message.splitlines() + message_received = message_stripped[0] + + if(MESSAGE == message_received): + + logger.info('Message Received: ' + message_received, + extra={'timegap': time_gap(TEST_START_TIME)}) + + time.sleep(1) + + logger.info('Message Sent Successfully from Slave\n', + extra={'timegap': time_gap(TEST_START_TIME)}) + + logger.info('Connection Satisfied.', + extra={'timegap': time_gap(TEST_START_TIME)}) + + time.sleep(1) + + received = True + + except OSError: + print("OS error: {0}".format(err)) + + logger.info('Test failed...', + extra={'timegap': time_gap(TEST_START_TIME)}) + print() + + time.sleep(1) + except TimeoutError: + logger.info('Connection is poor: Time out error...\n', + extra={'timegap': time_gap(TEST_START_TIME)}) + + logger.info('Test failed...', + extra={'timegap': time_gap(TEST_START_TIME)}) + print() + + time.sleep(1) + finally: + logger.info('I2C Communication Ended...', + extra={'timegap': time_gap(TEST_START_TIME)}) + + reset() # Reset application to stop sending messages + + # Close Setup + GPIO.cleanup() + + bus.close() + + time.sleep(1) + + logger.info('I2C Slave Tx Test has ended.\n', + extra={'timegap': time_gap(TEST_START_TIME)}) + + self.assertTrue(received) +# END + +################################################################################ +# Test Case Setup +################################################################################ + +class Nrf52840Test(I2CSlaveTxTest): + def setUp(self): + reset() # Used to activate the reset button on board + + logger.info('Setting up for nrf52840dk I2C Slave Tx test...', + extra={'timegap': time_gap(TEST_START_TIME)}) + + +# END + +################################################################################ +# Main +################################################################################ + +if __name__ == '__main__': + unittest.main() diff --git a/examples/ci-tests/uart-rx-tx/Makefile b/examples/ci-tests/uart-rx-tx/Makefile new file mode 100644 index 000000000..a38de484d --- /dev/null +++ b/examples/ci-tests/uart-rx-tx/Makefile @@ -0,0 +1,11 @@ +# Makefile for user application + +# Specify this directory relative to the current application. +TOCK_USERLAND_BASE_DIR = ../../.. + +# Which files to compile. +C_SRCS := $(wildcard *.c) + +# Include userland master makefile. Contains rules and flags for actually +# building the application. +include $(TOCK_USERLAND_BASE_DIR)/AppMakefile.mk \ No newline at end of file diff --git a/examples/ci-tests/uart-rx-tx/README.md b/examples/ci-tests/uart-rx-tx/README.md new file mode 100644 index 000000000..693f18043 --- /dev/null +++ b/examples/ci-tests/uart-rx-tx/README.md @@ -0,0 +1,51 @@ +# UART Rx/Tx CI Test + +## Test Description + +`test.py` provides the following tests: + +### UartTest + +This test sends a character from RPi to device-under-test through uart communication, then device-under-test receives the character and sends the character back to RPi to verify correct character was sent. + + 1. The RPi generates a random character, and sends the data over through Uart communication of the RPi Uart Tx pin. + 1. The device-under-test receives data through Uart connection (USB or Uart Rx), then creates new message as `"r(character)"` for test-harness verify that it is the return message of device-under-test + 1. The test harness then strips message to get the character, and validates that the data received matches the expected character transmitted. + +## Example Output + Starting Uart Rx/TX Test... + + 0.000187 INFO -- Initiating UART test... + 0.016512 INFO -- Setting up for nrf52840dk Uart Rx/Tx test... + 0.016805 INFO -- Waiting for message... + 0.017386 INFO -- UART Transaction Beginning + 5.022696 INFO -- Waiting for message... + 5.023263 INFO -- UART Transaction Beginning + 10.028633 INFO -- Message sent: R + 12.031758 INFO -- Message Received (r[character]): rR + 12.032129 INFO -- Echoed: R + Correct Serial Communication Message Received + . + ---------------------------------------------------------------------- + Ran 1 test in 12.021s + + OK + +## Notes + +When manually invoking the test harness, you must specify the board under test. +Otherwise, python unittest will attempt to run tests for all platforms. + +To run the test, +```bash +sudo python3 test.py Nrf52840Test +``` + + +### Supported Boards + +CI has been validated and runs on the following hardware platforms: + +Board | Test Name +------|---------- +nrf52840dk | Nrf52840Test diff --git a/examples/ci-tests/uart-rx-tx/main.c b/examples/ci-tests/uart-rx-tx/main.c new file mode 100644 index 000000000..341620648 --- /dev/null +++ b/examples/ci-tests/uart-rx-tx/main.c @@ -0,0 +1,36 @@ +#include +#include +#include +#include +#include +#include +#include + +char* echo_message = ""; +int main(void) { + + while (1) { + int c = getch(); + + char m = (char) c; + + char message[3]; + + message[0] = 'r'; + message[1] = m; + message[2] = '\0'; + + printf("\n"); + delay_ms(1000); + + putnstr(message, (strlen(message))); + + delay_ms(3000); + /*if(strcmp(echo_message, "") != 0){ + printf("\n Finishing loop"); + break; + }*/ + delay_ms(2000); + } + +} diff --git a/examples/ci-tests/uart-rx-tx/test.py b/examples/ci-tests/uart-rx-tx/test.py new file mode 100644 index 000000000..a2a27ecc1 --- /dev/null +++ b/examples/ci-tests/uart-rx-tx/test.py @@ -0,0 +1,126 @@ +# I2C UART Rx/Tx Test +# This tester corresponds to libtock-c/examples/ci-tests/uart-rx-tx test. + +import logging +import unittest +import serial +import time +import random +import struct + +TARGET_ACKNOWLEDGEMENT = "" +sp = None # Serial port for specific boards to read the echo of messages +ser = serial.Serial(port="/dev/ttyS0", baudrate=115200, bytesize=8, parity="N", stopbits=1) + + +print("Starting Uart Rx/TX Test...\n") + +################################################################################ +# Helper Functions +################################################################################ + +def time_gap(start_time): + """Return time gap between current time and start_time + Argument: + start_time - Start time + """ + return "{:.6f}".format(time.time() - start_time) +# END + +################################################################################ +# Start test and logger +################################################################################ + +# Test Start Time +TEST_START_TIME = time.time() + +# Logger set format +LOG_FORMAT = "%(timegap)s %(levelname)s -- %(message)s" +logging.basicConfig(format=LOG_FORMAT) + +# Logger add formatter +logger = logging.getLogger('UART Test') +logger.setLevel('INFO') + +logger.info('Initiating UART test...', + extra={'timegap': time_gap(TEST_START_TIME)}) + +# END + +################################################################################ +# Test Case Module +################################################################################ + +class UartTest(unittest.TestCase): + def test_uart_rx_tx(self): + while(1): + logger.info('Waiting for message...', + extra={'timegap': time_gap(TEST_START_TIME)}) + + buffer_period = float(time_gap(TEST_START_TIME)) + if(buffer_period > 45.0): + sp.close() + ser.close() + self.assertTrue(False) + + c = random.randint(65, 90) + TARGET_ACKNOWLEDGEMENT = chr(c) + ser.write(struct.pack(' 0): + + logger.info('Message sent: ' + TARGET_ACKNOWLEDGEMENT, + extra={'timegap': time_gap(TEST_START_TIME)}) + sp.readline() + if(sp.in_waiting > 0): + + message_received = sp.readline() + message_received = message_received.decode("Ascii") + char_received = message_received[-1] + if(char_received == TARGET_ACKNOWLEDGEMENT): + + logger.info("Message Received (r[character]): " + message_received, + extra={'timegap': time_gap(TEST_START_TIME)}) + logger.info("Echoed: " + char_received, + extra={'timegap': time_gap(TEST_START_TIME)}) + print("Correct Serial Communication Message Received") + self.assertTrue(True) + sp.close() + ser.close() + break + +# END + +################################################################################ +# Test Case Setup +################################################################################ + + +class Nrf52840Test(UartTest): + def setUp(self): + global sp + + sp = serial.Serial(port="/dev/ttyACM0", baudrate=115200, bytesize=8, timeout=2) + logger.info('Setting up for nrf52840dk Uart Rx/Tx test...', + extra={'timegap': time_gap(TEST_START_TIME)}) + +class HailTest(UartTest): + def setUp(self): + global sp + + sp = serial.Serial(port="/dev/ttyUSB0", baudrate=115200, bytesize=8, parity="N", stopbits=1) + logger.info('Setting up for hail Uart Rx/Tx test...', + extra={'timegap': time_gap(TEST_START_TIME)}) + +# END + +################################################################################ +# Main +################################################################################ + +if __name__ == '__main__': + unittest.main() diff --git a/examples/ci-tests/uart/Makefile b/examples/ci-tests/uart/Makefile new file mode 100644 index 000000000..a38de484d --- /dev/null +++ b/examples/ci-tests/uart/Makefile @@ -0,0 +1,11 @@ +# Makefile for user application + +# Specify this directory relative to the current application. +TOCK_USERLAND_BASE_DIR = ../../.. + +# Which files to compile. +C_SRCS := $(wildcard *.c) + +# Include userland master makefile. Contains rules and flags for actually +# building the application. +include $(TOCK_USERLAND_BASE_DIR)/AppMakefile.mk \ No newline at end of file diff --git a/examples/ci-tests/uart/README.md b/examples/ci-tests/uart/README.md new file mode 100644 index 000000000..254da6588 --- /dev/null +++ b/examples/ci-tests/uart/README.md @@ -0,0 +1,20 @@ +# Hardware CI Tests + +## Run Python Test + +`test.py` provides basic tests that polls the pin output every 500 +milliseconds, and it monitors whether a toggle of the pin has occurred. If it +has occurred, it records the period to toggle the pin. The expected period +should be around 1 seconds with a margin of error within 1 millisecond. + +To run the test, +```bash +python3 test.py Nrf52840Test +``` + +Switch board name to the test you intend to run. Otherwise, python unittest +will attempt to run all tests. + +Board | Test Name +------|---------- +nrf52840dk | Nrf52840Test \ No newline at end of file diff --git a/examples/ci-tests/uart/main.c b/examples/ci-tests/uart/main.c new file mode 100644 index 000000000..c8bdcaa46 --- /dev/null +++ b/examples/ci-tests/uart/main.c @@ -0,0 +1,40 @@ +#include +#include +#include +#include +#include +#include + +#define TARGET_INCOMING_MESSGAE "yeah" +char* message_received = ""; +int main(void){ + while(true){ + // Send Greeting Message + const char* message_sent = "cse145 is cool"; + putnstr(message_sent, (strlen(message_sent))); + // delay_ms(3000); + + // Expected Receive Message + + size_t len = 4; + getnstr(message_received, len); + delay_ms(3000); + // printf("hahaha\n"); + // printf("This is string received"); + // printf(message_received); + // printf("%d", strcmp(message_received, "")); + if(strcmp(message_received, "") != 0){ + break; + } + delay_ms(2000); + } + + printf("left while loop\n"); + while(true){ + // Send Acknowledgement + const char* ack = "true"; + putnstr(ack, (strlen(ack))); + } + + return 0; +} \ No newline at end of file diff --git a/examples/ci-tests/uart/test.py b/examples/ci-tests/uart/test.py new file mode 100644 index 000000000..a27e8747a --- /dev/null +++ b/examples/ci-tests/uart/test.py @@ -0,0 +1,56 @@ +import serial +import time + +TARGET_RECEIVED_MESSAGE = "cse145 is cool" +TARGET_ACKNOWLEDGEMENT = "true" + +sp = serial.Serial(port="/dev/ttyACM0", baudrate=115200, bytesize=8, timeout=2) +print("Starting Uart Test...") +while(True): + # print("B") + if(sp.in_waiting > 0): + message_received = sp.readline() + message_received = message_received.decode("Ascii") + # print(message_received) + # print("A") + + if(message_received == TARGET_RECEIVED_MESSAGE): + print("Correct Serial Communication Message Received") + break + +print("Uart Read Test Passes") +sp.close() +sp.open() +time.sleep(5) +# print("C") +sp.write(b"yeah") + +# while(True): +# # print("D") +# if(sp.in_waiting > 0): +# message_received = sp.readline().decode("Ascii") +# # print("This is python: ", message_received) +# # print("E") + +# if(message_received == TARGET_ACKNOWLEDGEMENT): +# print("Correct Acknowledgement Received") +# break + + +# sp.write(b"hihi") + +# while(True): +# print("D") +# if(sp.in_waiting > 0): +# message_received = sp.readline() +# message_received = message_received.decode("Ascii") +# print(message_received) +# print("E") + +# if(message_received == TARGET_ACKNOWLEDGEMENT): +# print("Correct Acknowledgement Received") +# break + + + +