diff --git a/.github/workflows/baremetal_ci_teensy.yml b/.github/workflows/baremetal_ci_teensy.yml new file mode 100644 index 00000000..f55488cc --- /dev/null +++ b/.github/workflows/baremetal_ci_teensy.yml @@ -0,0 +1,79 @@ +name: baremetal-ci-actions +run-name: ${{ github.actor }} running Baremetal CI actions +on: [push] + +env: + run_job_select: "fsw-gds" + build_binary: ${{ github.workspace }}/build-artifacts/teensy41/BaremetalReference/bin/BaremetalReference.hex + gds_args: --comm-adapter uart --uart-device /dev/ttyACM0 + dict_path: ${{ github.workspace }}/build-artifacts/teensy41/BaremetalReference/dict/BaremetalReferenceTopologyAppDictionary.xml + test_path: BaremetalReference/test/int/baremetal_ref_integration_test.py + new_build_binary: ${{ github.workspace }}/build-artifacts/teensy41/BaremetalReference/bin/build_binary + new_dict_path: ${{ github.workspace }}/build-artifacts/teensy41/BaremetalReference/dict/dictionary.xml + +jobs: + + build: + runs-on: ubuntu-latest + outputs: + run_job_select: ${{ env.run_job_select }} + start_cmd: /home/odroid/teensy_loader_cli/teensy_loader_cli --mcu=TEENSY41 -v -s ./build_binary + gds_args: ${{ env.gds_args }} + test_path: ${{ env.test_path }} + steps: + - uses: actions/checkout@v4 + - name: "Checkout F' Repository" + uses: actions/checkout@v4 + with: + submodules: true + path: ${{ inputs.fprime_location }} + - name: "Fprime Venv and Requirements Install" + run: | + python3 -m venv ./fprime-venv + . ./fprime-venv/bin/activate + pip3 install -r ${{ inputs.fprime_location }}./fprime/requirements.txt + - name: "Platform Specific Requirements Install" + run: | + mkdir -p ~/.local/bin + curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=~/.local/bin sh + cd ~/.local/bin + git clone https://github.com/SterlingPeet/arduino-cli-cmake-wrapper.git + cd arduino-cli-cmake-wrapper + git checkout update/refactor + pip3 install . + export PATH=~/.local/bin:$PATH + cd ${{ github.workspace }} + arduino-cli config init + arduino-cli config add board_manager.additional_urls https://www.pjrc.com/teensy/package_teensy_index.json + arduino-cli core update-index + arduino-cli core install teensy:avr + - name: "Build" + run: | + . ./fprime-venv/bin/activate + fprime-util generate + fprime-util build + - name: "Rename build files" + run: | + mv ${{ env.build_binary }} ${{ env.new_build_binary }} + mv ${{ env.dict_path}} ${{ env.new_dict_path}} + - name: "Upload build binary" + uses: actions/upload-artifact@v4 + with: + name: build_binary + path: ${{ env.new_build_binary }} + - name: "Upload dictionary" + uses: actions/upload-artifact@v4 + with: + name: dictionary.xml + path: ${{ env.new_dict_path }} + - run: echo "Expose env vars for reusable workflow." + + fit: + needs: build + uses: ./.github/workflows/reusable_fit_ci.yml + with: + test_path: ${{ needs.build.outputs.test_path }} + runs_on: self-hosted + run_job_select: ${{ needs.build.outputs.run_job_select }} + start_cmd: ${{ needs.build.outputs.start_cmd }} + gds_args: ${{ needs.build.outputs.gds_args }} diff --git a/.github/workflows/reusable_fit_ci.yml b/.github/workflows/reusable_fit_ci.yml new file mode 100644 index 00000000..24733c41 --- /dev/null +++ b/.github/workflows/reusable_fit_ci.yml @@ -0,0 +1,129 @@ +name: resuable-fit + +on: + workflow_call: + inputs: + test_path: + required: true + type: string + runs_on: + required: false + type: string + default: "ubuntu-latest" + run_job_select: + required: false + type: string + default: "non-embedded" + start_cmd: + required: false + type: string + default: "fprime-gds" + gds_args: + required: false + type: string + default: "" + +jobs: +# Setup job will run first by creating the virtual environment and then downloading the +# FSW binary and dictionary created in the build job step of the caller workflow to this +# workflow. After that the following run jobs are on a switch, only one will run based +# on run_job_select string. Each run job has a different method of starting FSW, FIT is +# run at the end of each job. If adding a new run job make sure it does not collide with +# an existing job. After one of these jobs is run, the upload FIT artifact job will then +# run. +# +# / run-non-embedded \ +# setup --->| run-fsw-gds |---> upload-fit-artifacts +# \ run-gds-fsw / + + setup: + runs-on: ${{ inputs.runs_on }} + steps: + - uses: actions/checkout@v4 + - name: "Checkout F' Repository" + uses: actions/checkout@v4 + with: + submodules: true + path: ${{ inputs.fprime_location }} + - name: "Install and activate Virtual Environment" + run: | + python3 -m venv ./fprime-venv + . ./fprime-venv/bin/activate + pip3 install -U setuptools wheel pip + pip3 install -r ${{ inputs.fprime_location }}./fprime/requirements.txt + - name: "Download FSW Binary" + uses: actions/download-artifact@v4 + with: + name: build_binary + - name: "Download Dictionary" + uses: actions/download-artifact@v4 + with: + name: dictionary.xml + + run-non-embedded: + if: ${{ inputs.run_job_select == 'non-embedded' }} + runs-on: ${{ inputs.runs_on }} + needs: setup + steps: + - name: "Start FSW and GDS" + run: | + . ./fprime-venv/bin/activate + ${{ inputs.start_cmd }} + - name: "Run Integration tests" + run: | + . ./fprime-venv/bin/activate + sleep 12 + pytest ${{ inputs.test_path }} --dictionary ./dictionary.xml -rP + + run-fsw-gds: + if: ${{ inputs.run_job_select == 'fsw-gds' }} + needs: setup + runs-on: ${{ inputs.runs_on }} + steps: + - name: "Start FSW" + run: ${{ inputs.start_cmd }} + - name: "Start GDS" + run: | + . ./fprime-venv/bin/activate + fprime-gds -n --dictionary ./dictionary.xml --gui none ${{ inputs.gds_args }} & + sleep 12 + - name: "Run Integration tests" + run: | + . ./fprime-venv/bin/activate + pytest ${{ inputs.test_path }} -rP --dictionary ./dictionary.xml --logs ./ + + run-gds-fsw: + if: ${{ inputs.run_job_select == 'gds-fsw' }} + needs: setup + runs-on: ${{ inputs.runs_on }} + steps: + - name: "Start GDS" + run: | + . ./fprime-venv/bin/activate + fprime-gds -n --dictionary ./dictionary.xml --gui none ${{ inputs.gds_args }} & + - name: "Start FSW" + run: | + ${{ inputs.start_cmd }} + sleep 12 + - name: "Run Integration tests" + run: | + . ./fprime-venv/bin/activate + pytest ${{ inputs.test_path }} --dictionary ./dictionary.xml -rP + + upload-fit-artifact: + runs-on: ${{ inputs.runs_on }} + if: ${{ always() }} #as long as one of the above jobs run, this job will run + needs: [run-non-embedded, run-fsw-gds, run-gds-fsw] + steps: + - name: "Upload Integration Test Results" + #Navigate into newest directory, which is for test logs. + #Rename test log because it contains date time and time is separated by colons that upload-artifact cannot handle. + #Store full path to test log xlsx in a variable for upload-artifact to use. + run: | + cd "$(ls -td -- */ | head -n 1)" + mv TestLog*.xlsx TestLog.xlsx + echo "TEST_LOG_PATH=$(realpath -s *.xlsx)" >> $GITHUB_ENV + - uses: actions/upload-artifact@v4 + with: + name: TestLog + path: ${{ env.TEST_LOG_PATH }} diff --git a/BaremetalReference/test/int/baremetal_ref_integration_test.py b/BaremetalReference/test/int/baremetal_ref_integration_test.py index f1880224..971fd289 100644 --- a/BaremetalReference/test/int/baremetal_ref_integration_test.py +++ b/BaremetalReference/test/int/baremetal_ref_integration_test.py @@ -16,8 +16,8 @@ def test_send_command(fprime_test_api): Tests command send, dispatch, and receipt using a pair of NO_OP commands. """ result = fprime_test_api.send_command('cmdDisp.CMD_NO_OP') - result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 3) - result = fprime_test_api.assert_event('OpCodeCompleted') + result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 5) + result = fprime_test_api.assert_event('OpCodeCompleted', timeout = 5) time.sleep(3) result = fprime_test_api.send_and_assert_command("cmdDisp.CMD_NO_OP", max_delay=10.0) #Note: send_and_assert_command has issues finding both displatched and completed EVRs. It can only find them if you @@ -30,8 +30,8 @@ def test_send_command_with_args(fprime_test_api): Test command send, dispatch, and receipt using a pair of NO_OP_STRING commands. """ result = fprime_test_api.send_command('cmdDisp.CMD_NO_OP_STRING', ['hello']) - result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 3) - result = fprime_test_api.assert_event('OpCodeCompleted') + result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 5) + result = fprime_test_api.assert_event('OpCodeCompleted', timeout = 5) time.sleep(3) result = fprime_test_api.send_and_assert_command('cmdDisp.CMD_NO_OP_STRING', ['hola'], max_delay=10.0) @@ -65,28 +65,28 @@ def test_event_filter(fprime_test_api): 4. Re-enable activity hi evrs and send BLINKING_ON_OFF back to on, verify evr is no longer filtered. """ result = fprime_test_api.send_command('blinker.BLINKING_ON_OFF', ['ON']) - result = fprime_test_api.assert_event('SetBlinkingState', timeout = 3) - result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 3) - result = fprime_test_api.assert_event('OpCodeCompleted') + result = fprime_test_api.assert_event('SetBlinkingState', timeout = 5) + result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 5) + result = fprime_test_api.assert_event('OpCodeCompleted', timeout = 5) time.sleep(3) result = fprime_test_api.send_command('eventLogger.SET_EVENT_FILTER', ['ACTIVITY_HI', 'DISABLED']) - result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 3) - result = fprime_test_api.assert_event('OpCodeCompleted') + result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 5) + result = fprime_test_api.assert_event('OpCodeCompleted', timeout = 5) time.sleep(3) result = fprime_test_api.send_command('blinker.BLINKING_ON_OFF', ['OFF']) result = fprime_test_api.await_event_count(0, 'SetBlinkingState') - result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 3) - result = fprime_test_api.assert_event('OpCodeCompleted') + result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 5) + result = fprime_test_api.assert_event('OpCodeCompleted', timeout = 5) time.sleep(3) #tear down steps. set EVR back to enable and LED back to on. result = fprime_test_api.send_command('eventLogger.SET_EVENT_FILTER', ['ACTIVITY_HI', 'ENABLED']) - result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 3) - result = fprime_test_api.assert_event('OpCodeCompleted') + result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 5) + result = fprime_test_api.assert_event('OpCodeCompleted', timeout = 5) time.sleep(3) result = fprime_test_api.send_command('blinker.BLINKING_ON_OFF', ['ON']) result = fprime_test_api.await_event_count(1, 'SetBlinkingState') - result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 3) - result = fprime_test_api.assert_event('OpCodeCompleted') + result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 5) + result = fprime_test_api.assert_event('OpCodeCompleted', timeout = 5) def test_id_filter(fprime_test_api): """Test that event can be filtered by ID. @@ -99,31 +99,31 @@ def test_id_filter(fprime_test_api): 5. Remove SetBlinkState EVR from the filter list and send BLINKING_ON_OFF back to on. """ result = fprime_test_api.send_command('blinker.BLINKING_ON_OFF', ['ON']) - result = fprime_test_api.assert_event('SetBlinkingState', timeout = 3) - result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 3) - result = fprime_test_api.assert_event('OpCodeCompleted') + result = fprime_test_api.assert_event('SetBlinkingState', timeout = 5) + result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 5) + result = fprime_test_api.assert_event('OpCodeCompleted', timeout = 5) time.sleep(3) result = fprime_test_api.send_command('eventLogger.SET_ID_FILTER', ['0x10001', 'ENABLED']) - result = fprime_test_api.assert_event('eventLogger.ID_FILTER_ENABLED', timeout = 3) - result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 3) - result = fprime_test_api.assert_event('OpCodeCompleted') + result = fprime_test_api.assert_event('eventLogger.ID_FILTER_ENABLED', timeout = 5) + result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 5) + result = fprime_test_api.assert_event('OpCodeCompleted', timeout = 5) time.sleep(3) result = fprime_test_api.send_command('blinker.BLINKING_ON_OFF', ['ON']) result = fprime_test_api.await_event_count(0, 'SetBlinkingState') - result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 3) - result = fprime_test_api.assert_event('OpCodeCompleted') + result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 5) + result = fprime_test_api.assert_event('OpCodeCompleted', timeout = 5) time.sleep(3) result = fprime_test_api.send_command('eventLogger.SET_ID_FILTER', ['0x10001', 'DISABLED']) - result = fprime_test_api.assert_event('eventLogger.ID_FILTER_REMOVED', timeout = 3) - result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 3) - result = fprime_test_api.assert_event('OpCodeCompleted') + result = fprime_test_api.assert_event('eventLogger.ID_FILTER_REMOVED', timeout = 5) + result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 5) + result = fprime_test_api.assert_event('OpCodeCompleted', timeout = 5) time.sleep(3) result = fprime_test_api.send_command('eventLogger.SET_ID_FILTER', ['0x100', 'DISABLED']) - result = fprime_test_api.assert_event('eventLogger.ID_FILTER_NOT_FOUND', timeout = 3) - result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 3) - result = fprime_test_api.assert_event('OpCodeError') + result = fprime_test_api.assert_event('eventLogger.ID_FILTER_NOT_FOUND', timeout = 5) + result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 5) + result = fprime_test_api.assert_event('OpCodeError', timeout = 5) time.sleep(3) result = fprime_test_api.send_command('blinker.BLINKING_ON_OFF', ['ON']) result = fprime_test_api.await_event_count(1, 'SetBlinkingState') - result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 3) - result = fprime_test_api.assert_event('OpCodeCompleted') + result = fprime_test_api.assert_event('OpCodeDispatched', timeout = 5) + result = fprime_test_api.assert_event('OpCodeCompleted', timeout = 5) diff --git a/docs/run-baremetal-reference.md b/docs/run-baremetal-reference.md index ac5ca06b..d3e443c3 100644 --- a/docs/run-baremetal-reference.md +++ b/docs/run-baremetal-reference.md @@ -24,6 +24,14 @@ Note: It has to be the full path to the BaremetalReference.hex file for arduino- 4. While programming the red led will be brighter and then flash. Once done the board will get out of HalfKay bootloader mode (red led off) and run the BaremetalReference application. You should now see an orange flashing led by the button. +### Alternate option for hex file upload +Instead of using arduino-cli, you can also use teensy_loader_cli. This alternative is best if you are running on a headless machine / SBC and using SSH. teensy_loader_cli requires compiling on the host machine but usage is simpler. + +1. After building the hex file, simply call: +``` +teensy_loader_cli --mcu=TEENSY41 -v -s /full_path/fprime-baremetal-reference/build-artifacts/teensy41/BaremetalReference/bin/BaremetalReference.hex +``` + ## Using GDS via serial 1. Find the serial port, this varies by your host system but it should show up as a `/dev/tty*` device. On the mac, it's /dev/tty.usbmodem*(9 digit number). 2. Launch GDS: