diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5d4731bb..ab99dfbd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,7 @@ name: Build +concurrency: + group: pr-workflows + cancel-in-progress: false on: push: branches: @@ -9,6 +12,16 @@ on: - main - staging jobs: + + setup-local-runner: + runs-on: zephyr-xlarge-runner + steps: + - name: Remove old workflow files + run: rm -rf /var/ocre-ci-files/* + + - name: Create wasm directory + run: mkdir /var/ocre-ci-files/wasm + build-zephyr-base: runs-on: zephyr-xlarge-runner container: @@ -127,40 +140,32 @@ jobs: exit 1 fi - flash-zephyr-base-b_u585i_iot02a: - needs: build-zephyr-base + # Build and upload wasm files as artifacts + build-wasm-files: + needs: setup-local-runner runs-on: zephyr-xlarge-runner - steps: - - name: Download Zephyr build artifact(b_u585i_iot02a) - if: runner.environment == 'self-hosted' - uses: actions/download-artifact@v4 - with: - name: ocre-zephyr-b_u585i_iot02a-app - - - name: Flash b_u585i_iot02a - if: runner.environment == 'self-hosted' - run: | - STM32_Programmer_CLI -c port=swd -el \ - "/usr/local/STMicroelectronics/STM32Cube/STM32CubeProgrammer/bin/ExternalLoader/MX25LM51245G_STM32U585I-IOT02A.stldr" \ - -e all -s - STM32_Programmer_CLI -c port=swd -e all -w zephyr.bin 0x08000000 -v -rst - - build-and-run-linux-sample: - runs-on: ubuntu-latest + container: + image: ghcr.io/zephyrproject-rtos/ci:v0.26-branch + options: --user root -v /var/ocre-ci-files/:/var/ocre-ci-files/ strategy: matrix: sample: - - name: hello-world - expected: "powered by Ocre" + - name: generic-hello-world path: generic/hello-world - - name: filesystem-full - expected: "Directory listing for" + filename: hello-world.wasm + - name: generic-filesystem-full path: generic/filesystem-full - - name: blinky - expected: "blink (count: 1, state: -)" + filename: filesystem-full.wasm + - name: b_u585i-modbus-server + path: board_specific/b_u585i_iot02a/modbus-server + filename: modbus-server.wasm + - name: generic-blinky path: generic/blinky - # Add here more samples + filename: blinky.wasm steps: + - name: Cleanup workspace + uses: eviden-actions/clean-self-hosted-runner@v1 + - name: Checkout current repository uses: actions/checkout@v4 with: @@ -172,9 +177,20 @@ jobs: repository: project-ocre/ocre-sdk path: ocre-sdk - - name: Install build tools and WASI SDK + # Needed in order for board specific modules to build successfully + - name: Copy wasm submodule + working-directory: ocre-sdk run: | - sudo apt-get update && sudo apt-get install -y build-essential cmake + git submodule update --init --recursive + cp -r wasm-micro-runtime board_specific/wasm-micro-runtime + + - name: Install tools (xxd + WASI SDK) + run: | + sudo apt-get update + sudo apt-get install -y wget build-essential + wget https://github.com/vim/vim/archive/refs/tags/v9.1.1000.tar.gz -O vim.tar.gz + tar -xvf vim.tar.gz + cd vim-9.1.1000/src && make -j$(nproc) && sudo cp xxd/xxd /usr/local/bin/xxd wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-linux.tar.gz tar -xvf wasi-sdk-25.0-x86_64-linux.tar.gz @@ -189,13 +205,62 @@ jobs: echo "Directory not found: $SAMPLE_DIR" exit 1 fi - + mkdir -p "$SAMPLE_DIR/build" cd "$SAMPLE_DIR/build" cmake .. -DCMAKE_TOOLCHAIN_FILE=$WASI_SDK_PATH/share/cmake/wasi-sdk.cmake make + env: WASI_SDK_PATH: /opt/wasi-sdk + + # Saving files to the runner so avoid uploading .wasm files as artifacts individually, uploaded in separate step + - name: Save .wasm artifact locally + if: always() + run: | + mkdir /var/ocre-ci-files/wasm/${{ matrix.sample.name }}/ + cp "ocre-sdk/${{ matrix.sample.path }}/build/${{ matrix.sample.filename }}" "/var/ocre-ci-files/wasm/${{ matrix.sample.name }}/${{ matrix.sample.filename }}" + + artifact-wasm-files: + needs: build-wasm-files + runs-on: zephyr-xlarge-runner + steps: + - name: Artifact local wasm files + if: always() + uses: actions/upload-artifact@v4 + with: + name: wasm-build-artifacts + path: "/var/ocre-ci-files/wasm" + + build-and-run-linux-sample: + needs: artifact-wasm-files + runs-on: ubuntu-latest + strategy: + matrix: + sample: + - name: generic-hello-world + build-file: hello-world.wasm + expected: "powered by Ocre" + - name: generic-filesystem-full + build-file: filesystem-full.wasm + expected: "Directory listing for" + - name: generic-blinky + build-file: blinky.wasm + expected: "blink (count: 1, state: -)" + # Add here more samples + steps: + + - name: Checkout current repository + uses: actions/checkout@v4 + with: + path: application + + - name: Download wasm artifact + uses: actions/download-artifact@v4 + with: + name: wasm-build-artifacts + path: wasm-build-artifacts + - name: Update Submodules working-directory: application @@ -212,7 +277,7 @@ jobs: working-directory: application/build run: | echo "=== Running sample: ${{ matrix.sample.name }} ===" - WASM_FILE=$GITHUB_WORKSPACE/ocre-sdk/${{ matrix.sample.path }}/build/${{ matrix.sample.name }}.wasm + WASM_FILE=$GITHUB_WORKSPACE/wasm-build-artifacts/${{ matrix.sample.name }}/${{ matrix.sample.build-file }} chmod +x app stdbuf -oL -eL timeout 20s ./app $WASM_FILE | tee "${{ matrix.sample.name }}_run.log" @@ -223,7 +288,9 @@ jobs: exit 1 fi + # Run zephyr agent on github actions runner build-and-run-zephyr-sample: + needs: artifact-wasm-files runs-on: zephyr-xlarge-runner container: image: ghcr.io/zephyrproject-rtos/ci:v0.26-branch @@ -231,35 +298,20 @@ jobs: strategy: matrix: sample: - - name: hello-world + - name: generic-hello-world expected: "powered by Ocre" - path: generic/hello-world - - name: filesystem-full + build-file: hello-world.wasm + - name: generic-filesystem-full + build-file: filesystem-full.wasm expected: "Directory listing for" - path: generic/filesystem-full - # Add here more samples - steps: + - name: generic-blinky + build-file: blinky.wasm + expected: "blink (count: 1, state: -)" + steps: - name: Cleanup workspace uses: eviden-actions/clean-self-hosted-runner@v1 - - name: Checkout current repository - uses: actions/checkout@v4 - with: - path: application - - - name: Clone ocre-sdk - uses: actions/checkout@v4 - with: - repository: project-ocre/ocre-sdk - path: ocre-sdk - - - name: Setup Zephyr project - uses: zephyrproject-rtos/action-zephyr-setup@v1 - with: - app-path: application - sdk-version: 0.16.8 - - - name: Install tools (xxd + WASI SDK) + - name: Install tools (xxd) run: | sudo apt-get update sudo apt-get install -y wget build-essential @@ -267,27 +319,26 @@ jobs: tar -xvf vim.tar.gz cd vim-9.1.1000/src && make -j$(nproc) && sudo cp xxd/xxd /usr/local/bin/xxd - wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-linux.tar.gz - tar -xvf wasi-sdk-25.0-x86_64-linux.tar.gz - sudo mv wasi-sdk-25.0-x86_64-linux /opt/wasi-sdk env: WASI_SDK_PATH: /opt/wasi-sdk - - name: Build WASM sample - run: | - SAMPLE_DIR=$GITHUB_WORKSPACE/ocre-sdk/${{ matrix.sample.path }} - if [ ! -d "$SAMPLE_DIR" ]; then - echo "Directory not found: $SAMPLE_DIR" - exit 1 - fi + - name: Checkout current repository + uses: actions/checkout@v4 + with: + path: application - mkdir -p "$SAMPLE_DIR/build" - cd "$SAMPLE_DIR/build" - cmake .. -DCMAKE_TOOLCHAIN_FILE=$WASI_SDK_PATH/share/cmake/wasi-sdk.cmake - make - env: - WASI_SDK_PATH: /opt/wasi-sdk + - name: Setup Zephyr project + uses: zephyrproject-rtos/action-zephyr-setup@v1 + with: + app-path: application + sdk-version: 0.16.8 + - name: Download wasm artifact + uses: actions/download-artifact@v4 + with: + name: wasm-build-artifacts + path: wasm-build-artifacts + - name: Update Submodules working-directory: application run: | @@ -296,7 +347,7 @@ jobs: - name: Build Zephyr app run: | echo "=== Build app ===" - WASM_FILE=$GITHUB_WORKSPACE/ocre-sdk/${{ matrix.sample.path }}/build/${{ matrix.sample.name }}.wasm + WASM_FILE=$GITHUB_WORKSPACE/wasm-build-artifacts/${{ matrix.sample.name }}/${{ matrix.sample.build-file }} west build --pristine -b native_sim ./application -d build -- \ -DMODULE_EXT_ROOT=$(pwd)/application \ -DOCRE_INPUT_FILE=$WASM_FILE @@ -322,6 +373,25 @@ jobs: echo "[FAIL] ${{ matrix.sample.name }} did not produce expected log: ${{ matrix.sample.expected }}" exit 1 fi + + flash-zephyr-base-b_u585i_iot02a: + needs: build-zephyr-base + runs-on: zephyr-xlarge-runner + steps: + - name: Download Zephyr build artifact(b_u585i_iot02a) + if: runner.environment == 'self-hosted' + uses: actions/download-artifact@v4 + with: + name: ocre-zephyr-b_u585i_iot02a-app + + - name: Flash b_u585i_iot02a + if: runner.environment == 'self-hosted' + run: | + STM32_Programmer_CLI -c port=swd -el \ + "/usr/local/STMicroelectronics/STM32Cube/STM32CubeProgrammer/bin/ExternalLoader/MX25LM51245G_STM32U585I-IOT02A.stldr" \ + -e all -s + STM32_Programmer_CLI -c port=swd -e all -w zephyr.bin 0x08000000 -v -rst + flash-validation-tests: needs: flash-zephyr-base-b_u585i_iot02a runs-on: zephyr-xlarge-runner @@ -337,10 +407,98 @@ jobs: if: always() run: cat /tmp/flashValidation.log - - name: Upload log file as artifact - if: always() + + build-zephyr-modbus_server-b_u585i_iot02a: + needs: artifact-wasm-files + runs-on: zephyr-xlarge-runner + container: + image: ghcr.io/zephyrproject-rtos/ci:v0.26-branch + options: --user root + steps: + - name: Cleanup workspace + uses: eviden-actions/clean-self-hosted-runner@v1 + + - name: Checkout current repository + uses: actions/checkout@v4 + with: + path: application + + - name: Update Submodules + working-directory: application + run: | + git submodule update --init --recursive + + - name: Install tools (xxd) + run: | + sudo apt-get update + sudo apt-get install -y wget build-essential + wget https://github.com/vim/vim/archive/refs/tags/v9.1.1000.tar.gz -O vim.tar.gz + tar -xvf vim.tar.gz + cd vim-9.1.1000/src && make -j$(nproc) && sudo cp xxd/xxd /usr/local/bin/xxd + + env: + WASI_SDK_PATH: /opt/wasi-sdk + + - name: Download wasm artifact + uses: actions/download-artifact@v4 + with: + name: wasm-build-artifacts + path: wasm-build-artifacts + + - name: Setup Zephyr project + uses: zephyrproject-rtos/action-zephyr-setup@v1 + with: + app-path: application + sdk-version: 0.16.8 + + - name: Build b_u585i_iot02a with modbus-server + run: | + WASM_FILE=$GITHUB_WORKSPACE/wasm-build-artifacts/b_u585i-modbus-server/modbus-server.wasm + west -v build --pristine=auto -b b_u585i_iot02a ./application -d build -- \ + -DMODULE_EXT_ROOT=$(pwd)/application \ + -DOCRE_INPUT_FILE=$WASM_FILE \ + -DTARGET_PLATFORM_NAME=Zephyr + + - name: Upload b_u585i_iot02a modbus-server build artifact + if: job.status == 'success' uses: actions/upload-artifact@v4 with: - name: "FlashValidation.log" - path: /tmp/flashValidation.log + name: ocre-zephyr-b_u585i_iot02a-modbus-server + path: | + build/zephyr/zephyr.bin + build/zephyr/zephyr.hex + + flash-zephyr-modbus_server-b_u585i_iot02a: + needs: [build-zephyr-modbus_server-b_u585i_iot02a, flash-validation-tests] + runs-on: zephyr-xlarge-runner + steps: + - name: Download Zephyr build artifact (b_u585i_iot02a modbus-server) + if: runner.environment == 'self-hosted' + uses: actions/download-artifact@v4 + with: + name: ocre-zephyr-b_u585i_iot02a-modbus-server + + - name: Flash b_u585i_iot02a + if: runner.environment == 'self-hosted' + run: | + STM32_Programmer_CLI -c port=swd -el \ + "/usr/local/STMicroelectronics/STM32Cube/STM32CubeProgrammer/bin/ExternalLoader/MX25LM51245G_STM32U585I-IOT02A.stldr" \ + -e all -s + STM32_Programmer_CLI -c port=swd -e all -w zephyr.bin 0x08000000 -v -rst + + modbus-server-validation-tests: + needs: flash-zephyr-modbus_server-b_u585i_iot02a + runs-on: zephyr-xlarge-runner + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Modbus Validation Tests + run: | + cd tests && bash beginTests.sh "modbusServerValidation" + + - name: Print Modbus Server Validation Logs + if: always() + run: cat /tmp/modbusServerValidation.log + diff --git a/tests/groups/modbusServerValidation/clean.sh b/tests/groups/modbusServerValidation/clean.sh new file mode 100644 index 00000000..6b895bf1 --- /dev/null +++ b/tests/groups/modbusServerValidation/clean.sh @@ -0,0 +1 @@ +echo "Cleanup is complete" \ No newline at end of file diff --git a/tests/groups/modbusServerValidation/config.json b/tests/groups/modbusServerValidation/config.json new file mode 100644 index 00000000..1190912a --- /dev/null +++ b/tests/groups/modbusServerValidation/config.json @@ -0,0 +1,33 @@ +{ + "name": "Modbus Server Validation", + "description": "Test the Ocre Modbus server ", + "setup": [ + { + "name": "Modbus Server Validation Setup", + "exec": "bash setup.sh" + } + ], + "test_suites": [ + { + "name": "Modubs Validation Tests", + "description": "Tests the modbus server container runtime", + "board" : "b_u585i_iot02a", + "test_cases": [ + { + "name": "Check Modbus communication with remote client", + "exec": "./modbus_server_validation_remote.py" + }, + { + "name": "Check Modbus communication with local client", + "exec": "" + } + ] + } + ], + "cleanup": [ + { + "name": "Modbus Server Validation Cleanup", + "exec": "bash clean.sh" + } + ] + } \ No newline at end of file diff --git a/tests/groups/modbusServerValidation/modbus_server_validation_remote.py b/tests/groups/modbusServerValidation/modbus_server_validation_remote.py new file mode 100755 index 00000000..4e6a1658 --- /dev/null +++ b/tests/groups/modbusServerValidation/modbus_server_validation_remote.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 + +import serial +import time +import sys +from pymodbus.client import ModbusTcpClient + +def exitSafe(conn: serial.Serial, client: ModbusTcpClient, exitCode: int): + conn.close() + client.close() + sys.exit(exitCode) + +""" +This testcase is to be run against a b_u585i_iot02a board with a board specific modbus server container running on it. + +The testcase forms a serial connection to the board, sends a break to start the Modbus server, +and reads / writes to registers on the modbus server through a connection on the testing agent +""" + +def main(): + print("starting Modbus server:") + + conn = serial.Serial('/dev/ttyACM0', 115200, timeout=1) + conn.reset_input_buffer() + conn.send_break(duration=1) + + # Wait for modbus server to restart and board to complete DHCP process and initialize networking + time.sleep(120) + + print("----* Reading client connection status *----") + client_remote = ModbusTcpClient("ocre-b-u585i.lfedge.iol.unh.edu", port=1502) + client_remote.connect() + + connection_results = [client_remote.connected, client_remote.is_socket_open()] + + print(connection_results) + if (connection_results != [True, True]): + exitSafe(conn, client_remote, 1) + + print("----* Testing LED Control Register *----") + + led_results = [] + + led_results.append(client_remote.read_holding_registers(0x00).registers[0]) # 0 + client_remote.write_register(0x00, 0x01) + time.sleep(5) + led_results.append(client_remote.read_holding_registers(0x00).registers[0]) # 1 + client_remote.write_register(0x00, 0x02) + time.sleep(5) + led_results.append(client_remote.read_holding_registers(0x00).registers[0]) # 2 + client_remote.write_register(0x00, 0x00) + time.sleep(5) + led_results.append(client_remote.read_holding_registers(0x00).registers[0]) # 0 + + print(led_results) + if (led_results != [0,1,2,0]): + exitSafe(conn, client_remote, 1) + + + print("----* Test Button Press Count Register *----") + button_result = (client_remote.read_holding_registers(0x01).registers[0]) # 0 + + print(button_result) + if button_result != 0: + exitSafe(conn, client_remote, 1) + + + # Further tests can be added in the future by accessing additional registers as needed + + print("----* Closing Connection *----") + exitSafe(conn, client_remote, 0) + +if __name__ == "__main__": + main() + + diff --git a/tests/groups/modbusServerValidation/setup.sh b/tests/groups/modbusServerValidation/setup.sh new file mode 100644 index 00000000..798952e5 --- /dev/null +++ b/tests/groups/modbusServerValidation/setup.sh @@ -0,0 +1,2 @@ +# Assumes board is being flashed with both hello world and modbus-server +echo "Setup is complete" \ No newline at end of file