From 2100bebaaf97b242235981dd5dc0a161dc06830a Mon Sep 17 00:00:00 2001 From: Zach Radlicz Date: Wed, 19 Mar 2025 13:37:06 -0500 Subject: [PATCH 1/2] Added test framework, test, build, deploy pipeline --- .github/workflows/README.md | 122 ++ .github/workflows/manual-compile.yaml | 36 + .github/workflows/manual-flash.yaml | 66 + .github/workflows/manual-release.yaml | 71 + .github/workflows/manual-test.yaml | 46 + .github/workflows/manual-upload.yaml | 73 + .github/workflows/pr-workflow.yaml | 98 ++ .github/workflows/release-workflow.yaml | 130 ++ .gitignore | 5 +- .gitmodules | 27 + CMakeLists.txt | 10 + docs/MainLoopFlowDiagram.mmd | 56 + docs/StartupFlowDiagram.mmd | 56 + lib/Adafruit_SHT31 | 1 + lib/DPS368-Library-Arduino | 1 + lib/Driver_-_Haar | 2 +- lib/Driver_-_Kestrel | 2 +- lib/Driver_-_Li710 | 2 +- lib/Driver_-_MCP79412 | 1 + lib/Driver_-_T9602 | 2 +- lib/PAC1932_Library | 1 + lib/PCA9634 | 1 + lib/SparkFun_u-blox_GNSS_Arduino_Library | 1 + lib/VEML3328 | 1 + src/FlightControl_Demo.cpp | 37 +- src/FlightControl_Demo.ino | 1339 ----------------- test/CMakeLists.txt | 62 + test/external/fff | 1 + test/external/googletest | 1 + test/main.cpp | 10 + test/mocks/Arduino.h | 29 + test/mocks/MockArduino.cpp | 31 + test/mocks/MockArduino.h | 89 ++ test/mocks/MockBMA456.cpp | 11 + test/mocks/MockBMA456.h | 37 + test/mocks/MockGNSS.cpp | 18 + test/mocks/MockGNSS.h | 58 + test/mocks/MockIncludes.h | 65 + test/mocks/MockMCP79412.cpp | 13 + test/mocks/MockMCP79412.h | 23 + test/mocks/MockMXC6655.cpp | 6 + test/mocks/MockMXC6655.h | 29 + test/mocks/MockPAC1934.cpp | 17 + test/mocks/MockPAC1934.h | 21 + test/mocks/MockPCA9634.cpp | 9 + test/mocks/MockPCA9634.h | 23 + test/mocks/MockPCAL9535A.cpp | 17 + test/mocks/MockPCAL9535A.h | 23 + test/mocks/MockParticle.h | 70 + test/mocks/MockSHT4x.cpp | 17 + test/mocks/MockSHT4x.h | 29 + test/mocks/MockSPI.cpp | 4 + test/mocks/MockSensor.h | 36 + test/mocks/MockVEML3328.cpp | 19 + test/mocks/MockVEML3328.h | 23 + test/mocks/MockWire.cpp | 17 + test/mocks/MockWireDeclare.h | 41 + test/mocks/Particle.h | 45 + test/mocks/SPI.h | 30 + test/mocks/TestKestrel.h | 336 +++++ test/mocks/WProgram.h | 4 + test/mocks/Wire.h | 5 + .../Driver_-_KestrelFunctionTests.cpp | 151 ++ .../Driver_-_KestrelSetupTests.cpp | 317 ++++ .../FlightControl_DemoTest.cpp | 48 + .../FlightControl_DemoTestHarness.cpp | 14 + .../FlightControl_DemoTestHarness.h | 12 + 67 files changed, 2637 insertions(+), 1361 deletions(-) create mode 100644 .github/workflows/README.md create mode 100644 .github/workflows/manual-compile.yaml create mode 100644 .github/workflows/manual-flash.yaml create mode 100644 .github/workflows/manual-release.yaml create mode 100644 .github/workflows/manual-test.yaml create mode 100644 .github/workflows/manual-upload.yaml create mode 100644 .github/workflows/pr-workflow.yaml create mode 100644 .github/workflows/release-workflow.yaml create mode 100644 CMakeLists.txt create mode 100644 docs/MainLoopFlowDiagram.mmd create mode 100644 docs/StartupFlowDiagram.mmd create mode 160000 lib/Adafruit_SHT31 create mode 160000 lib/DPS368-Library-Arduino create mode 160000 lib/Driver_-_MCP79412 create mode 160000 lib/PAC1932_Library create mode 160000 lib/PCA9634 create mode 160000 lib/SparkFun_u-blox_GNSS_Arduino_Library create mode 160000 lib/VEML3328 delete mode 100755 src/FlightControl_Demo.ino create mode 100644 test/CMakeLists.txt create mode 160000 test/external/fff create mode 160000 test/external/googletest create mode 100644 test/main.cpp create mode 100644 test/mocks/Arduino.h create mode 100644 test/mocks/MockArduino.cpp create mode 100644 test/mocks/MockArduino.h create mode 100644 test/mocks/MockBMA456.cpp create mode 100644 test/mocks/MockBMA456.h create mode 100644 test/mocks/MockGNSS.cpp create mode 100644 test/mocks/MockGNSS.h create mode 100644 test/mocks/MockIncludes.h create mode 100644 test/mocks/MockMCP79412.cpp create mode 100644 test/mocks/MockMCP79412.h create mode 100644 test/mocks/MockMXC6655.cpp create mode 100644 test/mocks/MockMXC6655.h create mode 100644 test/mocks/MockPAC1934.cpp create mode 100644 test/mocks/MockPAC1934.h create mode 100644 test/mocks/MockPCA9634.cpp create mode 100644 test/mocks/MockPCA9634.h create mode 100644 test/mocks/MockPCAL9535A.cpp create mode 100644 test/mocks/MockPCAL9535A.h create mode 100644 test/mocks/MockParticle.h create mode 100644 test/mocks/MockSHT4x.cpp create mode 100644 test/mocks/MockSHT4x.h create mode 100644 test/mocks/MockSPI.cpp create mode 100644 test/mocks/MockSensor.h create mode 100644 test/mocks/MockVEML3328.cpp create mode 100644 test/mocks/MockVEML3328.h create mode 100644 test/mocks/MockWire.cpp create mode 100644 test/mocks/MockWireDeclare.h create mode 100644 test/mocks/Particle.h create mode 100644 test/mocks/SPI.h create mode 100644 test/mocks/TestKestrel.h create mode 100644 test/mocks/WProgram.h create mode 100644 test/mocks/Wire.h create mode 100644 test/unit/Driver_-_Kestrel/Driver_-_KestrelFunctionTests.cpp create mode 100644 test/unit/Driver_-_Kestrel/Driver_-_KestrelSetupTests.cpp create mode 100644 test/unit/FlightControl_Demo/FlightControl_DemoTest.cpp create mode 100644 test/unit/FlightControl_Demo/FlightControl_DemoTestHarness.cpp create mode 100644 test/unit/FlightControl_Demo/FlightControl_DemoTestHarness.h diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000..9be4b0c --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,122 @@ +# FlightControl Demo GitHub Actions Workflows + +This folder contains GitHub Actions workflows for automating the testing, building, and deployment of the FlightControl Demo firmware. + +## Automated Workflow Overview + +### PR Workflow (`pr-workflow.yaml`) + +Triggered in the following scenarios: +- When a pull request is opened, reopened, or updated (synchronize) against the `master` branch +- Manually from the GitHub Actions tab + +This workflow has three sequential jobs: + +1. **Test**: Runs unit tests using CMake +2. **Compile**: Compiles the firmware for the BSOM platform +3. **Flash**: Flashes the compiled firmware to a test device + +Each job only runs if the previous job succeeds, providing a complete validation pipeline for pull requests. + +### Release Workflow (`release-workflow.yaml`) + +Triggered in the following scenarios: +- When a PR is merged to `master` (note: closing without merging won't trigger the release) +- When code is pushed directly to `master` +- Manually from the GitHub Actions tab + +This workflow has two sequential jobs: + +1. **Release**: + - Compiles the firmware with auto-versioning + - Commits the updated version file + - Creates a GitHub release with the firmware binary and debug information + +2. **Upload**: + - Uploads the firmware to the Particle cloud for OTA updates to production devices + - Only runs if the firmware version was updated in the previous step + +## Individual Manual Workflows + +These workflows can only be triggered manually from the GitHub Actions tab and are designed for testing individual steps before opening a PR. + +### Manual Test (`manual-test.yaml`) +Runs the unit tests on a specific branch. +- **Inputs**: + - Branch to test (optional) + +### Manual Compile (`manual-compile.yaml`) +Compiles the firmware on a specific branch. +- **Inputs**: + - Branch to compile (optional) +- **Outputs**: + - Firmware binary as an artifact + +### Manual Flash (`manual-flash.yaml`) +Flashes a device with compiled firmware. +- **Inputs**: + - Device ID (optional, defaults to test device) + - Use latest artifact (yes/no) +- **Notes**: + - Can use the firmware from the latest manual-compile run or compile a new binary + +### Manual Release (`manual-release.yaml`) +Creates a test release without committing version changes. +- **Inputs**: + - Branch to release (optional) + - Force version increment (yes/no) + - Custom release notes (optional) +- **Notes**: + - Creates a GitHub prerelease with '-manual' tag suffix + - Doesn't commit version changes back to the repository + +### Manual Upload (`manual-upload.yaml`) +Uploads a firmware binary to Particle cloud. +- **Inputs**: + - Product ID (optional) + - Firmware version (required) + - Release title (optional) + - Use release artifact (yes/no) +- **Notes**: + - Can use the firmware from the latest manual-release run or compile a new binary + +## Manual Workflow Triggering (Main Workflows) + +The main workflows can also be triggered manually from the GitHub Actions tab: + +1. **PR Workflow**: + - Optional input for the reason for manual testing + +2. **Release Workflow**: + - Option to control version increment (yes/no) + - Custom release notes field that overrides the auto-generated ones + +## Required Secrets + +The workflows require the following secrets to be configured in your GitHub repository: + +- `PARTICLE_ACCESS_TOKEN`: Particle cloud API token for uploading firmware +- `PARTICLE_USER_LEVEL_ACCESS_TOKEN`: Particle token for flashing individual test devices +- `PARTICLE_TEST_DEVICE_ID`: ID of the test device to flash during PR testing +- `PARTICLE_PRODUCT_ID`: ID of the Particle product for production releases + +## Event Flow + +1. **When opening a PR to master**: + - PR Workflow runs (test → compile → flash) + - This validates the changes before they're merged + +2. **When merging a PR to master**: + - Release Workflow runs (compile/release → upload) + - This creates a new version, GitHub release, and deploys to Particle + +3. **When pushing directly to master**: + - Release Workflow runs (same as merging a PR) + - This is for quick changes that don't require a PR + +## Usage Notes + +1. For initial testing, use the individual manual workflows to check each step separately +2. Once confident, open a PR to trigger the full PR workflow +3. After reviewing and approving, merge the PR to trigger the release workflow +4. Manual workflows provide a way to test specific stages of the pipeline in isolation \ No newline at end of file diff --git a/.github/workflows/manual-compile.yaml b/.github/workflows/manual-compile.yaml new file mode 100644 index 0000000..2c32ccc --- /dev/null +++ b/.github/workflows/manual-compile.yaml @@ -0,0 +1,36 @@ +name: Manual Compile + +# This workflow can only be triggered manually +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch to compile' + required: false + default: '' + type: string + +jobs: + compile: + name: Compile Firmware + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + ref: ${{ github.event.inputs.branch || github.ref }} + + - name: Compile application + id: compile + uses: particle-iot/compile-action@9dbe1eb567c6268f1baa7458217d5d6e5553850d + with: + particle-platform-name: 'bsom' + device-os-version: 6.2.1 + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: firmware-binary + path: ${{ steps.compile.outputs.firmware-path }} + retention-days: 1 \ No newline at end of file diff --git a/.github/workflows/manual-flash.yaml b/.github/workflows/manual-flash.yaml new file mode 100644 index 0000000..0bba37e --- /dev/null +++ b/.github/workflows/manual-flash.yaml @@ -0,0 +1,66 @@ +name: Manual Flash + +# This workflow can only be triggered manually +on: + workflow_dispatch: + branch: + description: 'Branch to compile' + required: false + default: '' + type: string + inputs: + device_id: + description: 'Device ID to flash (defaults to test device)' + required: false + default: '' + type: string + use_latest_artifact: + description: 'Use latest uploaded artifact? (yes/no)' + required: false + default: 'yes' + type: string + +jobs: + flash: + name: Flash Device + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + ref: ${{ github.event.inputs.branch || github.ref }} + + - name: Compile firmware + id: compile + if: github.event.inputs.use_latest_artifact != 'yes' + uses: particle-iot/compile-action@9dbe1eb567c6268f1baa7458217d5d6e5553850d + with: + particle-platform-name: 'bsom' + device-os-version: 6.2.1 + + - name: Download latest firmware artifact + if: github.event.inputs.use_latest_artifact == 'yes' + uses: dawidd6/action-download-artifact@v3 + with: + workflow: manual-compile.yaml + name: firmware-binary + path: ./firmware + + - name: Find firmware binary + id: find_binary + run: | + if [ "${{ github.event.inputs.use_latest_artifact }}" == "yes" ]; then + FIRMWARE=$(find ./firmware -name "*.bin" -type f | head -n 1) + else + FIRMWARE="${{ steps.compile.outputs.firmware-path }}" + fi + echo "Using firmware: $FIRMWARE" + echo "firmware-path=$FIRMWARE" >> $GITHUB_OUTPUT + + - name: Flash device + uses: zradlicz/flash-device-action@fef2be3306f8a1fa40c75c832e9127513d973f3a + with: + particle-access-token: ${{ secrets.PARTICLE_USER_LEVEL_ACCESS_TOKEN }} + device-id: ${{ github.event.inputs.device_id || secrets.PARTICLE_TEST_DEVICE_ID }} + firmware-path: ${{ steps.find_binary.outputs.firmware-path }} \ No newline at end of file diff --git a/.github/workflows/manual-release.yaml b/.github/workflows/manual-release.yaml new file mode 100644 index 0000000..537b147 --- /dev/null +++ b/.github/workflows/manual-release.yaml @@ -0,0 +1,71 @@ +name: Manual Release + +# This workflow can only be triggered manually +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch to release' + required: false + default: '' + type: string + force_version_increment: + description: 'Force version increment' + required: false + default: 'yes' + type: string + release_notes: + description: 'Custom release notes' + required: false + type: string + +jobs: + release: + name: Create Release + runs-on: ubuntu-latest + outputs: + firmware-path: ${{ steps.compile.outputs.firmware-path }} + firmware-version: ${{ steps.compile.outputs.firmware-version }} + firmware-version-updated: ${{ steps.compile.outputs.firmware-version-updated }} + release-url: ${{ steps.release.outputs.html_url }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event.inputs.branch || github.ref }} + + - name: Compile application + id: compile + uses: particle-iot/compile-action@v1 + with: + particle-platform-name: 'bsom' + auto-version: ${{ github.event.inputs.force_version_increment == 'yes' }} + device-os-version: 6.1.1 + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: release-artifacts + path: | + ${{ steps.compile.outputs.firmware-path }} + ${{ steps.compile.outputs.target-path }} + + - name: Create archive of target directory + if: steps.compile.outputs.firmware-version-updated == 'true' + run: | + tar -czf debug-objects.tar.gz ${{ steps.compile.outputs.target-path }} + + - name: Create GitHub release + id: release + if: steps.compile.outputs.firmware-version-updated == 'true' + uses: ncipollo/release-action@v1 + with: + artifacts: ${{ steps.compile.outputs.firmware-path }},debug-objects.tar.gz + generateReleaseNotes: ${{ github.event.inputs.release_notes == '' }} + body: ${{ github.event.inputs.release_notes }} + name: "Firmware v${{ steps.compile.outputs.firmware-version }} (Manual)" + tag: "v${{ steps.compile.outputs.firmware-version }}-manual" + commit: ${{ github.sha }} + token: ${{ secrets.GITHUB_TOKEN }} + prerelease: true \ No newline at end of file diff --git a/.github/workflows/manual-test.yaml b/.github/workflows/manual-test.yaml new file mode 100644 index 0000000..71cd0bc --- /dev/null +++ b/.github/workflows/manual-test.yaml @@ -0,0 +1,46 @@ +name: Manual Test + +# This workflow can only be triggered manually +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch to test' + required: false + default: '' + type: string + +jobs: + test: + name: Run Tests + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + ref: ${{ github.event.inputs.branch || github.ref }} + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y cmake build-essential + + - name: Create build directory + run: mkdir -p build + + - name: Configure CMake + run: cd build && cmake .. + + - name: Build + run: cd build && cmake --build . + + - name: Run tests + run: | + TEST_EXECUTABLE=$(find build -name "unit_tests" -type f -executable) + if [ -n "$TEST_EXECUTABLE" ]; then + $TEST_EXECUTABLE + else + echo "Test executable not found!" + exit 1 + fi \ No newline at end of file diff --git a/.github/workflows/manual-upload.yaml b/.github/workflows/manual-upload.yaml new file mode 100644 index 0000000..d1feb0b --- /dev/null +++ b/.github/workflows/manual-upload.yaml @@ -0,0 +1,73 @@ +name: Manual Upload to Particle + +# This workflow can only be triggered manually +on: + workflow_dispatch: + inputs: + product_id: + description: 'Custom product ID (defaults to secret)' + required: false + default: '' + type: string + version: + description: 'Firmware version' + required: true + type: string + title: + description: 'Release title' + required: false + default: 'Manual Upload' + type: string + use_release_artifact: + description: 'Use artifact from manual-release? (yes/no)' + required: false + default: 'yes' + type: string + +jobs: + upload: + name: Upload to Particle + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + if: github.event.inputs.use_release_artifact != 'yes' + with: + fetch-depth: 1 + + - name: Compile firmware if not using artifact + id: compile + if: github.event.inputs.use_release_artifact != 'yes' + uses: particle-iot/compile-action@v1 + with: + particle-platform-name: 'bsom' + device-os-version: 6.1.1 + + - name: Download release artifacts + if: github.event.inputs.use_release_artifact == 'yes' + uses: dawidd6/action-download-artifact@v3 + with: + workflow: manual-release.yaml + name: release-artifacts + path: ./firmware + + - name: Find firmware binary + id: find_binary + run: | + if [ "${{ github.event.inputs.use_release_artifact }}" == "yes" ]; then + FIRMWARE=$(find ./firmware -name "*.bin" -type f | head -n 1) + else + FIRMWARE="${{ steps.compile.outputs.firmware-path }}" + fi + echo "Using firmware: $FIRMWARE" + echo "firmware-path=$FIRMWARE" >> $GITHUB_OUTPUT + + - name: Upload product firmware to Particle + uses: particle-iot/firmware-upload-action@v1 + with: + particle-access-token: ${{ secrets.PARTICLE_ACCESS_TOKEN }} + firmware-path: ${{ steps.find_binary.outputs.firmware-path }} + firmware-version: ${{ github.event.inputs.version }} + product-id: ${{ github.event.inputs.product_id || secrets.PARTICLE_PRODUCT_ID }} + title: ${{ github.event.inputs.title }} + description: 'Manual upload from GitHub Actions workflow' \ No newline at end of file diff --git a/.github/workflows/pr-workflow.yaml b/.github/workflows/pr-workflow.yaml new file mode 100644 index 0000000..eef8465 --- /dev/null +++ b/.github/workflows/pr-workflow.yaml @@ -0,0 +1,98 @@ +name: Pull Request Workflow + +on: + # Runs on PR open + pull_request: + types: [opened, reopened, synchronize] + branches: + - master # or 'main' depending on your main branch + + # Allows manual triggering from the Actions tab + workflow_dispatch: + inputs: + reason: + description: 'Reason for manual trigger' + required: false + default: 'Manual testing' + +jobs: + test: + name: Run Tests + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y cmake build-essential + + - name: Create build directory + run: mkdir -p build + + - name: Configure CMake + run: cd build && cmake .. + + - name: Build + run: cd build && cmake --build . + + - name: Run tests + run: | + TEST_EXECUTABLE=$(find build -name "unit_tests" -type f -executable) + if [ -n "$TEST_EXECUTABLE" ]; then + $TEST_EXECUTABLE + else + echo "Test executable not found!" + exit 1 + fi + + compile: + name: Compile Firmware + needs: test + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Compile application + id: compile + uses: particle-iot/compile-action@9dbe1eb567c6268f1baa7458217d5d6e5553850d + with: + particle-platform-name: 'bsom' + device-os-version: 6.2.1 + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: firmware-artifact + path: ${{ steps.compile.outputs.firmware-path }} + retention-days: 1 + + flash: + name: Flash Test Device + needs: compile + runs-on: ubuntu-latest + steps: + - name: Download firmware artifact + uses: actions/download-artifact@v4 + with: + name: firmware-artifact + path: ./firmware + + - name: Find firmware binary + id: find_binary + run: | + FIRMWARE=$(find ./firmware -name "*.bin" -type f | head -n 1) + echo "firmware-path=$FIRMWARE" >> $GITHUB_OUTPUT + + - name: Flash device + uses: zradlicz/flash-device-action@fef2be3306f8a1fa40c75c832e9127513d973f3a + with: + particle-access-token: ${{ secrets.PARTICLE_USER_LEVEL_ACCESS_TOKEN }} + device-id: ${{ secrets.PARTICLE_TEST_DEVICE_ID }} + firmware-path: ${{ steps.find_binary.outputs.firmware-path }} \ No newline at end of file diff --git a/.github/workflows/release-workflow.yaml b/.github/workflows/release-workflow.yaml new file mode 100644 index 0000000..aeb8297 --- /dev/null +++ b/.github/workflows/release-workflow.yaml @@ -0,0 +1,130 @@ +name: Release Workflow + +on: + # Runs on direct push to master + push: + branches: + - master # or 'main' depending on your main branch + + # Runs on PR completion (merge or close) + pull_request: + types: + - closed + branches: + - master # or 'main' depending on your main branch + + # Allows manual triggering from the Actions tab + workflow_dispatch: + inputs: + version_increment: + description: 'Force version increment (yes/no)' + required: false + default: 'no' + release_notes: + description: 'Custom release notes' + required: false + +jobs: + release: + name: Compile and Release + # Run this job if: + # 1. Direct push to master, or + # 2. PR was merged (not just closed), or + # 3. Manually triggered + if: github.event_name == 'push' || + (github.event_name == 'pull_request' && github.event.pull_request.merged == true) || + github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + outputs: + firmware-path: ${{ steps.compile.outputs.firmware-path }} + firmware-version: ${{ steps.compile.outputs.firmware-version }} + firmware-version-updated: ${{ steps.compile.outputs.firmware-version-updated }} + release-url: ${{ steps.release.outputs.html_url }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Compile application + id: compile + uses: particle-iot/compile-action@9dbe1eb567c6268f1baa7458217d5d6e5553850d + with: + particle-platform-name: 'bsom' + auto-version: ${{ github.event_name != 'workflow_dispatch' || github.event.inputs.version_increment != 'no' }} + device-os-version: 6.2.1 + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: release-artifacts + path: | + ${{ steps.compile.outputs.firmware-path }} + ${{ steps.compile.outputs.target-path }} + + - name: Commit updated version file + id: commit + if: steps.compile.outputs.firmware-version-updated == 'true' + run: | + git config user.name 'github-actions[bot]' + git config user.email 'github-actions[bot]@users.noreply.github.com' + git commit -m "Update firmware version" -a + echo "updated-version-sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + + # When a GitHub Action pushes commits or tags, it does not trigger a new GitHub Action job + - name: Push changes + if: steps.compile.outputs.firmware-version-updated == 'true' + uses: ad-m/github-push-action@v0.6.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: ${{ github.ref }} + + - name: Create archive of target directory + if: steps.compile.outputs.firmware-version-updated == 'true' + run: | + tar -czf debug-objects.tar.gz ${{ steps.compile.outputs.target-path }} + + - name: Create GitHub release + id: release + if: steps.compile.outputs.firmware-version-updated == 'true' + uses: ncipollo/release-action@v1 + with: + artifacts: ${{ steps.compile.outputs.firmware-path }},debug-objects.tar.gz + generateReleaseNotes: ${{ github.event_name != 'workflow_dispatch' || github.event.inputs.release_notes == '' }} + body: ${{ github.event.inputs.release_notes }} + name: "Firmware v${{ steps.compile.outputs.firmware-version }}" + tag: "v${{ steps.compile.outputs.firmware-version }}" + commit: ${{ steps.commit.outputs.updated-version-sha || github.sha }} + token: ${{ secrets.GITHUB_TOKEN }} + + upload: + name: Upload to Particle + needs: release + runs-on: ubuntu-latest + # Only run if release job has completed and the firmware version was updated + if: needs.release.outputs.firmware-version-updated == 'true' + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Download release artifacts + uses: actions/download-artifact@v4 + with: + name: release-artifacts + path: ./release + + - name: Find firmware binary + id: find_binary + run: | + FIRMWARE=$(find ./release -name "*.bin" -type f | head -n 1) + echo "firmware-path=$FIRMWARE" >> $GITHUB_OUTPUT + + - name: Upload product firmware to Particle + uses: particle-iot/firmware-upload-action@v1 + with: + particle-access-token: ${{ secrets.PARTICLE_ACCESS_TOKEN }} + firmware-path: ${{ steps.find_binary.outputs.firmware-path }} + firmware-version: ${{ needs.release.outputs.firmware-version }} + product-id: ${{ secrets.PARTICLE_PRODUCT_ID }} + title: 'Firmware v${{ needs.release.outputs.firmware-version }}' + description: '[Firmware v${{ needs.release.outputs.firmware-version }} GitHub Release](${{ needs.release.outputs.release-url }})' \ No newline at end of file diff --git a/.gitignore b/.gitignore index be65264..68642cb 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,7 @@ #VS Code .vscode/ -target/ \ No newline at end of file +target/ + +#build +build/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index bba749b..fc0685c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -104,3 +104,30 @@ [submodule "lib/Driver_-_BaroVue10"] path = lib/Driver_-_BaroVue10 url = https://github.com/RTGS-Lab/Driver_-_BaroVue10.git +[submodule "lib/DPS368-Library-Arduino"] + path = lib/DPS368-Library-Arduino + url = https://github.com/gemsiot/DPS368-Library-Arduino.git +[submodule "lib/Adafruit_SHT31"] + path = lib/Adafruit_SHT31 + url = https://github.com/gems-labx/Adafruit_SHT31 +[submodule "lib/VEML3328"] + path = lib/VEML3328 + url = https://github.com/gemsiot/VEML3328.git +[submodule "lib/PAC1932_Library"] + path = lib/PAC1932_Library + url = https://github.com/bschulz1701/PAC1932_Library.git +[submodule "lib/PCA9634"] + path = lib/PCA9634 + url = https://github.com/gemsiot/PCA9634.git +[submodule "lib/SparkFun_u-blox_GNSS_Arduino_Library"] + path = lib/SparkFun_u-blox_GNSS_Arduino_Library + url = https://github.com/sparkfun/SparkFun_u-blox_GNSS_Arduino_Library.git +[submodule "lib/Driver_-_MCP79412"] + path = lib/Driver_-_MCP79412 + url = https://github.com/gemsiot/Driver_-_MCP79412.git +[submodule "test/external/googletest"] + path = test/external/googletest + url = https://github.com/google/googletest.git +[submodule "test/external/fff"] + path = test/external/fff + url = https://github.com/meekrosoft/fff.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6e61118 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,10 @@ +# FlightControl_Demo/CMakeLists.txt +cmake_minimum_required(VERSION 3.14) +project(FlightControl_Demo_Tests) + +# Specify C++ standard +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Add test directory +add_subdirectory(test) \ No newline at end of file diff --git a/docs/MainLoopFlowDiagram.mmd b/docs/MainLoopFlowDiagram.mmd new file mode 100644 index 0000000..3f9129e --- /dev/null +++ b/docs/MainLoopFlowDiagram.mmd @@ -0,0 +1,56 @@ +graph TD + MainLoop([Main Loop]) --> WakeSensors[Wake Up Logger and Sensors] + + WakeSensors --> WaitTimer[Wait Until Timer Period Done] + WaitTimer --> StartTimer[Start Next Timer Period] + + StartTimer --> LogCheck{Which Logging Mode?} + + LogCheck -->|PERFORMANCE| LogPerformance[Log Data Only for Speed] + LogCheck -->|STANDARD| CountCheck1{Log Count?} + LogCheck -->|BALANCED| LogBalanced[Log Data & Errors] + LogCheck -->|NO_LOCAL| CountCheck2{Log Count?} + + CountCheck1 -->|Count % 16 = 0| LogFull1[Full Log: Data, Diagnostics, Metadata] + CountCheck1 -->|Count % 8 = 0| LogPartial1[Partial Log: Data & Diagnostics] + CountCheck1 -->|Other| LogMinimal1[Minimal Log: Data Only] + + CountCheck2 -->|Count % 10 = 0| LogFull2[Full Log to Particle: Data, Diagnostics, Metadata] + CountCheck2 -->|Count % 5 = 0| LogPartial2[Partial Log to Particle: Data & Diagnostics] + CountCheck2 -->|Other| LogMinimal2[Minimal Log to Particle: Data Only] + + LogPerformance --> TimeDiagCheck{Time for Diagnostics?} + LogBalanced --> TimeDiagCheck + + TimeDiagCheck -->|Yes| LogFullDiag[Log Full Diagnostics & Metadata] + TimeDiagCheck -->|No| SkipDiag[Skip Extended Diagnostics] + + LogFull1 --> SleepSensors[Sleep Sensors] + LogPartial1 --> SleepSensors + LogMinimal1 --> SleepSensors + LogFull2 --> SleepSensors + LogPartial2 --> SleepSensors + LogMinimal2 --> SleepSensors + LogFullDiag --> SleepSensors + SkipDiag --> SleepSensors + + SleepSensors --> BackhaulCheck{Time for Backhaul?} + + BackhaulCheck -->|Yes| PowerCheck{Low Power Mode?} + BackhaulCheck -->|No| IncrementCount[Increment Log Count] + + PowerCheck -->|Yes| ConnectParticle[Connect to Particle] + PowerCheck -->|No| SkipConnect[Skip Connect] + + ConnectParticle --> WaitConnect[Wait for Connection] + WaitConnect --> SyncTime[Sync Time] + SkipConnect --> SyncTime + + SyncTime --> DumpFRAM[Dump FRAM to Particle] + DumpFRAM --> IncrementCount + + IncrementCount --> SleepFileSystem[Sleep File System] + SleepFileSystem --> SleepLogger[Sleep Logger] + SleepLogger --> Loop([Loop Back]) + + Loop --> MainLoop \ No newline at end of file diff --git a/docs/StartupFlowDiagram.mmd b/docs/StartupFlowDiagram.mmd new file mode 100644 index 0000000..2c73f48 --- /dev/null +++ b/docs/StartupFlowDiagram.mmd @@ -0,0 +1,56 @@ +graph TD + Start([Start]) --> ConfigPower[Configure Power Save Mode] + ConfigPower --> EnableFeatures[Enable System Features] + EnableFeatures --> CheckReset{Reset Reason?} + + CheckReset -->|Power Down| NormalStart[Continue Normal Startup] + CheckReset -->|Other| ParticleConnect[Connect to Particle Cloud] + ParticleConnect --> NormalStart + + NormalStart --> InitLogger[Initialize Kestrel Logger] + InitLogger --> InitFileSystem[Initialize File System] + InitFileSystem --> CheckErrors{Critical Errors?} + + CheckErrors -->|Yes| SetErrorLED[Set Error LED] + CheckErrors -->|No| SetPassLED[Set Pass LED] + + SetErrorLED --> InitBattery[Initialize Battery System] + SetPassLED --> InitBattery + + InitBattery --> InitIO[Initialize IO Expanders] + InitIO --> EnableLEDs[Enable LED Indicators] + + EnableLEDs --> SerialCheck{Serial Connected?} + SerialCheck -->|Yes| CommandMode[Enter Command Mode] + SerialCheck -->|No| ContinueStartup[Continue Startup] + CommandMode --> ContinueStartup + + ContinueStartup --> ConnectParticle[Connect to Particle Cloud] + ConnectParticle --> DetectTalons[Detect Connected Talons] + DetectTalons --> DetectSensors[Detect Connected Sensors] + + DetectSensors --> UpdateGPS[Update GPS Location] + UpdateGPS --> InitSensors[Initialize All Sensors] + InitSensors --> LogInitData[Log Initialization Data] + + LogInitData --> RapidCheck{Rapid Start?} + RapidCheck -->|Yes| SkipWait[Skip Connection Wait] + RapidCheck -->|No| WaitForConnect[Wait for Connections] + + WaitForConnect --> ConnCheck{Cell Connected?} + ConnCheck -->|Yes| SetCellPass[Set Cell LED to Pass] + ConnCheck -->|No| SetCellError[Set Cell LED to Error] + + SetCellPass --> GPSCheck{GPS Fix?} + SetCellError --> GPSCheck + + GPSCheck -->|Yes| SetGPSPass[Set GPS LED to Pass] + GPSCheck -->|No| SetGPSError[Set GPS LED to Error] + + SetGPSPass --> Backhaul[Try Backhaul Unsent Logs] + SetGPSError --> Backhaul + + SkipWait --> Backhaul + + Backhaul --> SetTimer[Start Log Timer] + SetTimer --> EnterMainLoop([Enter Main Loop]) \ No newline at end of file diff --git a/lib/Adafruit_SHT31 b/lib/Adafruit_SHT31 new file mode 160000 index 0000000..c9479a4 --- /dev/null +++ b/lib/Adafruit_SHT31 @@ -0,0 +1 @@ +Subproject commit c9479a4e17233d7228bbb0719c41410832ae309d diff --git a/lib/DPS368-Library-Arduino b/lib/DPS368-Library-Arduino new file mode 160000 index 0000000..e0f9576 --- /dev/null +++ b/lib/DPS368-Library-Arduino @@ -0,0 +1 @@ +Subproject commit e0f9576f26bd11020c58454b40c6142fcbc80f02 diff --git a/lib/Driver_-_Haar b/lib/Driver_-_Haar index a83fbc8..9b37f9d 160000 --- a/lib/Driver_-_Haar +++ b/lib/Driver_-_Haar @@ -1 +1 @@ -Subproject commit a83fbc891665b4cfbfcadeba453b1b040cb98ff8 +Subproject commit 9b37f9d6eec3ef2d1c3a32e467c48ba09170bb43 diff --git a/lib/Driver_-_Kestrel b/lib/Driver_-_Kestrel index 3ceaeb3..3591e84 160000 --- a/lib/Driver_-_Kestrel +++ b/lib/Driver_-_Kestrel @@ -1 +1 @@ -Subproject commit 3ceaeb34005faecb8a5fc1a03b4386dc7e285e90 +Subproject commit 3591e84f206a1f09ebe7635247c32d3427809086 diff --git a/lib/Driver_-_Li710 b/lib/Driver_-_Li710 index 69e7d29..c43b276 160000 --- a/lib/Driver_-_Li710 +++ b/lib/Driver_-_Li710 @@ -1 +1 @@ -Subproject commit 69e7d29f849221b08fd89aec60317318dd039ea0 +Subproject commit c43b276337d09504a9f53c28270dee2190e31ea1 diff --git a/lib/Driver_-_MCP79412 b/lib/Driver_-_MCP79412 new file mode 160000 index 0000000..45abb14 --- /dev/null +++ b/lib/Driver_-_MCP79412 @@ -0,0 +1 @@ +Subproject commit 45abb14ae26e4d28e16e6117524507e2a6d83d6f diff --git a/lib/Driver_-_T9602 b/lib/Driver_-_T9602 index b4b596e..7c57180 160000 --- a/lib/Driver_-_T9602 +++ b/lib/Driver_-_T9602 @@ -1 +1 @@ -Subproject commit b4b596ebf9901453ace207e53792dfc8d346c68f +Subproject commit 7c571805b6cb5a75b04131547f256479b8763e1c diff --git a/lib/PAC1932_Library b/lib/PAC1932_Library new file mode 160000 index 0000000..5d8eef0 --- /dev/null +++ b/lib/PAC1932_Library @@ -0,0 +1 @@ +Subproject commit 5d8eef0eb41814d948cbcf8b6be8ef7fac9092f1 diff --git a/lib/PCA9634 b/lib/PCA9634 new file mode 160000 index 0000000..678d383 --- /dev/null +++ b/lib/PCA9634 @@ -0,0 +1 @@ +Subproject commit 678d383fdef776160ac0c833f4af8e3b5bbdd799 diff --git a/lib/SparkFun_u-blox_GNSS_Arduino_Library b/lib/SparkFun_u-blox_GNSS_Arduino_Library new file mode 160000 index 0000000..4e542ba --- /dev/null +++ b/lib/SparkFun_u-blox_GNSS_Arduino_Library @@ -0,0 +1 @@ +Subproject commit 4e542ba29be98088e5a1c4ee9ccafdd07901cb86 diff --git a/lib/VEML3328 b/lib/VEML3328 new file mode 160000 index 0000000..39c1cc8 --- /dev/null +++ b/lib/VEML3328 @@ -0,0 +1 @@ +Subproject commit 39c1cc8a65f30284c2c7978458213eeb06d68921 diff --git a/src/FlightControl_Demo.cpp b/src/FlightControl_Demo.cpp index 8b058c1..1815ba8 100644 --- a/src/FlightControl_Demo.cpp +++ b/src/FlightControl_Demo.cpp @@ -1,9 +1,9 @@ -/******************************************************/ -// THIS IS A GENERATED FILE - DO NOT EDIT // -/******************************************************/ +#ifndef TESTING +#include "Particle.h" +#else +#include "MockParticle.h" +#endif -#include "Particle.h" -#line 1 "c:/Users/schul/Documents/Firmware_-_FlightControl-Demo/src/FlightControl_Demo.ino" /* * Project FlightControl_Demo * Description: Core controller for Flight data logger @@ -33,13 +33,14 @@ int takeSample(String dummy); int commandExe(String command); int systemRestart(String resetType); int configurePowerSave(int desiredPowerSaveMode); -#line 10 "c:/Users/schul/Documents/Firmware_-_FlightControl-Demo/src/FlightControl_Demo.ino" + #define WAIT_GPS false #define USE_CELL //System attempts to connect to cell #include #include #include #include +#include #include #include #include @@ -58,8 +59,8 @@ int configurePowerSave(int desiredPowerSaveMode); #include #include -const String firmwareVersion = "2.9.10"; -const String schemaVersion = "2.2.8"; +const String firmwareVersion = "2.9.11"; +const String schemaVersion = "2.2.9"; const unsigned long maxConnectTime = 180000; //Wait up to 180 seconds for systems to connect const unsigned long indicatorTimeout = 60000; //Wait for up to 1 minute with indicator lights on @@ -94,8 +95,8 @@ namespace LogModes { }; /////////////////////////// BEGIN USER CONFIG //////////////////////// -// PRODUCT_ID(15820) //Configured based on the target product, comment out if device has no product -// PRODUCT_VERSION(9) //Configure based on the firmware version you wish to create, check product firmware page to see what is currently the highest number +PRODUCT_ID(18596) //Configured based on the target product, comment out if device has no product +PRODUCT_VERSION(30) //Configure based on the firmware version you wish to create, check product firmware page to see what is currently the highest number const int backhaulCount = 4; //Number of log events before backhaul is performed const unsigned long logPeriod = 300; //Number of seconds to wait between logging events @@ -114,6 +115,7 @@ TDR315H soil3(sdi12, 0, 0); //Instantiate soil sensor with default ports and unk Hedorah gas(0, 0, 0x10); //Instantiate CO2 sensor with default ports and v1.0 hardware // T9602 humidity(0, 0, 0x00); //Instantiate Telair T9602 with default ports and version v0.0 LI710 et(sdi12, 0, 0); //Instantiate ET sensor with default ports and unknown version, pass over SDI12 Talon interface +BaroVue10 campPressure(sdi12, 0, 0x00); // Instantiate Barovue10 with default ports and v0.0 hardware const uint8_t numSensors = 7; //Number must match the number of objects defined in `sensors` array @@ -170,12 +172,13 @@ namespace PinsIOBeta { //For Kestrel v1.1 -// SYSTEM_MODE(MANUAL); -SYSTEM_MODE(SEMI_AUTOMATIC); -// SYSTEM_MODE(AUTOMATIC); //DEBUG! -SYSTEM_THREAD(ENABLED); //USE FOR FASTER STARTUP +// SYSTEM_MODE(MANUAL); //User must call Particle.process() to stay connected to cellular after conecting, not recommended for use. +SYSTEM_MODE(SEMI_AUTOMATIC); //Particle will wait until told to connect to Cellular, but try to stay connected once connected. +// SYSTEM_MODE(AUTOMATIC); //Particle automatically tries to connect to Cellular, once connected, user code starts running. + +SYSTEM_THREAD(ENABLED); //SYSTEM_THREAD enabled means Network processign runs on different thread than user loop code, recommended for use. // SYSTEM_THREAD(DISABLED); -// SYSTEM_MODE(AUTOMATIC); + int detectTalons(String dummyStr = ""); int detectSensors(String dummyStr = ""); @@ -185,8 +188,8 @@ String metadata = ""; String data = ""; void setup() { - configurePowerSave(desiredPowerSaveMode); //Setup power mode of the system - System.enableFeature(FEATURE_RESET_INFO); //DEBUG! + configurePowerSave(desiredPowerSaveMode); //Setup power mode of the system (Talons and Sensors) + System.enableFeature(FEATURE_RESET_INFO); //Allows for Particle to see reason for last reset using System.resetReason(); if(System.resetReason() != RESET_REASON_POWER_DOWN) { //DEBUG! Set safe mode Particle.connect(); //DEBUG! //If reset not caused by power switch, assume something bad happened, just connect to particle straight away diff --git a/src/FlightControl_Demo.ino b/src/FlightControl_Demo.ino deleted file mode 100755 index 7fb535a..0000000 --- a/src/FlightControl_Demo.ino +++ /dev/null @@ -1,1339 +0,0 @@ -/* - * Project FlightControl_Demo - * Description: Core controller for Flight data logger - * Author: Bobby Schulz - * Date: 06/14/2022 - * © 2023 Regents of the University of Minnesota. All rights reserved. - */ - -// #define RAPID_START //Does not wait for remote connection on startup -#define WAIT_GPS false -#define USE_CELL //System attempts to connect to cell -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -const String firmwareVersion = "2.9.11"; -const String schemaVersion = "2.2.9"; - -const unsigned long maxConnectTime = 180000; //Wait up to 180 seconds for systems to connect -const unsigned long indicatorTimeout = 60000; //Wait for up to 1 minute with indicator lights on -const uint64_t balancedDiagnosticPeriod = 3600000; //Report diagnostics once an hour //DEBUG! -int powerSaveMode = 0; //Default to 0, update when configure power save mode is called - -Kestrel logger(true); -KestrelFileHandler fileSys(logger); -Gonk battery(5); //Instantiate with defaults, manually set to port 5 -AuxTalon aux(0, 0x14); //Instantiate AUX talon with deaults - null port and hardware v1.4 -I2CTalon i2c(0, 0x21); //Instantiate I2C talon with alt - null port and hardware v2.1 -SDI12Talon sdi12(0, 0x14); //Instantiate SDI12 talon with alt - null port and hardware v1.4 -PCAL9535A ioAlpha(0x20); -PCAL9535A ioBeta(0x21); - -String globalNodeID = ""; //Store current node ID - -const uint8_t numTalons = 3; //Number must match the number of objects defined in `talonsToTest` array - -Talon* talons[Kestrel::numTalonPorts]; //Create an array of the total possible length -Talon* talonsToTest[numTalons] = { - &aux, - &i2c, - &sdi12 -}; - -namespace LogModes { - constexpr uint8_t STANDARD = 0; - constexpr uint8_t PERFORMANCE = 1; - constexpr uint8_t BALANCED = 2; - constexpr uint8_t NO_LOCAL = 3; //Same as standard log, but no attempt to log to SD card -}; - -/////////////////////////// BEGIN USER CONFIG //////////////////////// -// PRODUCT_ID(15820) //Configured based on the target product, comment out if device has no product -// PRODUCT_VERSION(9) //Configure based on the firmware version you wish to create, check product firmware page to see what is currently the highest number - -const int backhaulCount = 4; //Number of log events before backhaul is performed -const unsigned long logPeriod = 300; //Number of seconds to wait between logging events -int desiredPowerSaveMode = PowerSaveModes::LOW_POWER; //Specify the power save mode you wish to use: PERFORMANCE, BALANCED, LOW_POWER, ULTRA_LOW_POWER -int loggingMode = LogModes::STANDARD; //Specify the type of logging mode you wish to use: STANDARD, PERFORMANCE, BALANCED, NO_LOCAL - -Haar haar(0, 0, 0x20); //Instantiate Haar sensor with default ports and version v2.0 -// Haar haar1(0, 0, 0x20); //Instantiate Haar sensor with default ports and version v2.0 -// Haar haar2(0, 0, 0x20); //Instantiate Haar sensor with default ports and version v2.0 -SO421 apogeeO2(sdi12, 0, 0); //Instantiate O2 sensor with default ports and unknown version, pass over SDI12 Talon interface -SP421 apogeeSolar(sdi12, 0, 0); //Instantiate solar sensor with default ports and unknown version, pass over SDI12 Talon interface -// TEROS11 soil(sdi12, 0, 0); //Instantiate soil sensor with default ports and unknown version, pass over SDI12 Talon interface -TDR315H soil1(sdi12, 0, 0); //Instantiate soil sensor with default ports and unknown version, pass over SDI12 Talon interface -TDR315H soil2(sdi12, 0, 0); //Instantiate soil sensor with default ports and unknown version, pass over SDI12 Talon interface -TDR315H soil3(sdi12, 0, 0); //Instantiate soil sensor with default ports and unknown version, pass over SDI12 Talon interface -Hedorah gas(0, 0, 0x10); //Instantiate CO2 sensor with default ports and v1.0 hardware -// T9602 humidity(0, 0, 0x00); //Instantiate Telair T9602 with default ports and version v0.0 -LI710 et(sdi12, 0, 0); //Instantiate ET sensor with default ports and unknown version, pass over SDI12 Talon interface -BaroVue10 campPressure(sdi12, 0, 0x00); // Instantiate Barovue10 with default ports and v0.0 hardware - -const uint8_t numSensors = 7; //Number must match the number of objects defined in `sensors` array - -Sensor* const sensors[numSensors] = { - &fileSys, - &aux, - &i2c, - &sdi12, - &battery, - &logger, //Add sensors after this line - &et - // &haar, - // &soil1, - // &apogeeSolar, - - // &soil2, - // &soil3, - // &gas, - // &apogeeO2, -}; -/////////////////////////// END USER CONFIG ///////////////////////////////// - -namespace PinsIO { //For Kestrel v1.1 - constexpr uint16_t VUSB = 5; -} - -namespace PinsIOAlpha { - constexpr uint16_t I2C_EXT_EN = 10; - constexpr uint16_t SD_CD = 8; - constexpr uint16_t SD_EN = 12; - constexpr uint16_t AUX_EN = 15; - constexpr uint16_t CE = 11; - constexpr uint16_t LED_EN = 13; -} - -namespace PinsIOBeta { //For Kestrel v1.1 - constexpr uint16_t SEL1 = 0; - constexpr uint16_t SEL2 = 4; - constexpr uint16_t SEL3 = 8; - constexpr uint16_t SEL4 = 12; - constexpr uint16_t I2C_EN1 = 1; - constexpr uint16_t I2C_EN2 = 5; - constexpr uint16_t I2C_EN3 = 9; - constexpr uint16_t I2C_EN4 = 13; - constexpr uint16_t EN1 = 3; - constexpr uint16_t EN2 = 7; - constexpr uint16_t EN3 = 11; - constexpr uint16_t EN4 = 15; - constexpr uint16_t FAULT1 = 2; - constexpr uint16_t FAULT2 = 6; - constexpr uint16_t FAULT3 = 10; - constexpr uint16_t FAULT4 = 14; -} - - - -// SYSTEM_MODE(MANUAL); -SYSTEM_MODE(SEMI_AUTOMATIC); -// SYSTEM_MODE(AUTOMATIC); //DEBUG! -SYSTEM_THREAD(ENABLED); //USE FOR FASTER STARTUP -// SYSTEM_THREAD(DISABLED); -// SYSTEM_MODE(AUTOMATIC); -int detectTalons(String dummyStr = ""); -int detectSensors(String dummyStr = ""); - -String diagnostic = ""; -String errors = ""; -String metadata = ""; -String data = ""; - -void setup() { - configurePowerSave(desiredPowerSaveMode); //Setup power mode of the system - System.enableFeature(FEATURE_RESET_INFO); //DEBUG! - if(System.resetReason() != RESET_REASON_POWER_DOWN) { - //DEBUG! Set safe mode - Particle.connect(); //DEBUG! //If reset not caused by power switch, assume something bad happened, just connect to particle straight away - } - //////////// MANUAL POSITIONING ////////////////// - // talons[aux.getTalonPort()] = &aux; //Place talon objects at coresponding positions in array - // talons[aux1.getTalonPort()] = &aux1; - time_t startTime = millis(); - Particle.function("nodeID", setNodeID); - Particle.function("findSensors", detectSensors); - Particle.function("findTalons", detectTalons); - Particle.function("systemRestart", systemRestart); - Particle.function("takeSample", takeSample); - Particle.function("commandExe", commandExe); - Serial.begin(1000000); - waitFor(serialConnected, 10000); //DEBUG! Wait until serial starts sending or 10 seconds - Serial.print("RESET CAUSE: "); //DEBUG! - Serial.println(System.resetReason()); //DEBUG! - bool hasCriticalError = false; - bool hasError = false; - // logger.begin(Time.now(), hasCriticalError, hasError); //Needs to be called the first time with Particle time since I2C not yet initialized - logger.begin(0, hasCriticalError, hasError); //Called with 0 since time collection system has not been initialized - logger.setIndicatorState(IndicatorLight::ALL,IndicatorMode::INIT); - bool batState = logger.testForBat(); //Check if a battery is connected - logger.enableI2C_OB(false); - logger.enableI2C_External(true); //Connect to Gonk I2C port - logger.enableI2C_Global(true); - if(batState) battery.setIndicatorState(GonkIndicatorMode::SOLID); //Turn on charge indication LEDs during setup - else battery.setIndicatorState(GonkIndicatorMode::BLINKING); //If battery not switched on, set to blinking - fileSys.begin(0, hasCriticalError, hasError); //Initialzie, but do not attempt backhaul - if(hasCriticalError) { - Serial.println(getErrorString()); //Report current error codes - logger.setIndicatorState(IndicatorLight::STAT,IndicatorMode::ERROR); //Display error state if critical error is reported - } - else logger.setIndicatorState(IndicatorLight::STAT,IndicatorMode::PASS); //If no critical fault, switch STAT off - battery.begin(0, hasCriticalError, hasError); //Init final CORE element - // I2C_OnBoardEn(true); - // Wire.setClock(400000); //Confirm operation in fast mode - // Wire.begin(); - logger.enableI2C_Global(false); //Connect to internal bus - logger.enableI2C_OB(true); - ioAlpha.begin(); //RESTORE - ioBeta.begin(); //RESTORE - // ioBeta.pinMode(PinsIOBeta::SEL2, OUTPUT); //DEBUG - // ioBeta.digitalWrite(PinsIOBeta::SEL2, LOW); //DEBUG - ioAlpha.pinMode(PinsIOAlpha::LED_EN, OUTPUT); - ioAlpha.digitalWrite(PinsIOAlpha::LED_EN, LOW); //Turn on LED indicators - // logger.setIndicatorState(IndicatorLight::ALL,IndicatorMode::IDLE); - // waitFor(serialConnected, 10000); //DEBUG! Wait until serial starts sending or 10 seconds - if(Serial.available()) { - //COMMAND MODE! - logger.setIndicatorState(IndicatorLight::ALL,IndicatorMode::COMMAND); - systemConfig(); - } - logger.setIndicatorState(IndicatorLight::ALL,IndicatorMode::WAITING); - Particle.connect(); //Once passed attempted serial connect, try to connect to particle - - ////////// ADD INTERRUPTS! - // for(int i = 1; i <= Kestrel::numTalonPorts; i++) { //Iterate over ALL ports - // logger.enablePower(i, true); //Turn on all power by default - // logger.enablePower(i, false); //Toggle power to reset - // logger.enablePower(i, true); - // logger.enableData(i, false); //Turn off all data by default - // } - - detectTalons(); - detectSensors(); - - // I2C_OnBoardEn(false); - // I2C_GlobalEn(true); - - // bool hasCriticalError = false; - // bool hasError = false; - // logger.enableData(4, true); - // logger.enableI2C_OB(false); - // logger.enableI2C_Global(true); - // String initDiagnostic = aux.begin(Time.now(), hasCriticalError, hasError); - logger.updateLocation(true); //Force GPS update (hopfully connected) - String initDiagnostic = initSensors(); - Serial.print("DIAGNOSTIC: "); - Serial.println(initDiagnostic); - if(loggingMode == LogModes::NO_LOCAL) { - fileSys.writeToFRAM(initDiagnostic, DataType::Diagnostic, DestCodes::Particle); - logEvents(4, DestCodes::Particle); //Grab data log with metadata, no diagnostics - } - else { - fileSys.writeToFRAM(initDiagnostic, DataType::Diagnostic, DestCodes::Both); - logEvents(4, DestCodes::Both); //Grab data log with metadata, no diagnostics - } - // fileSys.writeToParticle(initDiagnostic, "diagnostic"); - // // logger.enableSD(true); - // fileSys.writeToSD(initDiagnostic, "Dummy.txt"); - - #ifndef RAPID_START //Only do this if not rapid starting - while((!Particle.connected() || logger.gps.getFixType() == 0) && (millis() - startTime) < maxConnectTime) { //Wait while at least one of the remote systems is not connected - if(Particle.connected()) { - logger.setIndicatorState(IndicatorLight::CELL, IndicatorMode::PASS); //If cell is connected, set to PASS state - if(WAIT_GPS == false) break; //If not told to wait for GPS, break out after cell is connected - } - if(logger.gps.getTimeValid() == true) { - if(logger.gps.getFixType() >= 2 && logger.gps.getFixType() <= 4 && logger.gps.getGnssFixOk()) { //If you get a 2D fix or better, pass GPS - logger.setIndicatorState(IndicatorLight::GPS, IndicatorMode::PASS); - } - else { - logger.setIndicatorState(IndicatorLight::GPS, IndicatorMode::PREPASS); //If time is good, set preliminary pass only - } - } - Serial.println("Wait for cell connect..."); //DEBUG! - delay(5000); //Wait 5 seconds between each check to not lock up the process //DEBUG! - } - #endif - - if(Particle.connected()) logger.setIndicatorState(IndicatorLight::CELL, IndicatorMode::PASS); //Catches connection of cell is second device to connect - else { - logger.setIndicatorState(IndicatorLight::CELL, IndicatorMode::ERROR); //If cell still not connected, display error - // Particle.disconnect(); //DEBUG! - } - if(logger.gps.getFixType() >= 2 && logger.gps.getFixType() <= 4 && logger.gps.getGnssFixOk()) { //Make fix report is in range and fix is OK - logger.setIndicatorState(IndicatorLight::GPS, IndicatorMode::PASS); //Catches connection of GPS is second device to connect - } - else { - logger.setIndicatorState(IndicatorLight::GPS, IndicatorMode::ERROR); //If GPS fails to connect after period, set back to error - } - fileSys.tryBackhaul(); //See if we can backhaul any unsent logs - - // fileSys.writeToFRAM(getDiagnosticString(1), DataType::Diagnostic, DestCodes::Both); //DEBUG! - // logEvents(3); //Grab data log with metadata //DEBUG! - fileSys.dumpFRAM(); //Backhaul this data right away - // Particle.publish("diagnostic", initDiagnostic); - - // logger.enableData(3, true); - // logger.enableI2C_OB(false); - // logger.enableI2C_Global(true); - // aux1.begin(Time.now(), hasCriticalError, hasError); - //FIX! RESPOND TO ERROR RESULTS! -} - -void loop() { - // aux.sleep(false); - static int count = 1; //Keep track of number of cycles - logger.wake(); //Wake up logger system - fileSys.wake(); //Wake up file handling - wakeSensors(); //Wake each sensor - if(System.millis() > indicatorTimeout) { - logger.setIndicatorState(IndicatorLight::ALL, IndicatorMode::NONE); //Turn LED indicators off if it has been longer than timeout since startup (use system.millis() which does not rollover) - logger.enableI2C_OB(false); - logger.enableI2C_External(true); //Connect to Gonk I2C port - logger.enableI2C_Global(true); - battery.setIndicatorState(GonkIndicatorMode::PUSH_BUTTON); //Turn off indicator lights on battery, return to push button control - logger.enableI2C_External(false); //Turn off external I2C - } - bool alarm = logger.waitUntilTimerDone(); //Wait until the timer period has finished //REPLACE FOR NON-SLEEP - // if(alarm) Serial.println("RTC Wakeup"); //DEBUG! - // else Serial.println("Timeout Wakeup"); //DEBUG! - // Serial.print("RAM, Start Log Events: "); //DEBUG! - // Serial.println(System.freeMemory()); //DEBUG! - logger.startTimer(logPeriod); //Start timer as soon done reading sensors //REPLACE FOR NON-SLEEP - switch(loggingMode) { - static uint64_t lastDiagnostic = System.millis(); - case (LogModes::PERFORMANCE): - logEvents(6, DestCodes::Both); - break; - case (LogModes::STANDARD): - if((count % 16) == 0) logEvents(3, DestCodes::Both); - else if((count % 8) == 0) logEvents(2, DestCodes::Both); - else if((count % 1) == 0) logEvents(1, DestCodes::Both); - break; - case (LogModes::BALANCED): - logEvents(7, DestCodes::Both); - if((System.millis() - lastDiagnostic) > balancedDiagnosticPeriod) { - logEvents(3, DestCodes::Both); //Do a full diagnostic and metadata report once an hour - lastDiagnostic = System.millis(); - } - break; - case (LogModes::NO_LOCAL): - if((count % 10) == 0) logEvents(3, DestCodes::Particle); - else if((count % 5) == 0) logEvents(2, DestCodes::Particle); - else if((count % 1) == 0) logEvents(1, DestCodes::Particle); - break; - default: - logEvents(1, DestCodes::Both); //If unknown configuration, use general call - // break; - } - - - // Serial.print("RAM, End Log Events: "); //DEBUG! - // Serial.println(System.freeMemory()); //DEBUG! - Serial.println("Log Done"); //DEBUG! - Serial.print("WDT Status: "); //DEBUG! - Serial.println(logger.feedWDT()); - sleepSensors(); - - - // Particle.publish("diagnostic", diagnostic); - // Particle.publish("error", errors); - // Particle.publish("data", data); - // Particle.publish("metadata", metadata); - - // Serial.print("DIAGNOSTIC: "); - // Serial.println(diagnostic); - // Serial.print("ERROR: "); - // Serial.println(errors); - // Serial.print("DATA: "); - // Serial.println(data); - // Serial.print("METADATA: "); - // Serial.println(metadata); - - // logger.enableI2C_OB(true); - // logger.enableI2C_Global(false); - // fileSys.writeToFRAM(diagnostic, "diagnostic", DestCodes::Particle); - - if((count % backhaulCount) == 0) { - Serial.println("BACKHAUL"); //DEBUG! - if(powerSaveMode >= PowerSaveModes::LOW_POWER) { - Particle.connect(); - waitFor(Particle.connected, 300000); //Wait up to 5 minutes to connect if using low power modes - } - logger.syncTime(); - fileSys.dumpFRAM(); //dump FRAM every Nth log - } - count++; - fileSys.sleep(); //Wait to sleep until after backhaul attempt - logger.sleep(); //Put system into sleep mode - - // SystemSleepConfiguration config; - // config.mode(SystemSleepMode::STOP) - // .network(NETWORK_INTERFACE_CELLULAR) - // .flag(SystemSleepFlag::WAIT_CLOUD) - // .duration(2min); - // System.sleep(config); -} - -void logEvents(uint8_t type, uint8_t destination) -{ - - // String diagnostic = ""; - // String errors = ""; - // String metadata = ""; - // String data = ""; - diagnostic = ""; - errors = ""; - metadata = ""; - data = ""; - Serial.print("LOG: "); //DEBUG! - Serial.println(type); - if(type == 0) { //Grab errors only - // data = getDataString(); - // diagnostic = getDiagnosticString(4); //DEBUG! RESTORE - errors = getErrorString(); //Get errors last to wait for error codes to be updated //DEBUG! RESTORE - // logger.enableI2C_OB(true); - // logger.enableI2C_Global(false); - Serial.println(errors); //DEBUG! - // Serial.println(data); //DEBUG! - // Serial.println(diagnostic); //DEBUG! - - if(errors.equals("") == false) { - // Serial.println("Write Errors to FRAM"); //DEBUG! - fileSys.writeToFRAM(errors, DataType::Error, destination); //Write value out only if errors are reported - } - // fileSys.writeToFRAM(data, DataType::Data, DestCodes::Both); - // fileSys.writeToFRAM(diagnostic, DataType::Diagnostic, DestCodes::Both); - } - if(type == 1) { - data = getDataString(); - diagnostic = getDiagnosticString(4); //DEBUG! RESTORE - errors = getErrorString(); //Get errors last to wait for error codes to be updated //DEBUG! RESTORE - // logger.enableI2C_OB(true); - // logger.enableI2C_Global(false); - Serial.println(errors); //DEBUG! - Serial.println(data); //DEBUG! - Serial.println(diagnostic); //DEBUG! - - if(errors.equals("") == false) { - // Serial.println("Write Errors to FRAM"); //DEBUG! - fileSys.writeToFRAM(errors, DataType::Error, destination); //Write value out only if errors are reported - } - fileSys.writeToFRAM(data, DataType::Data, destination); - fileSys.writeToFRAM(diagnostic, DataType::Diagnostic, destination); - } - else if(type == 2) { - data = getDataString(); - diagnostic = getDiagnosticString(3); - errors = getErrorString(); - // logger.enableI2C_OB(true); - // logger.enableI2C_Global(false); - if(errors.equals("") == false) fileSys.writeToFRAM(errors, DataType::Error, destination); //Write value out only if errors are reported - fileSys.writeToFRAM(data, DataType::Data, destination); - fileSys.writeToFRAM(diagnostic, DataType::Diagnostic, destination); - } - else if(type == 3) { - data = getDataString(); - diagnostic = getDiagnosticString(2); - metadata = getMetadataString(); - errors = getErrorString(); - // logger.enableI2C_OB(true); - // logger.enableI2C_Global(false); - if(errors.equals("") == false) fileSys.writeToFRAM(errors, DataType::Error, destination); //Write value out only if errors are reported - fileSys.writeToFRAM(data, DataType::Data, destination); - fileSys.writeToFRAM(diagnostic, DataType::Diagnostic, destination); - fileSys.writeToFRAM(metadata, DataType::Metadata, destination); - } - else if(type == 4) { //To be used on startup, don't grab diagnostics since init already got them - data = getDataString(); - // diagnostic = getDiagnosticString(2); - metadata = getMetadataString(); - errors = getErrorString(); - // logger.enableI2C_OB(true); - // logger.enableI2C_Global(false); - Serial.println(errors); //DEBUG! - Serial.println(data); //DEBUG - Serial.println(metadata); //DEBUG! - if(errors.equals("") == false) fileSys.writeToFRAM(errors, DataType::Error, destination); //Write value out only if errors are reported - fileSys.writeToFRAM(data, DataType::Data, destination); - // fileSys.writeToFRAM(diagnostic, DataType::Diagnostic, DestCodes::Both); - fileSys.writeToFRAM(metadata, DataType::Metadata, destination); - } - else if(type == 5) { //To be used on startup, don't grab diagnostics since init already got them - data = getDataString(); - diagnostic = getDiagnosticString(5); - // metadata = getMetadataString(); - errors = getErrorString(); - // logger.enableI2C_OB(true); - // logger.enableI2C_Global(false); - Serial.println(errors); //DEBUG! - Serial.println(data); //DEBUG - // Serial.println(metadata); //DEBUG! - if(errors.equals("") == false) fileSys.writeToFRAM(errors, DataType::Error, DestCodes::SD); //Write value out only if errors are reported - fileSys.writeToFRAM(data, DataType::Data, DestCodes::SD); - fileSys.writeToFRAM(diagnostic, DataType::Diagnostic, DestCodes::SD); - // fileSys.writeToFRAM(metadata, DataType::Metadata, DestCodes::Both); - } - else if(type == 6) { //Log ONLY data - fastest method - data = getDataString(); - // diagnostic = getDiagnosticString(5); - // metadata = getMetadataString(); - // errors = getErrorString(); - // logger.enableI2C_OB(true); - // logger.enableI2C_Global(false); - // Serial.println(errors); //DEBUG! - // Serial.println(data); //DEBUG - // Serial.println(metadata); //DEBUG! - // if(errors.equals("") == false) fileSys.writeToFRAM(errors, DataType::Error, DestCodes::SD); //Write value out only if errors are reported - fileSys.writeToFRAM(data, DataType::Data, destination); - // fileSys.writeToFRAM(diagnostic, DataType::Diagnostic, DestCodes::SD); - // fileSys.writeToFRAM(metadata, DataType::Metadata, DestCodes::Both); - } - else if(type == 7) { //Log data and error if there - data = getDataString(); - // diagnostic = getDiagnosticString(5); - // metadata = getMetadataString(); - errors = getErrorString(); - // logger.enableI2C_OB(true); - // logger.enableI2C_Global(false); - // Serial.println(errors); //DEBUG! - // Serial.println(data); //DEBUG - // Serial.println(metadata); //DEBUG! - if(errors.equals("") == false) fileSys.writeToFRAM(errors, DataType::Error, destination); //Write value out only if errors are reported - fileSys.writeToFRAM(data, DataType::Data, destination); - // fileSys.writeToFRAM(diagnostic, DataType::Diagnostic, DestCodes::SD); - // fileSys.writeToFRAM(metadata, DataType::Metadata, DestCodes::Both); - } - // switch(type) { - // case 1: //Standard, short interval, log - - // data = getDataString(); - // diagnostic = getDiagnosticString(4); //DEBUG! RESTORE - // errors = getErrorString(); //Get errors last to wait for error codes to be updated //DEBUG! RESTORE - // // logger.enableI2C_OB(true); - // // logger.enableI2C_Global(false); - // Serial.println(errors); //DEBUG! - // Serial.println(data); //DEBUG! - // Serial.println(diagnostic); //DEBUG! - - // if(errors.equals("") == false) { - // // Serial.println("Write Errors to FRAM"); //DEBUG! - // fileSys.writeToFRAM(errors, DataType::Error, DestCodes::Both); //Write value out only if errors are reported - // } - // fileSys.writeToFRAM(data, DataType::Data, DestCodes::Both); - // fileSys.writeToFRAM(diagnostic, DataType::Diagnostic, DestCodes::Both); - // break; - - // case 2: //Low period log with diagnostics - // data = getDataString(); - // diagnostic = getDiagnosticString(3); - // errors = getErrorString(); - // // logger.enableI2C_OB(true); - // // logger.enableI2C_Global(false); - // if(errors.equals("") == false) fileSys.writeToFRAM(errors, DataType::Error, DestCodes::Both); //Write value out only if errors are reported - // fileSys.writeToFRAM(data, DataType::Data, DestCodes::Both); - // fileSys.writeToFRAM(diagnostic, DataType::Diagnostic, DestCodes::Both); - // break; - - // case 3: //Daily log event with increased diagnostics and metadata - // data = getDataString(); - // diagnostic = getDiagnosticString(2); - // metadata = getMetadataString(); - // errors = getErrorString(); - // // logger.enableI2C_OB(true); - // // logger.enableI2C_Global(false); - // if(errors.equals("") == false) fileSys.writeToFRAM(errors, DataType::Error, DestCodes::Both); //Write value out only if errors are reported - // fileSys.writeToFRAM(data, DataType::Data, DestCodes::Both); - // fileSys.writeToFRAM(diagnostic, DataType::Diagnostic, DestCodes::Both); - // fileSys.writeToFRAM(metadata, DataType::Metadata, DestCodes::Both); - // break; - - // } - // diagnostic* = (const char*)NULL; - // data* = (const char*)NULL; - // metadata* = (const char*)NULL; - // errors* = (const char*)NULL; - -} -String getErrorString() -{ - unsigned long numErrors = 0; //Used to keep track of total errors across all devices - String errors = "{\"Error\":{"; - errors = errors + "\"Time\":" + logger.getTimeString() + ","; //Concatonate time - errors = errors + "\"Loc\":[" + logger.getPosLat() + "," + logger.getPosLong() + "," + logger.getPosAlt() + "," + logger.getPosTimeString() + "],"; - if(globalNodeID != "") errors = errors + "\"Node ID\":\"" + globalNodeID + "\","; //Concatonate node ID - else errors = errors + "\"Device ID\":\"" + System.deviceID() + "\","; //If node ID not initialized, use device ID - errors = errors + "\"Packet ID\":" + logger.getMessageID() + ","; //Concatonate unique packet hash - errors = errors + "\"NumDevices\":" + String(numSensors) + ","; //Concatonate number of sensors - errors = errors + "\"Devices\":["; - for(int i = 0; i < numSensors; i++) { - if(sensors[i]->totalErrors() > 0) { - numErrors = numErrors + sensors[i]->totalErrors(); //Increment the total error count - if(!errors.endsWith("[")) errors = errors + ","; //Only append if not first entry - errors = errors + "{" + sensors[i]->getErrors() + "}"; - } - } - errors = errors + "]}}"; //Close data - Serial.print("Num Errors: "); //DEBUG! - Serial.println(numErrors); - if(numErrors > 0) return errors; - else return ""; //Return null string if no errors reported -} - -String getDataString() -{ - String leader = "{\"Data\":{"; - leader = leader + "\"Time\":" + logger.getTimeString() + ","; //Concatonate time - leader = leader + "\"Loc\":[" + logger.getPosLat() + "," + logger.getPosLong() + "," + logger.getPosAlt() + "," + logger.getPosTimeString() + "],"; - if(globalNodeID != "") leader = leader + "\"Node ID\":\"" + globalNodeID + "\","; //Concatonate node ID - else leader = leader + "\"Device ID\":\"" + System.deviceID() + "\","; //If node ID not initialized, use device ID - leader = leader + "\"Packet ID\":" + logger.getMessageID() + ","; //Concatonate unique packet hash - leader = leader + "\"NumDevices\":" + String(numSensors) + ","; //Concatonate number of sensors - leader = leader + "\"Devices\":["; - const String closer = "]}}"; - String output = leader; - - uint8_t deviceCount = 0; //Used to keep track of how many devices have been appended - for(int i = 0; i < numSensors; i++) { - logger.disableDataAll(); //Turn off data to all ports, then just enable those needed - if(sensors[i]->sensorInterface != BusType::CORE && sensors[i]->getTalonPort() != 0) logger.enablePower(sensors[i]->getTalonPort(), true); //Turn on kestrel port for needed Talon, only if not core system and port is valid - if(sensors[i]->sensorInterface != BusType::CORE && sensors[i]->getTalonPort() != 0) logger.enableData(sensors[i]->getTalonPort(), true); //Turn on kestrel port for needed Talon, only if not core system and port is valid - logger.enableI2C_OB(false); - logger.enableI2C_Global(true); - bool dummy1; - bool dummy2; - - if(sensors[i]->getTalonPort() > 0 && talons[sensors[i]->getTalonPort() - 1]) { //DEBUG! REPALCE! - Serial.print("TALON CALL: "); //DEBUG! - Serial.println(sensors[i]->getTalonPort()); - logger.configTalonSense(); //Setup to allow for current testing - // talons[sensors[i]->getTalonPort() - 1]->begin(logger.getTime(), dummy1, dummy2); //DEBUG! Do only if talon is associated with sensor, and object exists - talons[sensors[i]->getTalonPort() - 1]->restart(); //DEBUG! Do only if talon is associated with sensor, and object exists - // logger.enableI2C_OB(false); //Return to isolation mode - // logger.enableI2C_Global(true); - } - if(sensors[i]->getSensorPort() > 0 && sensors[i]->getTalonPort() > 0) { //If not a Talon - Serial.print("Device "); //DEBUG! - Serial.print(i); - Serial.println(" is a sensor"); - talons[sensors[i]->getTalonPort() - 1]->disableDataAll(); //Turn off all data ports to start for the given Talon - // talons[sensors[i]->getTalonPort() - 1]->disablePowerAll(); //Turn off all power ports to start for the given Talon - // talons[sensors[i]->getTalonPort() - 1]->enablePower(sensors[i]->getSensorPort(), true); //Turn on power for the given port on the Talon - talons[sensors[i]->getTalonPort() - 1]->enableData(sensors[i]->getSensorPort(), true); //Turn on data for the given port on the Talon - // bool dummy1; - // bool dummy2; - // sensors[i]->begin(Time.now(), dummy1, dummy2); //DEBUG! - } - // delay(100); //DEBUG! - logger.enableI2C_OB(false); - logger.enableI2C_Global(true); - Serial.print("Data string from sensor "); //DEBUG! - Serial.print(i); - Serial.print(": "); - String val = sensors[i]->getData(logger.getTime()); - Serial.println(val); - if(!val.equals("")) { //Only append if not empty string - if(output.length() - output.lastIndexOf('\n') + val.length() + closer.length() + 1 < Kestrel::MAX_MESSAGE_LENGTH) { //Add +1 to account for comma appending, subtract any previous lines from count - if(deviceCount > 0) output = output + ","; //Add preceeding comma if not the first entry - output = output + "{" + val + "}"; //Append result - deviceCount++; - // if(i + 1 < numSensors) diagnostic = diagnostic + ","; //Only append if not last entry - } - else { - output = output + closer + "\n"; //End this packet - output = output + leader + "{" + val + "}"; //Start a new packet and add new payload - } - } - // if(!val.equals("")) { //Only append if real result - // if(deviceCount > 0) data = data + ","; //Preappend comma only if not first addition - // data = data + "{" + val + "}"; - // deviceCount++; - // // if(i + 1 < numSensors) metadata = metadata + ","; //Only append if not last entry - // } - Serial.print("Cumulative data string: "); //DEBUG! - Serial.println(output); //DEBUG! - // data = data + sensors[i]->getData(logger.getTime()); //DEBUG! REPLACE! - // if(i + 1 < numSensors) data = data + ","; //Only append if not last entry - if(sensors[i]->getSensorPort() > 0 && sensors[i]->getTalonPort() > 0) { - talons[sensors[i]->getTalonPort() - 1]->enableData(sensors[i]->getSensorPort(), false); //Turn off data for the given port on the Talon - // talons[sensors[i]->getTalonPort() - 1]->enablePower(sensors[i]->getSensorPort(), false); //Turn off power for the given port on the Talon //DEBUG! - } - } - output = output + "]}}"; //Close data - return output; -} - -String getDiagnosticString(uint8_t level) -{ - String leader = "{\"Diagnostic\":{"; - leader = leader + "\"Time\":" + logger.getTimeString() + ","; //Concatonate time - leader = leader + "\"Loc\":[" + logger.getPosLat() + "," + logger.getPosLong() + "," + logger.getPosAlt() + "," + logger.getPosTimeString() + "],"; - if(globalNodeID != "") leader = leader + "\"Node ID\":\"" + globalNodeID + "\","; //Concatonate node ID - else leader = leader + "\"Device ID\":\"" + System.deviceID() + "\","; //If node ID not initialized, use device ID - leader = leader + "\"Packet ID\":" + logger.getMessageID() + ","; //Concatonate unique packet hash - leader = leader + "\"NumDevices\":" + String(numSensors) + ",\"Level\":" + String(level) + ",\"Devices\":["; //Concatonate number of sensors and level - const String closer = "]}}"; - String output = leader; - - uint8_t deviceCount = 0; //Used to keep track of how many devices have been appended - for(int i = 0; i < numSensors; i++) { - logger.disableDataAll(); //Turn off data to all ports, then just enable those needed - if(sensors[i]->sensorInterface != BusType::CORE && sensors[i]->getTalonPort() != 0) logger.enablePower(sensors[i]->getTalonPort(), true); //Turn on kestrel port for needed Talon, only if not core system and port is valid - if(sensors[i]->sensorInterface != BusType::CORE && sensors[i]->getTalonPort() != 0) logger.enableData(sensors[i]->getTalonPort(), true); //Turn on kestrel port for needed Talon, only if not core system and port is valid - logger.enableI2C_OB(false); - logger.enableI2C_Global(true); - // if(!sensors[i]->isTalon()) { //If sensor is not Talon - if(sensors[i]->getSensorPort() > 0 && sensors[i]->getTalonPort() > 0) { //If a Talon is associated with the sensor, turn that port on - talons[sensors[i]->getTalonPort() - 1]->disableDataAll(); //Turn off all data on Talon - // talons[sensors[i]->getTalonPort() - 1]->enablePower(sensors[i]->getSensorPort(), true); //Turn on power for the given port on the Talon - talons[sensors[i]->getTalonPort() - 1]->enableData(sensors[i]->getSensorPort(), true); //Turn back on only port used - - } - - String diagnostic = sensors[i]->selfDiagnostic(level, logger.getTime()); - if(!diagnostic.equals("")) { //Only append if not empty string - if(output.length() - output.lastIndexOf('\n') + diagnostic.length() + closer.length() + 1 < Kestrel::MAX_MESSAGE_LENGTH) { //Add +1 to account for comma appending, subtract any previous lines from count - if(deviceCount > 0) output = output + ","; //Add preceeding comma if not the first entry - output = output + "{" + diagnostic + "}"; //Append result - deviceCount++; - // if(i + 1 < numSensors) diagnostic = diagnostic + ","; //Only append if not last entry - } - else { - output = output + closer + "\n"; //End this packet - output = output + leader + "{" + diagnostic + "}"; //Start a new packet and add new payload - } - } - - if(sensors[i]->getSensorPort() > 0 && sensors[i]->getTalonPort() > 0) { - talons[sensors[i]->getTalonPort() - 1]->enableData(sensors[i]->getSensorPort(), false); //Turn off data for the given port on the Talon - // talons[sensors[i]->getTalonPort() - 1]->enablePower(sensors[i]->getSensorPort(), false); //Turn off power for the given port on the Talon //DEBUG! - } - - } - output = output + closer; //Close diagnostic - return output; -} - -String getMetadataString() -{ - String leader = "{\"Metadata\":{"; - leader = leader + "\"Time\":" + logger.getTimeString() + ","; //Concatonate time - leader = leader + "\"Loc\":[" + logger.getPosLat() + "," + logger.getPosLong() + "," + logger.getPosAlt() + "," + logger.getPosTimeString() + "],"; - if(globalNodeID != "") leader = leader + "\"Node ID\":\"" + globalNodeID + "\","; //Concatonate node ID - else leader = leader + "\"Device ID\":\"" + System.deviceID() + "\","; //If node ID not initialized, use device ID - leader = leader + "\"Packet ID\":" + logger.getMessageID() + ","; //Concatonate unique packet hash - leader = leader + "\"NumDevices\":" + String(numSensors) + ","; //Concatonate number of sensors - leader = leader + "\"Devices\":["; - const String closer = "]}}"; - String output = leader; - - output = output + "{\"System\":{"; - // output = output + "\"DUMMY\":\"BLOODYMARYBLOODYMARYBLODDYMARY\","; - output = output + "\"Schema\":\"" + schemaVersion + "\","; - output = output + "\"Firm\":\"" + firmwareVersion + "\","; - output = output + "\"OS\":\"" + System.version() + "\","; - output = output + "\"ID\":\"" + System.deviceID() + "\","; - output = output + "\"Update\":" + String(logPeriod) + ","; - output = output + "\"Backhaul\":" + String(backhaulCount) + ","; - output = output + "\"LogMode\":" + String(loggingMode) + ","; - output = output + "\"Sleep\":" + String(powerSaveMode) + "}},"; - //FIX! Add support for device name - - uint8_t deviceCount = 0; //Used to keep track of how many devices have been appended - for(int i = 0; i < numSensors; i++) { - logger.disableDataAll(); //Turn off data to all ports, then just enable those needed - if(sensors[i]->sensorInterface != BusType::CORE && sensors[i]->getTalonPort() != 0) logger.enableData(sensors[i]->getTalonPort(), true); //Turn on data to required Talon port only if not core and port is valid - // if(!sensors[i]->isTalon()) { //If sensor is not Talon - if(sensors[i]->getSensorPort() > 0 && sensors[i]->getTalonPort() > 0) { //If a Talon is associated with the sensor, turn that port on - talons[sensors[i]->getTalonPort() - 1]->disableDataAll(); //Turn off all data on Talon - talons[sensors[i]->getTalonPort() - 1]->enableData(sensors[i]->getSensorPort(), true); //Turn back on only port used - } - // logger.enablePower(sensors[i]->getTalon(), true); //Turn on power to port - // logger.enableData(sensors[i]->getTalon(), true); //Turn on data to port - logger.enableI2C_OB(false); - logger.enableI2C_Global(true); - String val = sensors[i]->getMetadata(); - // metadata = metadata + sensors[i]->getMetadata(); - // if(!val.equals("")) { //Only append if real result - // if(deviceCount > 0) metadata = metadata + ","; //Preappend comma only if not first addition - // metadata = metadata + val; - // deviceCount++; - // // if(i + 1 < numSensors) metadata = metadata + ","; //Only append if not last entry - // } - if(!val.equals("")) { //Only append if not empty string - if(output.length() - output.lastIndexOf('\n') + val.length() + closer.length() + 1 < Kestrel::MAX_MESSAGE_LENGTH) { //Add +1 to account for comma appending, subtract any previous lines from count - if(deviceCount > 0) output = output + ","; //Add preceeding comma if not the first entry - output = output + "{" + val + "}"; //Append result - deviceCount++; - // if(i + 1 < numSensors) diagnostic = diagnostic + ","; //Only append if not last entry - } - else { - output = output + closer + "\n"; //End this packet - output = output + leader + "{" + val + "}"; //Start a new packet and add new payload - } - } - } - - output = output + closer; //Close metadata - return output; -} - -String initSensors() -{ - String leader = "{\"Diagnostic\":{"; - leader = leader + "\"Time\":" + logger.getTimeString() + ","; //Concatonate time - leader = leader + "\"Loc\":[" + logger.getPosLat() + "," + logger.getPosLong() + "," + logger.getPosAlt() + "," + logger.getPosTimeString() + "],"; - if(globalNodeID != "") leader = leader + "\"Node ID\":\"" + globalNodeID + "\","; //Concatonate node ID - else leader = leader + "\"Device ID\":\"" + System.deviceID() + "\","; //If node ID not initialized, use device ID - leader = leader + "\"Packet ID\":" + logger.getMessageID() + ","; //Concatonate unique packet hash - leader = leader + "\"NumDevices\":" + String(numSensors) + ",\"Devices\":["; //Concatonate number of sensors and level - - String closer = "]}}"; - String output = leader; - bool reportCriticalError = false; //Used to keep track of the global status of the error indications for all sensors - bool reportError = false; - bool missingSensor = false; - // output = output + "\"Devices\":["; - uint8_t deviceCount = 0; //Used to keep track of how many devices have been appended - for(int i = 0; i < numSensors; i++) { - logger.disableDataAll(); //Turn off data to all ports, then just enable those needed - if(sensors[i]->sensorInterface != BusType::CORE && sensors[i]->getTalonPort() != 0) logger.enableData(sensors[i]->getTalonPort(), true); //Turn on data to required Talon port only if not core and the port is valid - logger.enableI2C_OB(false); - logger.enableI2C_Global(true); - bool dummy1; - bool dummy2; - // if(!sensors[i]->isTalon()) { //If sensor is not Talon - logger.configTalonSense(); //Setup to allow for current testing - // if(sensors[i]->getTalonPort() > 0 && talons[sensors[i]->getTalonPort() - 1]) talons[sensors[i]->getTalonPort() - 1]->begin(logger.getTime(), dummy1, dummy2); //DEBUG! Do only if talon is associated with sensor, and object exists //DEBUG! REPLACE! - if(sensors[i]->getTalonPort() > 0 && talons[sensors[i]->getTalonPort() - 1]) talons[sensors[i]->getTalonPort() - 1]->restart(); //DEBUG! Do only if talon is associated with sensor, and object exists //DEBUG! REPLACE! - if(sensors[i]->getSensorPort() > 0 && sensors[i]->getTalonPort() > 0) { //If a Talon is associated with the sensor, turn that port on - talons[sensors[i]->getTalonPort() - 1]->disableDataAll(); //Turn off all data on Talon - // talons[sensors[i]->getTalonPort() - 1]->enablePower(sensors[i]->getSensorPort(), true); //Turn on power for the given port on the Talon - talons[sensors[i]->getTalonPort() - 1]->enableData(sensors[i]->getSensorPort(), true); //Turn back on only port used - - } - if(sensors[i]->getTalonPort() == 0 && sensors[i]->sensorInterface != BusType::CORE) { - missingSensor = true; //Set flag if any sensors not assigned to Talon and not a core sensor - Serial.print("Missing Sensor: "); //DEBUG! - Serial.print(i); - Serial.print("\t"); - Serial.println(sensors[i]->sensorInterface); - } - bool hasCriticalError = false; - bool hasError = false; - - String val; - if(sensors[i]->getTalonPort() > 0 && sensors[i]->getSensorPort() > 0) val = sensors[i]->begin(logger.getTime(), hasCriticalError, hasError); //If detected sensor, run begin - else if(sensors[i]->getTalonPort() > 0 && sensors[i]->getSensorPort() == 0 || sensors[i]->sensorInterface == BusType::CORE) val = sensors[i]->selfDiagnostic(2, logger.getTime()); //If sensor is a Talon or CORE type, run diagnostic, begin has already been run - if(hasError) reportError = true; //Set if any of them throw an error - if(hasCriticalError) reportCriticalError = true; //Set if any of them throw a critical error - if(!val.equals("")) { //Only append if not empty string - if(output.length() - output.lastIndexOf('\n') + val.length() + closer.length() + 1 < Kestrel::MAX_MESSAGE_LENGTH) { //Add +1 to account for comma appending, subtract any previous lines from count - if(deviceCount > 0) output = output + ","; //Add preceeding comma if not the first entry - output = output + "{" + val + "}"; //Append result - deviceCount++; - // if(i + 1 < numSensors) diagnostic = diagnostic + ","; //Only append if not last entry - } - else { - output = output + closer + "\n"; //End this packet - output = output + leader + "{" + val + "}"; //Start a new packet and add new payload - } - } - - } - if(missingSensor) logger.setIndicatorState(IndicatorLight::SENSORS, IndicatorMode::ERROR); - else logger.setIndicatorState(IndicatorLight::SENSORS, IndicatorMode::PASS); //If no errors are reported, set to pass state - //FIX! Replace! - // if(reportCriticalError) logger.setIndicatorState(IndicatorLight::SENSORS, IndicatorMode::ERROR_CRITICAL); - // else if(reportError) logger.setIndicatorState(IndicatorLight::SENSORS, IndicatorMode::ERROR); //Only set minimal error state if critical error is not thrown - // else logger.setIndicatorState(IndicatorLight::SENSORS, IndicatorMode::PASS); //If no errors are reported, set to pass state - - output = output + closer; //Close diagnostic - return output; -} - -void quickTalonShutdown() -{ - // Wire.beginTransmission(0x22); //Talk to I2C Talon - // Wire.write(0x48); //Point to pullup/pulldown select reg - // Wire.write(0xF0); //Set pins 1 - 4 as pulldown - // Wire.endTransmission(); - - // Wire.beginTransmission(0x22); //Talk to I2C Talon - // Wire.write(0x46); //Point to pullup/pulldown enable reg - // Wire.write(0x0F); //Enable pulldown on pins 1-4 - // Wire.endTransmission(); - - //////////// DEBUG! ///////////// - //// SET SDI-12 TALON First to eliminiate issue with power being applied to Apogee port - Wire.beginTransmission(0x25); //Talk to SDI12 Talon - Wire.write(0x02); //Point to output port - Wire.write(0x00); //Set pints 1 - 8 low - Wire.endTransmission(); - - Wire.beginTransmission(0x25); //Talk to SDI12 Talon - Wire.write(0x06); //Point to config port - Wire.write(0x00); //Set pins 1 - 8 as output - Wire.endTransmission(); - - Wire.beginTransmission(0x25); //Talk to SDI12 Talon - Wire.write(0x00); //Point to port reg - // Wire.write(0xF0); //Set pints 1 - 4 low - Wire.endTransmission(); - - Wire.requestFrom(0x25, 1); - Wire.read(); //Read back current value - - Wire.beginTransmission(0x22); //Talk to I2C Talon - Wire.write(0x06); //Point to config port - Wire.write(0xF0); //Set pins 1 - 4 as output - Wire.endTransmission(); - - Wire.beginTransmission(0x22); //Talk to I2C Talon - Wire.write(0x02); //Point to output port - Wire.write(0xF0); //Set pints 1 - 4 low - Wire.endTransmission(); - - Wire.beginTransmission(0x22); //Talk to I2C Talon - Wire.write(0x00); //Point to port reg - // Wire.write(0xF0); //Set pints 1 - 4 low - Wire.endTransmission(); - - Wire.requestFrom(0x22, 1); - Wire.read(); //Read back current value - /////////// END DEBUG! ///////////// - - // Wire.beginTransmission(0x25); //Talk to SDI-12 Talon - // Wire.write(0x48); //Point to pullup/pulldown select reg - // Wire.write(0xF0); //Set pins 1 - 4 as pulldown - // Wire.endTransmission(); - - // Wire.beginTransmission(0x25); //Talk to SDI-12 Talon - // Wire.write(0x46); //Point to pullup/pulldown enable reg - // Wire.write(0x0F); //Enable pulldown on pins 1-4 - // Wire.endTransmission(); - - -} - -bool serialConnected() //Used to check if a monitor has been connected at the begining of operation for override control -{ - if(Serial.available() > 0) return true; - else return false; -} - -void systemConfig() -{ - Serial.println("HALT: Entered Command Mode - Here be Dragons"); //DEBUG! - static int ReadLength = 0; - String ReadString; - char ReadArray[25] = {0}; - while(1) { - if(Serial.available() > 0) { - char Input = Serial.read(); - if(Input != '\r') { //Wait for return - ReadArray[ReadLength] = Input; - ReadLength++; - } - if(Input == '\r') { - ReadString = String(ReadArray); - ReadString.trim(); - memset(ReadArray, 0, sizeof(ReadArray)); - ReadLength = 0; - - Serial.print(">"); - Serial.println(ReadString); //Echo back to serial monitor - - if(ReadString.equalsIgnoreCase("Erase FRAM")) { - fileSys.eraseFRAM(); - Serial.println("\tDone"); - } - - if(ReadString.equalsIgnoreCase("Set Accel Zero")) { - logger.zeroAccel(); - Serial.println("\tDone"); - } - - if(ReadString.equalsIgnoreCase("Clear Accel Zero")) { - logger.zeroAccel(true); - Serial.println("\tDone"); - } - - if(ReadString.equalsIgnoreCase("Exit")) { - return; //Exit the setup function - } - } - } - } -} - -int sleepSensors() -{ - if(powerSaveMode > PowerSaveModes::PERFORMANCE) { //Only turn off is power save requested - Serial.println("BEGIN SENSOR SLEEP"); //DEBUG! - for(int s = 0; s < numSensors; s++) { //Iterate over all sensors objects - //If not set to keep power on and Talon is assocated, power down sensor. Ignore if core device, we will handle these seperately - if(sensors[s]->keepPowered == false && sensors[s]->sensorInterface != BusType::CORE && sensors[s]->getTalonPort() > 0 && sensors[s]->getTalonPort() < numTalons) { - Serial.print("Power Down Sensor "); //DEBUG! - Serial.print(s + 1); - Serial.print(","); - Serial.println(sensors[s]->getTalonPort()); - talons[sensors[s]->getTalonPort() - 1]->enablePower(sensors[s]->getSensorPort(), false); //Turn off power for any sensor which does not need to be kept powered - } - else if(sensors[s]->sensorInterface != BusType::CORE && sensors[s]->getTalonPort() > 0 && sensors[s]->getTalonPort() < numTalons){ //If sensor has a position and is not core, but keepPowered is true, run sleep routine - Serial.print("Sleep Sensor "); //DEBUG! - Serial.println(s + 1); - sensors[s]->sleep(); //If not powered down, run sleep protocol - } - else if(sensors[s]->sensorInterface == BusType::CORE) { - Serial.print("Sensor "); //DEBUG! - Serial.print(s + 1); - Serial.println(" is core, do nothing"); - } - else { - Serial.print("Sensor "); //DEBUG! - Serial.print(s + 1); - Serial.println(" not detected, do nothing"); - } - } - - for(int t = 0; t < Kestrel::numTalonPorts; t++) { //Iterate over all talon objects - if(talons[t] && talons[t]->keepPowered == false) { //If NO sensors on a given Talon require it to be kept powered, shut the whole thing down - Serial.print("Power Down Talon "); //DEBUG! - Serial.println(talons[t]->getTalonPort()); - logger.enablePower(talons[t]->getTalonPort(), false); //Turn off power to given port - } - else if(!talons[t]) { - Serial.print("Power Down Empty Port "); //DEBUG! - Serial.println(t + 1); - logger.enablePower(t + 1, false); //Turn off power to unused port - } - } - } - - return 0; //DEBUG! -} - -int wakeSensors() -{ - logger.enableI2C_Global(true); //Connect to external bus to talk to sensors/Talons - logger.enableI2C_OB(false); - logger.disableDataAll(); //Turn off all data to start - for(int p = 1; p <= Kestrel::numTalonPorts; p++) logger.enablePower(p, true); //Turn power back on to all Kestrel ports - for(int t = 0; t < Kestrel::numTalonPorts; t++) { - if(talons[t] && talons[t]->getTalonPort() != 0) { - logger.enableData(talons[t]->getTalonPort(), true); //Turn on data for given port - talons[t]->restart(); //Restart all Talons, this turns on all ports it can - logger.enableData(talons[t]->getTalonPort(), false); //Turn data back off for given port - } - } - for(int s = 0; s < numSensors; s++) { - if(sensors[s]->getTalonPort() != 0) { - logger.enableData(sensors[s]->getTalonPort(), true); //Turn on data for given port - sensors[s]->wake(); //Wake each sensor - logger.enableData(sensors[s]->getTalonPort(), false); //Turn data back off for given port - } - } - return 0; //DEBUG! -} - -int detectTalons(String dummyStr) -{ - ////////////// AUTO TALON DETECTION /////////////////////// - // talons[0] = &aux; //Place talon objects at arbitrary positions in array - // talons[1] = &aux1; - // talons[2] = &i2c; - - // bool hasCriticalError = false; - // bool hasError = false; - // for(int i = 0; i < numTalons; i++) { //Initialize all Talons //DEBUG! - // talons[i]->begin(Time.now(), hasCriticalError, hasError); - // } - // logger.enableI2C_External(false); //Turn off connection to - logger.enableI2C_Global(true); //Connect to external bus to talk to sensors/Talons - logger.enableI2C_OB(false); - for(int port = 1; port <= Kestrel::numTalonPorts; port++) { //Test all ports - logger.enableData(port, true); //Turn on specific channel - logger.enablePower(port, false); - logger.enablePower(port, true); - // logger.enableAuxPower(true); - // logger.enableI2C_Global(true); - // logger.enableI2C_OB(false); - // delay(1);//DEBUG! - unsigned long localTime = millis(); - int error = 0; - while((millis() - localTime) < 10) { //Wait up to 10ms for connection to be established - Wire.beginTransmission(0); - error = Wire.endTransmission(); - if(error == 0) break; //Exit loop once we are able to connect with Talon - } - quickTalonShutdown(); //Quickly disables power to all ports on I2C or SDI talons, this is a kluge - for(int t = 0; t < numTalons; t++) { //Iterate over all Talon objects - if(talonsToTest[t]->getTalonPort() == 0) { //If port not already specified - Serial.print("New Talon: "); - Serial.println(t); - // logger.enableAuxPower(false); //Turn aux power off, then configure port to on, then switch aux power back for faster response - // logger.enablePower(port, true); //Toggle power just before testing to get result within 10ms - // logger.enablePower(port, false); - if(talonsToTest[t]->isPresent()) { //Test if that Talon is present, if it is, configure the port - talonsToTest[t]->setTalonPort(port); - talons[port - 1] = talonsToTest[t]; //Copy test talon object to index location in talons array - Serial.print("Talon Port Result "); //DEBUG! - Serial.print(t); - Serial.print(": "); - Serial.println(talonsToTest[t]->getTalonPort()); - break; //Exit the interation after the first one tests positive - } - } - } - logger.enableData(port, false); //Turn port back off - } - // talons[aux.getTalonPort() - 1] = &aux; //Place talon objects at detected positions in array - // talons[aux1.getTalonPort() - 1] = &aux1; - // talons[i2c.getTalonPort() - 1] = &i2c; - bool dummy; - bool dummy1; - for(int i = 0; i < Kestrel::numTalonPorts - 1; i++) { - if(talons[i] && talons[i]->getTalonPort() > 0) { - Serial.print("BEGIN TALON: "); //DEBUG! - Serial.print(talons[i]->getTalonPort()); - Serial.print(","); - Serial.println(i); - if(talons[i]->talonInterface == BusType::SDI12) { - Serial.println("SET FOR SDI12 SEL"); //DEBUG! - logger.setDirection(talons[i]->getTalonPort(), HIGH); //If the talon is an SDI12 interface type, set port to use serial interface - } - else if(talons[i]->talonInterface != BusType::CORE) logger.setDirection(talons[i]->getTalonPort(), LOW); //Otherwise set talon to use GPIO interface, unless bus type is core, in which case ignore it - logger.enablePower(i + 1, true); //Turn on specific channel - logger.enableData(i + 1, true); - if(logger.getFault(talons[i]->getTalonPort())) { //Only toggle power if there is a fault on that Talon line - logger.enablePower(i + 1, true); //Toggle power just before testing to get result within 10ms - logger.enablePower(i + 1, false); - logger.enablePower(i + 1, true); - } - - logger.configTalonSense(); //Setup to allow for current testing - // Serial.println("TALON SENSE CONFIG DONE"); //DEBUG! - // Serial.flush(); //DEBUG! - // logger.enableI2C_Global(true); - // logger.enableI2C_OB(false); - // talons[i]->begin(Time.now(), dummy, dummy1); //If Talon object exists and port has been assigned, initialize it //DEBUG! - talons[i]->begin(logger.getTime(), dummy, dummy1); //If Talon object exists and port has been assigned, initialize it //REPLACE getTime! - // talons[i]->begin(0, dummy, dummy1); //If Talon object exists and port has been assigned, initialize it //REPLACE getTime! - // Serial.println("TALON BEGIN DONE"); //DEBUG! - // Serial.flush(); //DEBUG! - // delay(10000); //DEBUG! - logger.enableData(i + 1, false); //Turn data back off to prevent conflict - // Serial.println("ENABLE DATA DONE"); //DEBUG! - // Serial.flush(); //DEBUG! - // delay(10000); //DEBUG! - } - } - return 0; //DEBUG! -} - -int detectSensors(String dummyStr) -{ - /////////////// SENSOR AUTO DETECTION ////////////////////// - for(int t = 0; t < Kestrel::numTalonPorts; t++) { //Iterate over each Talon - // Serial.println(talons[t]->talonInterface); //DEBUG! - // Serial.print("DETECT ON TALON: "); //DEBUG! - // Serial.println(t); - // Serial.flush(); - // if(talons[t]) { - // delay(5000); - // Serial.println("TALON EXISTS"); //DEBUG! - // Serial.flush(); - // } - // else { - // delay(5000); - // Serial.println("TALON NOT EXISTS"); //DEBUG! - // Serial.flush(); - // } - // delay(5000); - // if(talons[t]->talonInterface != BusType::NONE) { - // delay(5000); - // Serial.println("TALON NOT NONE"); //DEBUG! - // Serial.flush(); - // } - // else { - // delay(5000); - // Serial.println("TALON NONE"); //DEBUG! - // Serial.flush(); - // } - // delay(10000); //DEBUG! - // Serial.println(talons[t]->talonInterface); //DEBUG! - if(talons[t] && talons[t]->talonInterface != BusType::NONE && talons[t]->getTalonPort() != 0) { //Only proceed if Talon has a bus which can be iterated over, and the talon in question exists and has been detected - logger.enableData(talons[t]->getTalonPort(), true); //Turn on specific channel - // logger.enableI2C_Global(true); - // logger.enableI2C_OB(false); - talons[t]->disableDataAll(); //Turn off all data ports on Talon - for(int p = 1; p <= talons[t]->getNumPorts(); p++) { //Iterate over each port on given Talon - // talons[t]->enablePower(p, true); //Turn data and power on for specific channel - talons[t]->enableData(p, true); - delay(10); //Wait to make sure sensor is responsive after power up command - Serial.print("Testing Port: "); //DEBUG! - Serial.print(t + 1); - Serial.print(","); - Serial.println(p); - for(int s = 0; s < numSensors; s++) { //Iterate over all sensors objects - if(sensors[s]->getTalonPort() == 0 && talons[t]->talonInterface == sensors[s]->sensorInterface) { //If Talon not already specified AND sensor bus is compatible with Talon bus - Serial.print("Test Sensor: "); //DEBUG! - Serial.println(s); - if(sensors[s]->isPresent()) { //Test if that sensor is present, if it is, configure the port - sensors[s]->setTalonPort(t + 1); - sensors[s]->setSensorPort(p); - if(sensors[s]->keepPowered == true) talons[sensors[s]->getTalonPort() - 1]->keepPowered = true; //If any of the sensors on a Talon require power, set the flag for the Talon - Serial.print("Sensor Found:\n\t"); //DEBUG! - Serial.println(sensors[s]->getTalonPort()); - Serial.print('\t'); - Serial.println(sensors[s]->getSensorPort()); - // Serial.print("Talon Port Result "); //DEBUG! - // Serial.print(t); - // Serial.print(": "); - // Serial.println(talons[t]->getTalonPort()); - // talons[t]->enableData(p, false); - break; //Exit the interation after the first sensor tests positive - } - delay(10); //Wait in between sensor calls - // talons[t]->enableData(p, false); - } - } - talons[t]->enableData(p, false); //Turn data back off when done - } - logger.enableData(talons[t]->getTalonPort(), false); //Turn port back off - } - // Serial.print("NEXT TALON"); //DEBUG! - // Serial.flush(); - } - return 0; //DEBUG! -} - -int setNodeID(String nodeID) -{ - if(nodeID.length() > 8 || nodeID.length() < 0) return -1; //Return failure if string is not valid - else { - globalNodeID = nodeID; //If string passed in is valid, copy it to the global value - return 0; - } -} - -int takeSample(String dummy) -{ - logger.wake(); //Wake logger in case it was sleeping - wakeSensors(); //Wake up sensors from sleep - if(dummy == "true") { //If told to use backhaul, use normal FRAM method - fileSys.writeToFRAM(getDataString(), DataType::Data, DestCodes::Both); - fileSys.dumpFRAM(); //Dump data - } - else fileSys.writeToParticle(getDataString(), "data/v2"); //Otherwise fast return - sleepSensors(); // - logger.sleep(); - return 1; -} - -int commandExe(String command) -{ - if(command == "300") { - logger.releaseWDT(); - return 1; //DEBUG! - } - if(command == "102") { - logger.wake(); //Wake logger in case it was sleeping - wakeSensors(); //Wake up sensors from sleep - fileSys.writeToParticle(getDiagnosticString(2), "diagnostic/v2"); - sleepSensors(); // - logger.sleep(); - return 1; //DEBUG! - } - if(command == "103") { - logger.wake(); //Wake logger in case it was sleeping - wakeSensors(); //Wake up sensors from sleep - fileSys.writeToParticle(getDiagnosticString(3), "diagnostic/v2"); - sleepSensors(); // - logger.sleep(); - return 1; //DEBUG! - } - if(command == "104") { - logger.wake(); //Wake logger in case it was sleeping - wakeSensors(); //Wake up sensors from sleep - fileSys.writeToParticle(getDiagnosticString(4), "diagnostic/v2"); - sleepSensors(); // - logger.sleep(); - return 1; //DEBUG! - } - if(command == "111") { - logger.wake(); //Wake logger in case it was sleeping - wakeSensors(); //Wake up sensors from sleep - fileSys.writeToParticle(getDataString(), "data/v2"); - sleepSensors(); // - logger.sleep(); - return 1; //DEBUG! - } - if(command == "120") { - fileSys.writeToParticle(getErrorString(), "error/v2"); - return 1; //DEBUG! - } - if(command == "130") { - logger.wake(); //Wake logger in case it was sleeping - wakeSensors(); //Wake up sensors from sleep - fileSys.writeToParticle(getMetadataString(), "metadata/v2"); - sleepSensors(); // - logger.sleep(); - return 1; //DEBUG! - } - if(command == "401") { - fileSys.wake(); - fileSys.dumpFRAM(); - fileSys.sleep(); - return 1; - } - if(command == "410") { - fileSys.wake(); - fileSys.eraseFRAM(); //Clear FRAM and start over - fileSys.sleep(); - return 1; //DEBUG! - } - else { - return -1; //Return unknown command - } -} - -int systemRestart(String resetType) -{ - if(resetType.equalsIgnoreCase("hard")) System.reset(RESET_NO_WAIT); //Perform a hard reset - else System.reset(); //Attempt to inform cloud of a reset first - return 1; -} - -int configurePowerSave(int desiredPowerSaveMode) -{ - powerSaveMode = desiredPowerSaveMode; //Configure global flag - for(int s = 0; s < numSensors; s++) { //Iterate over all sensors objects - sensors[s]->powerSaveMode = desiredPowerSaveMode; //Set power save mode for all sensors - } - - for(int t = 0; t < numTalons; t++) { //Iterate over all talon objects - talonsToTest[t]->powerSaveMode = desiredPowerSaveMode; //Set power save mode for all talons - } - return 0; //DEBUG! -} \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..af8e3f2 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,62 @@ +# FlightControl_Demo/test/CMakeLists.txt +# Add GoogleTest +add_subdirectory(external/googletest) + +# Define the TESTING preprocessor macro for all test builds +add_compile_definitions(TESTING) + +# IMPORTANT: Make sure mocks are included first to override system headers +include_directories(BEFORE mocks) + +# Include FFF headers +include_directories(external/fff) + +# Include Google Test headers +include_directories(external/googletest/googletest/include) + +# Create a mocks library +add_library(mocks STATIC + mocks/MockWire.cpp + mocks/MockArduino.cpp + mocks/MockPCAL9535A.cpp + mocks/MockMCP79412.cpp + mocks/MockSPI.cpp + mocks/MockPCA9634.cpp + mocks/MockSHT4x.cpp + mocks/MockVEML3328.cpp + mocks/MockPAC1934.cpp + mocks/MockMXC6655.cpp + mocks/MockBMA456.cpp + mocks/MockGNSS.cpp +) + +# Specify test executable +add_executable(unit_tests + # Main test runner + main.cpp + + # Kestrel tests + unit/Driver_-_Kestrel/Driver_-_KestrelSetupTests.cpp + unit/Driver_-_Kestrel/Driver_-_KestrelFunctionTests.cpp +) + +# Link against mocks and GoogleTest +target_link_libraries(unit_tests mocks gtest gtest_main) + +# Find all driver directories and add them to the include path +file(GLOB DRIVER_DIRS ${CMAKE_SOURCE_DIR}/lib/*/src) + +# Add header include directories - mock directory MUST be first +target_include_directories(mocks BEFORE PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/mocks # Mocks FIRST + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/src + ${DRIVER_DIRS} # Add all driver directories +) + +target_include_directories(unit_tests BEFORE PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/mocks # Mocks FIRST + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/src + ${DRIVER_DIRS} # Add all driver directories +) \ No newline at end of file diff --git a/test/external/fff b/test/external/fff new file mode 160000 index 0000000..5111c61 --- /dev/null +++ b/test/external/fff @@ -0,0 +1 @@ +Subproject commit 5111c61e1ef7848e3afd3550044a8cf4405f4199 diff --git a/test/external/googletest b/test/external/googletest new file mode 160000 index 0000000..2ae29b5 --- /dev/null +++ b/test/external/googletest @@ -0,0 +1 @@ +Subproject commit 2ae29b52fdff88c52fef655fa0d245fc514ca35b diff --git a/test/main.cpp b/test/main.cpp new file mode 100644 index 0000000..ec03b1f --- /dev/null +++ b/test/main.cpp @@ -0,0 +1,10 @@ +// FlightControl_Demo/test/main.cpp +#include "gtest/gtest.h" +#include "fff.h" + +DEFINE_FFF_GLOBALS; + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/test/mocks/Arduino.h b/test/mocks/Arduino.h new file mode 100644 index 0000000..371a6c9 --- /dev/null +++ b/test/mocks/Arduino.h @@ -0,0 +1,29 @@ +#pragma once + +// Include our Arduino mocks +#include "MockArduino.h" + +// Arduino standard libraries +#include +#include +#include + +// Forward declarations for common Arduino types and functions +// This will silence most Arduino.h include errors +typedef uint8_t byte; + +class HardwareSerial { +public: + void begin(unsigned long baud) {} + int available() { return 0; } + int read() { return -1; } + void print(const char* str) {} + void print(int val) {} + void println(const char* str) {} + void println(int val) {} +}; + +extern HardwareSerial Serial1; +extern HardwareSerial Serial2; + +// Add more Arduino types/functions as needed \ No newline at end of file diff --git a/test/mocks/MockArduino.cpp b/test/mocks/MockArduino.cpp new file mode 100644 index 0000000..a3ebc20 --- /dev/null +++ b/test/mocks/MockArduino.cpp @@ -0,0 +1,31 @@ +// test/mocks/MockArduino.cpp +#include "MockArduino.h" + +// Define fake functions +DEFINE_FAKE_VOID_FUNC(pinMode, uint8_t, uint8_t); +DEFINE_FAKE_VOID_FUNC(digitalWrite, uint8_t, uint8_t); +DEFINE_FAKE_VALUE_FUNC(int, digitalRead, uint8_t); +DEFINE_FAKE_VALUE_FUNC(int, analogRead, uint8_t); +DEFINE_FAKE_VOID_FUNC(analogWrite, uint8_t, int); + +// Time functions +DEFINE_FAKE_VALUE_FUNC(unsigned long, millis); +DEFINE_FAKE_VALUE_FUNC(unsigned long, micros); +DEFINE_FAKE_VOID_FUNC(delay, unsigned long); +DEFINE_FAKE_VOID_FUNC(delayMicroseconds, unsigned int); + +// Misc +DEFINE_FAKE_VOID_FUNC(randomSeed, unsigned long); +DEFINE_FAKE_VALUE_FUNC(long, random, long, long); + +// Serial functions +DEFINE_FAKE_VOID_FUNC(Serial_begin, unsigned long); +DEFINE_FAKE_VALUE_FUNC(int, Serial_available); +DEFINE_FAKE_VALUE_FUNC(int, Serial_read); +DEFINE_FAKE_VOID_FUNC(Serial_print_s, const char*); +DEFINE_FAKE_VOID_FUNC(Serial_print_i, int); +DEFINE_FAKE_VOID_FUNC(Serial_println_s, const char*); +DEFINE_FAKE_VOID_FUNC(Serial_println_i, int); + +// Define the global Serial instance +MockSerial Serial; \ No newline at end of file diff --git a/test/mocks/MockArduino.h b/test/mocks/MockArduino.h new file mode 100644 index 0000000..4e36f5a --- /dev/null +++ b/test/mocks/MockArduino.h @@ -0,0 +1,89 @@ +// test/mocks/MockArduino.h +#pragma once + +#include +#include "fff.h" + +// Define Arduino pin modes +#define INPUT 0 +#define OUTPUT 1 +#define INPUT_PULLUP 2 +#define INPUT_PULLDOWN 3 + +// Define pin values +#define HIGH 1 +#define LOW 0 + +// Define common Arduino pins +#define A0 14 +#define A1 15 +#define A2 16 +#define A3 17 +#define A4 18 +#define A5 19 +#define A6 20 +#define A7 21 +#define D0 0 +#define D1 1 +#define D2 2 +#define D3 3 +#define D4 4 +#define D5 5 +#define D6 6 +#define D7 7 +#define D8 8 +#define D9 9 +#define D10 10 +#define D11 11 +#define D12 12 +#define D13 13 +#define D14 14 +#define D15 15 +#define D16 16 +#define D17 17 +#define D18 18 +#define D19 19 +#define D20 20 +#define D21 21 +#define D22 22 +#define D23 23 + +// Digital and Analog I/O +DECLARE_FAKE_VOID_FUNC(pinMode, uint8_t, uint8_t); +DECLARE_FAKE_VOID_FUNC(digitalWrite, uint8_t, uint8_t); +DECLARE_FAKE_VALUE_FUNC(int, digitalRead, uint8_t); +DECLARE_FAKE_VALUE_FUNC(int, analogRead, uint8_t); +DECLARE_FAKE_VOID_FUNC(analogWrite, uint8_t, int); + +// Time functions +DECLARE_FAKE_VALUE_FUNC(unsigned long, millis); +DECLARE_FAKE_VALUE_FUNC(unsigned long, micros); +DECLARE_FAKE_VOID_FUNC(delay, unsigned long); +DECLARE_FAKE_VOID_FUNC(delayMicroseconds, unsigned int); + +// Misc +DECLARE_FAKE_VOID_FUNC(randomSeed, unsigned long); +DECLARE_FAKE_VALUE_FUNC(long, random, long, long); + +// Forward declarations for Serial functions +DECLARE_FAKE_VOID_FUNC(Serial_begin, unsigned long); +DECLARE_FAKE_VALUE_FUNC(int, Serial_available); +DECLARE_FAKE_VALUE_FUNC(int, Serial_read); +DECLARE_FAKE_VOID_FUNC(Serial_print_s, const char*); +DECLARE_FAKE_VOID_FUNC(Serial_print_i, int); +DECLARE_FAKE_VOID_FUNC(Serial_println_s, const char*); +DECLARE_FAKE_VOID_FUNC(Serial_println_i, int); + +// Mock class for the Serial object +class MockSerial { +public: + void begin(unsigned long baud) { Serial_begin(baud); } + int available() { return Serial_available(); } + int read() { return Serial_read(); } + void print(const char* str) { Serial_print_s(str); } + void print(int val) { Serial_print_i(val); } + void println(const char* str) { Serial_println_s(str); } + void println(int val) { Serial_println_i(val); } +}; + +extern MockSerial Serial; \ No newline at end of file diff --git a/test/mocks/MockBMA456.cpp b/test/mocks/MockBMA456.cpp new file mode 100644 index 0000000..66d38e2 --- /dev/null +++ b/test/mocks/MockBMA456.cpp @@ -0,0 +1,11 @@ +#include "MockBMA456.h" + +// Define fake functions for BMA456 +DEFINE_FAKE_VALUE_FUNC(bool, BMA456_begin); +DEFINE_FAKE_VALUE_FUNC(bool, BMA456_readAcceleration, float*, float*, float*); +DEFINE_FAKE_VALUE_FUNC(bool, BMA456_enableStepCounter, bool); +DEFINE_FAKE_VALUE_FUNC(uint32_t, BMA456_getStepCount); +DEFINE_FAKE_VALUE_FUNC(bool, BMA456_resetStepCounter); + +// Initialize the global object +BMA456 accel_bma456; \ No newline at end of file diff --git a/test/mocks/MockBMA456.h b/test/mocks/MockBMA456.h new file mode 100644 index 0000000..40549fa --- /dev/null +++ b/test/mocks/MockBMA456.h @@ -0,0 +1,37 @@ +#pragma once + +#include "fff.h" +#include + +// Declare fake functions for BMA456 accelerometer +DECLARE_FAKE_VALUE_FUNC(bool, BMA456_begin); +DECLARE_FAKE_VALUE_FUNC(bool, BMA456_readAcceleration, float*, float*, float*); +DECLARE_FAKE_VALUE_FUNC(bool, BMA456_enableStepCounter, bool); +DECLARE_FAKE_VALUE_FUNC(uint32_t, BMA456_getStepCount); +DECLARE_FAKE_VALUE_FUNC(bool, BMA456_resetStepCounter); + +// Mock class for BMA456 via arduino_bma456 +class BMA456 { +public: + // Default mock values + float x_acceleration = 0.0f; + float y_acceleration = 0.0f; + float z_acceleration = 1.0f; // Default to 1G in Z direction (earth gravity) + uint32_t step_count = 0; + + bool begin() { return BMA456_begin(); } + + bool readAcceleration(float* x, float* y, float* z) { + *x = x_acceleration; + *y = y_acceleration; + *z = z_acceleration; + return BMA456_readAcceleration(x, y, z); + } + + bool enableStepCounter(bool enable) { return BMA456_enableStepCounter(enable); } + uint32_t getStepCount() { return BMA456_getStepCount(); } + bool resetStepCounter() { return BMA456_resetStepCounter(); } +}; + +// This simulates the global object provided by the arduino_bma456 library +extern BMA456 accel_bma456; \ No newline at end of file diff --git a/test/mocks/MockGNSS.cpp b/test/mocks/MockGNSS.cpp new file mode 100644 index 0000000..4d88aa9 --- /dev/null +++ b/test/mocks/MockGNSS.cpp @@ -0,0 +1,18 @@ +#include "MockGNSS.h" + +// Define fake functions for GNSS +DEFINE_FAKE_VALUE_FUNC(bool, GNSS_begin, int); +DEFINE_FAKE_VALUE_FUNC(bool, GNSS_isConnected); +DEFINE_FAKE_VALUE_FUNC(bool, GNSS_setI2COutput, uint8_t); +DEFINE_FAKE_VALUE_FUNC(bool, GNSS_setNavigationFrequency, uint8_t); +DEFINE_FAKE_VALUE_FUNC(long, GNSS_getLatitude); +DEFINE_FAKE_VALUE_FUNC(long, GNSS_getLongitude); +DEFINE_FAKE_VALUE_FUNC(long, GNSS_getAltitude); +DEFINE_FAKE_VALUE_FUNC(uint8_t, GNSS_getSIV); +DEFINE_FAKE_VALUE_FUNC(uint32_t, GNSS_getTimeOfWeek); +DEFINE_FAKE_VALUE_FUNC(uint16_t, GNSS_getYear); +DEFINE_FAKE_VALUE_FUNC(uint8_t, GNSS_getMonth); +DEFINE_FAKE_VALUE_FUNC(uint8_t, GNSS_getDay); +DEFINE_FAKE_VALUE_FUNC(uint8_t, GNSS_getHour); +DEFINE_FAKE_VALUE_FUNC(uint8_t, GNSS_getMinute); +DEFINE_FAKE_VALUE_FUNC(uint8_t, GNSS_getSecond); \ No newline at end of file diff --git a/test/mocks/MockGNSS.h b/test/mocks/MockGNSS.h new file mode 100644 index 0000000..7511d12 --- /dev/null +++ b/test/mocks/MockGNSS.h @@ -0,0 +1,58 @@ +#pragma once + +#include "fff.h" +#include +#include + +// Declare fake functions for u-blox GNSS +DECLARE_FAKE_VALUE_FUNC(bool, GNSS_begin, int); +DECLARE_FAKE_VALUE_FUNC(bool, GNSS_isConnected); +DECLARE_FAKE_VALUE_FUNC(bool, GNSS_setI2COutput, uint8_t); +DECLARE_FAKE_VALUE_FUNC(bool, GNSS_setNavigationFrequency, uint8_t); +DECLARE_FAKE_VALUE_FUNC(long, GNSS_getLatitude); +DECLARE_FAKE_VALUE_FUNC(long, GNSS_getLongitude); +DECLARE_FAKE_VALUE_FUNC(long, GNSS_getAltitude); +DECLARE_FAKE_VALUE_FUNC(uint8_t, GNSS_getSIV); +DECLARE_FAKE_VALUE_FUNC(uint32_t, GNSS_getTimeOfWeek); +DECLARE_FAKE_VALUE_FUNC(uint16_t, GNSS_getYear); +DECLARE_FAKE_VALUE_FUNC(uint8_t, GNSS_getMonth); +DECLARE_FAKE_VALUE_FUNC(uint8_t, GNSS_getDay); +DECLARE_FAKE_VALUE_FUNC(uint8_t, GNSS_getHour); +DECLARE_FAKE_VALUE_FUNC(uint8_t, GNSS_getMinute); +DECLARE_FAKE_VALUE_FUNC(uint8_t, GNSS_getSecond); + +// Mock class for SFE_UBLOX_GNSS +class SFE_UBLOX_GNSS { +public: + // Default mock values + long latitude = 449673925; // Minneapolis ~44.96°N + long longitude = -932838386; // Minneapolis ~-93.28°E + long altitude = 25000; // 250m above MSL + uint8_t satellites = 8; // 8 satellites in view + + // Time values + uint16_t year = 2023; + uint8_t month = 5; + uint8_t day = 15; + uint8_t hour = 10; + uint8_t minute = 30; + uint8_t second = 0; + + bool begin(int wirePort = 0) { return GNSS_begin(wirePort); } + bool isConnected() { return GNSS_isConnected(); } + bool setI2COutput(uint8_t comSettings) { return GNSS_setI2COutput(comSettings); } + bool setNavigationFrequency(uint8_t navFreq) { return GNSS_setNavigationFrequency(navFreq); } + + long getLatitude() { return GNSS_getLatitude(); } + long getLongitude() { return GNSS_getLongitude(); } + long getAltitude() { return GNSS_getAltitude(); } + uint8_t getSIV() { return GNSS_getSIV(); } + + uint32_t getTimeOfWeek() { return GNSS_getTimeOfWeek(); } + uint16_t getYear() { return GNSS_getYear(); } + uint8_t getMonth() { return GNSS_getMonth(); } + uint8_t getDay() { return GNSS_getDay(); } + uint8_t getHour() { return GNSS_getHour(); } + uint8_t getMinute() { return GNSS_getMinute(); } + uint8_t getSecond() { return GNSS_getSecond(); } +}; \ No newline at end of file diff --git a/test/mocks/MockIncludes.h b/test/mocks/MockIncludes.h new file mode 100644 index 0000000..0a677c9 --- /dev/null +++ b/test/mocks/MockIncludes.h @@ -0,0 +1,65 @@ +#pragma once + +// This file contains includes and settings for testing the Kestrel driver with real code +// It ensures we properly redirect hardware calls to mocks while avoiding redefinition errors + +// Tell the compiler we're in testing mode - this can be used in #ifdef blocks +#define TESTING + +// First define all the hardware classes as empty forward declarations +// This prevents the real implementations from being compiled +class Sensor; +class PCAL9535A; +class PCA9634; +class MCP79412; +class SFE_UBLOX_GNSS; +class PAC1934; +class VEML3328; +class Adafruit_SHT4x; +class MXC6655; +class BMA456; + +// Now include our mock implementations +#include "MockSensor.h" +#include "MockPCAL9535A.h" +#include "MockPCA9634.h" +#include "MockMCP79412.h" +#include "MockGNSS.h" +#include "MockPAC1934.h" +#include "MockVEML3328.h" +#include "MockSHT4x.h" +#include "MockMXC6655.h" +#include "MockBMA456.h" +#include "MockArduino.h" +#include "MockWireDeclare.h" + +// Define any constants that might be needed by Kestrel.h +#define OUTPUT 1 +#define INPUT 0 +#define HIGH 1 +#define LOW 0 + +// Arduino constants +#define A0 14 +#define A1 15 +#define A2 16 +#define A3 17 +#define A4 18 +#define A5 19 +#define A6 20 +#define A7 21 +#define D0 0 +#define D1 1 +#define D2 2 +#define D3 3 +#define D4 4 +#define D5 5 +#define D6 6 +#define D7 7 +#define D8 8 +#define D22 22 +#define D23 23 + +// Define additional constants that Kestrel.h might need +// If there are specific enum values or constants that Kestrel.h expects, +// add them here \ No newline at end of file diff --git a/test/mocks/MockMCP79412.cpp b/test/mocks/MockMCP79412.cpp new file mode 100644 index 0000000..2641e83 --- /dev/null +++ b/test/mocks/MockMCP79412.cpp @@ -0,0 +1,13 @@ +#include "MockMCP79412.h" + +// Define fake functions +DEFINE_FAKE_VALUE_FUNC(bool, MCP79412_begin); +DEFINE_FAKE_VALUE_FUNC(time_t, MCP79412_getTime); +DEFINE_FAKE_VALUE_FUNC(bool, MCP79412_setTime, time_t); + +// Set default behavior +void setup_mock_MCP79412_defaults() { + MCP79412_begin_fake.return_val = true; + MCP79412_getTime_fake.return_val = time(NULL); // Current time + MCP79412_setTime_fake.return_val = true; +} \ No newline at end of file diff --git a/test/mocks/MockMCP79412.h b/test/mocks/MockMCP79412.h new file mode 100644 index 0000000..b2117ae --- /dev/null +++ b/test/mocks/MockMCP79412.h @@ -0,0 +1,23 @@ +#pragma once + +#include "fff.h" +#include +#include + +// Declare fake functions +DECLARE_FAKE_VALUE_FUNC(bool, MCP79412_begin); +DECLARE_FAKE_VALUE_FUNC(time_t, MCP79412_getTime); +DECLARE_FAKE_VALUE_FUNC(bool, MCP79412_setTime, time_t); + +// Mock class for MCP79412 RTC +class MCP79412 { +public: + bool begin() { return MCP79412_begin(); } + time_t getTime() { return MCP79412_getTime(); } + bool setTime(time_t time) { return MCP79412_setTime(time); } + + // Additional properties to match the real implementation + uint8_t numErrors = 0; + + // Add more methods as needed for Kestrel tests +}; \ No newline at end of file diff --git a/test/mocks/MockMXC6655.cpp b/test/mocks/MockMXC6655.cpp new file mode 100644 index 0000000..535b0dc --- /dev/null +++ b/test/mocks/MockMXC6655.cpp @@ -0,0 +1,6 @@ +#include "MockMXC6655.h" + +// Define fake functions for MXC6655 +DEFINE_FAKE_VALUE_FUNC(bool, MXC6655_begin); +DEFINE_FAKE_VALUE_FUNC(bool, MXC6655_readAcceleration, float*, float*, float*); +DEFINE_FAKE_VALUE_FUNC(bool, MXC6655_resetOrientation); \ No newline at end of file diff --git a/test/mocks/MockMXC6655.h b/test/mocks/MockMXC6655.h new file mode 100644 index 0000000..40509ef --- /dev/null +++ b/test/mocks/MockMXC6655.h @@ -0,0 +1,29 @@ +#pragma once + +#include "fff.h" +#include + +// Declare fake functions for MXC6655 accelerometer +DECLARE_FAKE_VALUE_FUNC(bool, MXC6655_begin); +DECLARE_FAKE_VALUE_FUNC(bool, MXC6655_readAcceleration, float*, float*, float*); +DECLARE_FAKE_VALUE_FUNC(bool, MXC6655_resetOrientation); + +// Mock class for MXC6655 accelerometer +class MXC6655 { +public: + // Default mock values + float x_acceleration = 0.0f; + float y_acceleration = 0.0f; + float z_acceleration = 1.0f; // Default to 1G in Z direction (earth gravity) + + bool begin() { return MXC6655_begin(); } + + bool readAcceleration(float* x, float* y, float* z) { + *x = x_acceleration; + *y = y_acceleration; + *z = z_acceleration; + return MXC6655_readAcceleration(x, y, z); + } + + bool resetOrientation() { return MXC6655_resetOrientation(); } +}; \ No newline at end of file diff --git a/test/mocks/MockPAC1934.cpp b/test/mocks/MockPAC1934.cpp new file mode 100644 index 0000000..a1c86ff --- /dev/null +++ b/test/mocks/MockPAC1934.cpp @@ -0,0 +1,17 @@ +#include "MockPAC1934.h" + +// Define fake functions +DEFINE_FAKE_VALUE_FUNC(bool, PAC1934_begin); +DEFINE_FAKE_VALUE_FUNC(float, PAC1934_readVoltage, uint8_t); +DEFINE_FAKE_VALUE_FUNC(float, PAC1934_readCurrent, uint8_t); +DEFINE_FAKE_VALUE_FUNC(float, PAC1934_readPower, uint8_t); +DEFINE_FAKE_VALUE_FUNC(bool, PAC1934_enableChannel, uint8_t, bool); + +// Set default behavior +void setup_mock_PAC1934_defaults() { + PAC1934_begin_fake.return_val = true; + PAC1934_readVoltage_fake.return_val = 3.3f; + PAC1934_readCurrent_fake.return_val = 0.1f; + PAC1934_readPower_fake.return_val = 0.33f; + PAC1934_enableChannel_fake.return_val = true; +} diff --git a/test/mocks/MockPAC1934.h b/test/mocks/MockPAC1934.h new file mode 100644 index 0000000..b39276f --- /dev/null +++ b/test/mocks/MockPAC1934.h @@ -0,0 +1,21 @@ +#pragma once + +#include "fff.h" +#include + +// Declare fake functions for PAC1934 (power monitor) +DECLARE_FAKE_VALUE_FUNC(bool, PAC1934_begin); +DECLARE_FAKE_VALUE_FUNC(float, PAC1934_readVoltage, uint8_t); +DECLARE_FAKE_VALUE_FUNC(float, PAC1934_readCurrent, uint8_t); +DECLARE_FAKE_VALUE_FUNC(float, PAC1934_readPower, uint8_t); +DECLARE_FAKE_VALUE_FUNC(bool, PAC1934_enableChannel, uint8_t, bool); + +// Mock class for PAC1934 +class PAC1934 { +public: + bool begin() { return PAC1934_begin(); } + float readVoltage(uint8_t channel) { return PAC1934_readVoltage(channel); } + float readCurrent(uint8_t channel) { return PAC1934_readCurrent(channel); } + float readPower(uint8_t channel) { return PAC1934_readPower(channel); } + bool enableChannel(uint8_t channel, bool enable) { return PAC1934_enableChannel(channel, enable); } +}; diff --git a/test/mocks/MockPCA9634.cpp b/test/mocks/MockPCA9634.cpp new file mode 100644 index 0000000..4bfd766 --- /dev/null +++ b/test/mocks/MockPCA9634.cpp @@ -0,0 +1,9 @@ +#include "MockPCA9634.h" + +// Define fake functions for PCA9634 +DEFINE_FAKE_VALUE_FUNC(bool, PCA9634_begin); +DEFINE_FAKE_VALUE_FUNC(bool, PCA9634_begin_address, uint8_t); +DEFINE_FAKE_VALUE_FUNC(bool, PCA9634_setLEDOutputMode, uint8_t, uint8_t); +DEFINE_FAKE_VALUE_FUNC(bool, PCA9634_setLEDDriverMode, uint8_t, uint8_t); +DEFINE_FAKE_VALUE_FUNC(bool, PCA9634_setBrightness, uint8_t, uint8_t); +DEFINE_FAKE_VALUE_FUNC(bool, PCA9634_setOutputState, uint8_t, uint8_t); \ No newline at end of file diff --git a/test/mocks/MockPCA9634.h b/test/mocks/MockPCA9634.h new file mode 100644 index 0000000..76f41d4 --- /dev/null +++ b/test/mocks/MockPCA9634.h @@ -0,0 +1,23 @@ +#pragma once + +#include "fff.h" +#include + +// Declare fake functions for PCA9634 LED driver +DECLARE_FAKE_VALUE_FUNC(bool, PCA9634_begin); +DECLARE_FAKE_VALUE_FUNC(bool, PCA9634_begin_address, uint8_t); +DECLARE_FAKE_VALUE_FUNC(bool, PCA9634_setLEDOutputMode, uint8_t, uint8_t); +DECLARE_FAKE_VALUE_FUNC(bool, PCA9634_setLEDDriverMode, uint8_t, uint8_t); +DECLARE_FAKE_VALUE_FUNC(bool, PCA9634_setBrightness, uint8_t, uint8_t); +DECLARE_FAKE_VALUE_FUNC(bool, PCA9634_setOutputState, uint8_t, uint8_t); + +// Mock class for PCA9634 LED driver +class PCA9634 { +public: + bool begin() { return PCA9634_begin(); } + bool begin(uint8_t address) { return PCA9634_begin_address(address); } + bool setLEDOutputMode(uint8_t ledNum, uint8_t mode) { return PCA9634_setLEDOutputMode(ledNum, mode); } + bool setLEDDriverMode(uint8_t ledNum, uint8_t mode) { return PCA9634_setLEDDriverMode(ledNum, mode); } + bool setBrightness(uint8_t ledNum, uint8_t brightness) { return PCA9634_setBrightness(ledNum, brightness); } + bool setOutputState(uint8_t ledNum, uint8_t state) { return PCA9634_setOutputState(ledNum, state); } +}; \ No newline at end of file diff --git a/test/mocks/MockPCAL9535A.cpp b/test/mocks/MockPCAL9535A.cpp new file mode 100644 index 0000000..88c502f --- /dev/null +++ b/test/mocks/MockPCAL9535A.cpp @@ -0,0 +1,17 @@ +#include "MockPCAL9535A.h" + +// Define fake functions +DEFINE_FAKE_VALUE_FUNC(bool, PCAL9535A_begin); +DEFINE_FAKE_VALUE_FUNC(bool, PCAL9535A_pinMode, uint8_t, uint8_t); +DEFINE_FAKE_VALUE_FUNC(bool, PCAL9535A_digitalWrite, uint8_t, uint8_t); +DEFINE_FAKE_VALUE_FUNC(uint8_t, PCAL9535A_digitalRead, uint8_t); +DEFINE_FAKE_VALUE_FUNC(bool, PCAL9535A_begin_address, uint8_t); + +// Set default behavior +void setup_mock_PCAL9535A_defaults() { + PCAL9535A_begin_fake.return_val = true; + PCAL9535A_pinMode_fake.return_val = true; + PCAL9535A_digitalWrite_fake.return_val = true; + PCAL9535A_digitalRead_fake.return_val = 0; + PCAL9535A_begin_address_fake.return_val = true; +} \ No newline at end of file diff --git a/test/mocks/MockPCAL9535A.h b/test/mocks/MockPCAL9535A.h new file mode 100644 index 0000000..0646586 --- /dev/null +++ b/test/mocks/MockPCAL9535A.h @@ -0,0 +1,23 @@ +#pragma once + +#include "fff.h" +#include + +// Declare fake functions +DECLARE_FAKE_VALUE_FUNC(bool, PCAL9535A_begin); +DECLARE_FAKE_VALUE_FUNC(bool, PCAL9535A_pinMode, uint8_t, uint8_t); +DECLARE_FAKE_VALUE_FUNC(bool, PCAL9535A_digitalWrite, uint8_t, uint8_t); +DECLARE_FAKE_VALUE_FUNC(uint8_t, PCAL9535A_digitalRead, uint8_t); +DECLARE_FAKE_VALUE_FUNC(bool, PCAL9535A_begin_address, uint8_t); + +// Mock class for PCAL9535A IO expander +class PCAL9535A { +public: + bool begin() { return PCAL9535A_begin(); } + bool pinMode(uint8_t pin, uint8_t mode) { return PCAL9535A_pinMode(pin, mode); } + bool digitalWrite(uint8_t pin, uint8_t value) { return PCAL9535A_digitalWrite(pin, value); } + uint8_t digitalRead(uint8_t pin) { return PCAL9535A_digitalRead(pin); } + bool begin(uint8_t address) { return PCAL9535A_begin_address(address); } + + // Add more methods as needed for Kestrel tests +}; \ No newline at end of file diff --git a/test/mocks/MockParticle.h b/test/mocks/MockParticle.h new file mode 100644 index 0000000..38ba1be --- /dev/null +++ b/test/mocks/MockParticle.h @@ -0,0 +1,70 @@ +// test/mocks/mock_particle.h +#pragma once +#include +#include +#include + +// Basic String implementation to match Particle's String class +class String { +private: + std::string data; + +public: + String() : data("") {} + String(const char* str) : data(str ? str : "") {} + String(const std::string& str) : data(str) {} + String(int value) : data(std::to_string(value)) {} + + // Common methods used in the tests + bool equals(const String& other) const { return data == other.data; } + bool startsWith(const String& prefix) const { + return data.find(prefix.data) == 0; + } + bool endsWith(const String& suffix) const { + if (suffix.data.length() > data.length()) return false; + return data.rfind(suffix.data) == (data.length() - suffix.data.length()); + } + + // Operators + String operator+(const String& rhs) const { + return String(data + rhs.data); + } + String& operator+=(const String& rhs) { + data += rhs.data; + return *this; + } + bool operator==(const String& rhs) const { + return data == rhs.data; + } + + // Conversion operators + operator const char*() const { return data.c_str(); } + + // Common String methods + size_t length() const { return data.length(); } + const char* c_str() const { return data.c_str(); } + + // Specific methods needed for your FlightControl_Demo + bool endsWith(char c) const { + return !data.empty() && data.back() == c; + } + + int lastIndexOf(char c) const { + size_t pos = data.rfind(c); + return pos != std::string::npos ? static_cast(pos) : -1; + } + + int lastIndexOf(const String& str) const { + size_t pos = data.rfind(str.data); + return pos != std::string::npos ? static_cast(pos) : -1; + } + + // Add more methods as they're needed +}; + +// Add other Particle types and functions as needed +// For example: +typedef uint8_t byte; + +// Time functions are implemented in MockArduino.cpp +// Don't define millis() here to avoid duplicate definition \ No newline at end of file diff --git a/test/mocks/MockSHT4x.cpp b/test/mocks/MockSHT4x.cpp new file mode 100644 index 0000000..f944eb3 --- /dev/null +++ b/test/mocks/MockSHT4x.cpp @@ -0,0 +1,17 @@ +#include "MockSHT4x.h" + +// Define fake functions +DEFINE_FAKE_VALUE_FUNC(bool, SHT4x_begin); +DEFINE_FAKE_VALUE_FUNC(bool, SHT4x_setPrecision, uint8_t); +DEFINE_FAKE_VALUE_FUNC(bool, SHT4x_readTemperature); +DEFINE_FAKE_VALUE_FUNC(bool, SHT4x_readHumidity); +DEFINE_FAKE_VALUE_FUNC(bool, SHT4x_getEvent); + +// Set default behavior +void setup_mock_SHT4x_defaults() { + SHT4x_begin_fake.return_val = true; + SHT4x_setPrecision_fake.return_val = true; + SHT4x_readTemperature_fake.return_val = true; + SHT4x_readHumidity_fake.return_val = true; + SHT4x_getEvent_fake.return_val = true; +} diff --git a/test/mocks/MockSHT4x.h b/test/mocks/MockSHT4x.h new file mode 100644 index 0000000..22081ea --- /dev/null +++ b/test/mocks/MockSHT4x.h @@ -0,0 +1,29 @@ +#pragma once + +#include "fff.h" +#include + +// Constants to match Adafruit's SHT4x +#define SHT4X_HIGH_PRECISION 0 +#define SHT4X_MED_PRECISION 1 +#define SHT4X_LOW_PRECISION 2 + +// Declare fake functions +DECLARE_FAKE_VALUE_FUNC(bool, SHT4x_begin); +DECLARE_FAKE_VALUE_FUNC(bool, SHT4x_setPrecision, uint8_t); +DECLARE_FAKE_VALUE_FUNC(bool, SHT4x_readTemperature); +DECLARE_FAKE_VALUE_FUNC(bool, SHT4x_readHumidity); +DECLARE_FAKE_VALUE_FUNC(bool, SHT4x_getEvent); + +// Mock class for Adafruit_SHT4x +class Adafruit_SHT4x { +public: + float temperature = 25.0f; // Default mock values + float humidity = 50.0f; + + bool begin() { return SHT4x_begin(); } + bool setPrecision(uint8_t precision) { return SHT4x_setPrecision(precision); } + float readTemperature() { SHT4x_readTemperature(); return temperature; } + float readHumidity() { SHT4x_readHumidity(); return humidity; } + bool getEvent() { return SHT4x_getEvent(); } +}; diff --git a/test/mocks/MockSPI.cpp b/test/mocks/MockSPI.cpp new file mode 100644 index 0000000..430c89b --- /dev/null +++ b/test/mocks/MockSPI.cpp @@ -0,0 +1,4 @@ +#include "SPI.h" + +// Define the extern variable +SPIClass SPI; \ No newline at end of file diff --git a/test/mocks/MockSensor.h b/test/mocks/MockSensor.h new file mode 100644 index 0000000..3c27298 --- /dev/null +++ b/test/mocks/MockSensor.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include "MockParticle.h" + +// This is a minimal mock of the Sensor base class that Kestrel inherits from +class Sensor { +public: + Sensor() {} + virtual ~Sensor() {} + + // Common base class properties + static constexpr int MAX_NUM_ERRORS = 10; // Maximum number of errors to log + uint32_t errors[MAX_NUM_ERRORS] = {0}; + uint8_t numErrors = 0; + bool errorOverwrite = false; + + // Common methods + virtual int throwError(uint32_t error) { + // Simple error handling implementation + if (numErrors < MAX_NUM_ERRORS) { + errors[numErrors++] = error; + } else { + errors[MAX_NUM_ERRORS - 1] = error; + errorOverwrite = true; + } + return 0; + } + + // Virtual methods that would be implemented by derived classes + virtual String begin(bool &criticalFault, bool &fault) { return ""; } + virtual String getData(void) { return ""; } + virtual String getMetadata(void) { return ""; } + virtual String getErrors(void) { return ""; } + virtual String selfDiagnostic(uint8_t diagnosticLevel) { return ""; } +}; \ No newline at end of file diff --git a/test/mocks/MockVEML3328.cpp b/test/mocks/MockVEML3328.cpp new file mode 100644 index 0000000..38909fe --- /dev/null +++ b/test/mocks/MockVEML3328.cpp @@ -0,0 +1,19 @@ +#include "MockVEML3328.h" + +// Define fake functions +DEFINE_FAKE_VALUE_FUNC(bool, VEML3328_begin); +DEFINE_FAKE_VALUE_FUNC(uint16_t, VEML3328_readRed); +DEFINE_FAKE_VALUE_FUNC(uint16_t, VEML3328_readGreen); +DEFINE_FAKE_VALUE_FUNC(uint16_t, VEML3328_readBlue); +DEFINE_FAKE_VALUE_FUNC(uint16_t, VEML3328_readClear); +DEFINE_FAKE_VALUE_FUNC(uint16_t, VEML3328_readIR); + +// Set default behavior +void setup_mock_VEML3328_defaults() { + VEML3328_begin_fake.return_val = true; + VEML3328_readRed_fake.return_val = 500; + VEML3328_readGreen_fake.return_val = 600; + VEML3328_readBlue_fake.return_val = 450; + VEML3328_readClear_fake.return_val = 800; + VEML3328_readIR_fake.return_val = 300; +} diff --git a/test/mocks/MockVEML3328.h b/test/mocks/MockVEML3328.h new file mode 100644 index 0000000..619355e --- /dev/null +++ b/test/mocks/MockVEML3328.h @@ -0,0 +1,23 @@ +#pragma once + +#include "fff.h" +#include + +// Declare fake functions for VEML3328 (light sensor) +DECLARE_FAKE_VALUE_FUNC(bool, VEML3328_begin); +DECLARE_FAKE_VALUE_FUNC(uint16_t, VEML3328_readRed); +DECLARE_FAKE_VALUE_FUNC(uint16_t, VEML3328_readGreen); +DECLARE_FAKE_VALUE_FUNC(uint16_t, VEML3328_readBlue); +DECLARE_FAKE_VALUE_FUNC(uint16_t, VEML3328_readClear); +DECLARE_FAKE_VALUE_FUNC(uint16_t, VEML3328_readIR); + +// Mock class for VEML3328 +class VEML3328 { +public: + bool begin() { return VEML3328_begin(); } + uint16_t readRed() { return VEML3328_readRed(); } + uint16_t readGreen() { return VEML3328_readGreen(); } + uint16_t readBlue() { return VEML3328_readBlue(); } + uint16_t readClear() { return VEML3328_readClear(); } + uint16_t readIR() { return VEML3328_readIR(); } +}; diff --git a/test/mocks/MockWire.cpp b/test/mocks/MockWire.cpp new file mode 100644 index 0000000..2ef0b89 --- /dev/null +++ b/test/mocks/MockWire.cpp @@ -0,0 +1,17 @@ +// test/mocks/wire_mock.cpp +#include "MockWireDeclare.h" + +// Define fake functions +DEFINE_FAKE_VOID_FUNC(Wire_begin); +DEFINE_FAKE_VOID_FUNC(Wire_setClock, unsigned long); +DEFINE_FAKE_VOID_FUNC(Wire_beginTransmission, uint8_t); +DEFINE_FAKE_VALUE_FUNC(uint8_t, Wire_endTransmission, bool); +DEFINE_FAKE_VALUE_FUNC(uint8_t, Wire_requestFrom_3args, uint8_t, uint8_t, bool); +DEFINE_FAKE_VALUE_FUNC(uint8_t, Wire_requestFrom_2args, uint8_t, uint8_t); +DEFINE_FAKE_VALUE_FUNC(int, Wire_available); +DEFINE_FAKE_VALUE_FUNC(int, Wire_read); +DEFINE_FAKE_VOID_FUNC(Wire_write_uint8, uint8_t); +DEFINE_FAKE_VOID_FUNC(Wire_write_buffer, const uint8_t*, size_t); + +// Define the global Wire instance +MockWire Wire; \ No newline at end of file diff --git a/test/mocks/MockWireDeclare.h b/test/mocks/MockWireDeclare.h new file mode 100644 index 0000000..334300c --- /dev/null +++ b/test/mocks/MockWireDeclare.h @@ -0,0 +1,41 @@ +// test/mocks/mock_wire_defs.h +#ifndef WIRE_MOCK_DEFS_H +#define WIRE_MOCK_DEFS_H + +#include +#include "fff.h" + +// Declare fake functions +DECLARE_FAKE_VOID_FUNC(Wire_begin); +DECLARE_FAKE_VOID_FUNC(Wire_setClock, unsigned long); +DECLARE_FAKE_VOID_FUNC(Wire_beginTransmission, uint8_t); +DECLARE_FAKE_VALUE_FUNC(uint8_t, Wire_endTransmission, bool); +DECLARE_FAKE_VALUE_FUNC(uint8_t, Wire_requestFrom_3args, uint8_t, uint8_t, bool); +DECLARE_FAKE_VALUE_FUNC(uint8_t, Wire_requestFrom_2args, uint8_t, uint8_t); +DECLARE_FAKE_VALUE_FUNC(int, Wire_available); +DECLARE_FAKE_VALUE_FUNC(int, Wire_read); +DECLARE_FAKE_VOID_FUNC(Wire_write_uint8, uint8_t); +DECLARE_FAKE_VOID_FUNC(Wire_write_buffer, const uint8_t*, size_t); + +// Mock Wire class declaration +class MockWire { +public: + void begin() { Wire_begin(); } + void setClock(unsigned long clock) { Wire_setClock(clock); } + void beginTransmission(uint8_t address) { Wire_beginTransmission(address); } + uint8_t endTransmission(bool sendStop = true) { return Wire_endTransmission(sendStop); } + uint8_t requestFrom(uint8_t address, uint8_t quantity, bool sendStop) { + return Wire_requestFrom_3args(address, quantity, sendStop); + } + uint8_t requestFrom(uint8_t address, uint8_t quantity) { + return Wire_requestFrom_2args(address, quantity); + } + int available() { return Wire_available(); } + int read() { return Wire_read(); } + void write(uint8_t data) { Wire_write_uint8(data); } + void write(const uint8_t* data, size_t quantity) { Wire_write_buffer(data, quantity); } +}; + +extern MockWire Wire; + +#endif // WIRE_MOCK_DEFS_H \ No newline at end of file diff --git a/test/mocks/Particle.h b/test/mocks/Particle.h new file mode 100644 index 0000000..31ed9a3 --- /dev/null +++ b/test/mocks/Particle.h @@ -0,0 +1,45 @@ +#pragma once + +#include "MockParticle.h" +#include +#include + +// Mock Particle types and functions to satisfy dependencies +typedef uint8_t system_event_t; + +// System event handlers +constexpr system_event_t TIME_CHANGED = 1; +constexpr system_event_t LOW_MEMORY = 2; + +// Mock Particle functions +inline void System_on(system_event_t event, void (*handler)(system_event_t, int)) { + // Just do nothing in mock +} + +inline int Time_now() { + return time(NULL); +} + +inline bool Particle_connected() { + return true; +} + +inline bool Particle_syncTime() { + return true; +} + +// Define the actual Particle API namespace/functions +namespace Particle { + inline bool connected() { return Particle_connected(); } + inline bool syncTime() { return Particle_syncTime(); } + + namespace System { + inline void on(system_event_t event, void (*handler)(system_event_t, int)) { + System_on(event, handler); + } + } + + namespace Time { + inline int now() { return Time_now(); } + } +} \ No newline at end of file diff --git a/test/mocks/SPI.h b/test/mocks/SPI.h new file mode 100644 index 0000000..1f4fc88 --- /dev/null +++ b/test/mocks/SPI.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include // For size_t + +// SPI settings +#define SPI_MODE0 0x00 +#define SPI_MODE1 0x01 +#define SPI_MODE2 0x02 +#define SPI_MODE3 0x03 + +#define SPI_CLOCK_DIV2 0x00 +#define SPI_CLOCK_DIV4 0x01 +#define SPI_CLOCK_DIV8 0x02 +#define SPI_CLOCK_DIV16 0x03 +#define SPI_CLOCK_DIV32 0x04 +#define SPI_CLOCK_DIV64 0x05 +#define SPI_CLOCK_DIV128 0x06 + +// Simple mock for the SPI class +class SPIClass { +public: + void begin() {} + void beginTransaction(uint32_t settings) {} + void endTransaction() {} + uint8_t transfer(uint8_t data) { return 0; } + void transfer(void *buf, size_t count) {} +}; + +extern SPIClass SPI; \ No newline at end of file diff --git a/test/mocks/TestKestrel.h b/test/mocks/TestKestrel.h new file mode 100644 index 0000000..8898d5f --- /dev/null +++ b/test/mocks/TestKestrel.h @@ -0,0 +1,336 @@ +#pragma once + +// Modified Kestrel.h intended for testing only +// This provides a controlled environment without class redefinitions + +// Include all mocks we need +#include "MockArduino.h" +#include "MockSensor.h" +#include "MockPCAL9535A.h" +#include "MockPCA9634.h" +#include "MockMCP79412.h" +#include "MockGNSS.h" +#include "MockPAC1934.h" +#include "MockVEML3328.h" +#include "MockSHT4x.h" +#include "MockMXC6655.h" +#include "MockBMA456.h" +#include "MockWireDeclare.h" + +// Define constants and namespaces from Kestrel.h that we need for testing +namespace Pins { + constexpr uint16_t WD_HOLD = D2; + constexpr uint16_t SD_CS = D8; + constexpr uint16_t Clock_INT = D22; + constexpr uint16_t TALON1_GPIOA = A3; + constexpr uint16_t TALON1_GPIOB = D7; + constexpr uint16_t TALON2_GPIOA = A2; + constexpr uint16_t TALON2_GPIOB = D6; + constexpr uint16_t TALON3_GPIOA = A1; + constexpr uint16_t TALON3_GPIOB = D5; + constexpr uint16_t I2C_GLOBAL_EN = D23; + constexpr uint16_t I2C_OB_EN = A6; +} + +namespace PinsOB { + constexpr uint16_t I2C_EXT_EN = 10; + constexpr uint16_t SD_CD = 8; + constexpr uint16_t SD_EN = 12; + constexpr uint16_t AUX_EN = 15; + constexpr uint16_t CE = 11; + constexpr uint16_t LED_EN = 13; + constexpr uint16_t CSA_EN = 14; + constexpr uint16_t GPS_INT = 7; +} + +namespace PinsTalon { + constexpr uint8_t SEL[4] = {0, 4, 8, 12}; + constexpr uint8_t I2C_EN[4] = {1, 5, 9, 13}; + constexpr uint8_t EN[4] = {3, 7, 11, 15}; + constexpr uint8_t FAULT[4] = {2, 6, 10, 14}; +} + +namespace TimeSource { + constexpr uint8_t INCREMENT = 4; + constexpr uint8_t RTC = 3; + constexpr uint8_t GPS_RTC = 2; + constexpr uint8_t CELLULAR = 1; + constexpr uint8_t GPS = 0; + constexpr uint8_t NONE = 5; +} + +namespace IndicatorLight { + constexpr uint8_t SENSORS = 1; + constexpr uint8_t GPS = 2; + constexpr uint8_t CELL = 3; + constexpr uint8_t STAT = 4; + constexpr uint8_t ALL = 5; +} + +namespace IndicatorMode { + constexpr uint8_t NONE = 0; + constexpr uint8_t PASS = 1; + constexpr uint8_t WAITING = 2; + constexpr uint8_t ERROR = 3; + constexpr uint8_t ERROR_CRITICAL = 4; + constexpr uint8_t PREPASS = 5; + constexpr uint8_t INIT = 6; + constexpr uint8_t IDLE = 7; + constexpr uint8_t COMMAND = 8; +} + +namespace AccelType { + constexpr uint8_t MXC6655 = 0; + constexpr uint8_t BMA456 = 1; +} + +namespace HardwareVersion { + constexpr uint8_t PRE_1v9 = 0; + constexpr uint8_t MODEL_1v9 = 1; +} + +struct dateTimeStruct { + int year; + int month; + int day; + int hour; + int minute; + int second; + uint8_t source = TimeSource::NONE; +}; + +// Simplified version of Kestrel for testing +// We'll manually implement the methods we want to test +class Kestrel : public Sensor { +public: + constexpr static int MAX_NUM_ERRORS = 10; + const std::string FIRMWARE_VERSION = "1.7.5"; + static constexpr uint8_t numTalonPorts = 5; + static constexpr int MAX_MESSAGE_LENGTH = 1024; + + dateTimeStruct currentDateTime = {2049, 6, 16, 3, 27, 31, TimeSource::NONE}; + uint8_t timeFix = 0; + SFE_UBLOX_GNSS gps; + + // Constructor + Kestrel(bool useSensors = false) : reportSensors(useSensors) { + // Initialize member objects + } + + // Public methods we want to test + std::string begin(time_t time, bool &criticalFault, bool &fault) { + // Call internal device initializations + ioOB.begin(); + ioTalon.begin(); + rtc.begin(); + led.begin(); + + if (reportSensors) { + als.begin(); + csaAlpha.begin(); + csaBeta.begin(); + atmos.begin(); + accel.begin(); + gps.begin(); + } + + // Return success message + return "Kestrel " + FIRMWARE_VERSION + " initialized successfully"; + } + + bool enablePower(uint8_t port, bool state = true) { + if (port >= numTalonPorts) return false; + + // Call IO expander to enable/disable power + return ioTalon.digitalWrite(PinsTalon::EN[port], state); + } + + bool enableData(uint8_t port, bool state = true) { + if (port >= numTalonPorts) return false; + + // Call IO expander to enable/disable data + return ioTalon.digitalWrite(PinsTalon::I2C_EN[port], state); + } + + time_t getTime() { + return rtc.getTime(); + } + + bool enableI2C_OB(bool state = true) { + return ioOB.digitalWrite(PinsOB::I2C_EXT_EN, state); + } + + bool enableI2C_Global(bool state = true) { + // In our mock environment, digitalWrite returns void + // But in the real implementation it would return a status + digitalWrite(Pins::I2C_GLOBAL_EN, state); + return true; // Assume success + } + + bool enableI2C_External(bool state = true) { + return ioOB.digitalWrite(PinsOB::I2C_EXT_EN, state); + } + + bool setIndicatorState(uint8_t ledBank, uint8_t mode) { + int brightness = 0; + + // Convert mode to brightness + switch (mode) { + case IndicatorMode::PASS: + brightness = 75; + break; + case IndicatorMode::ERROR: + brightness = 100; + break; + default: + brightness = 0; + break; + } + + return led.setBrightness(ledBank, brightness); + } + + // Other methods as needed for testing... + +private: + PCAL9535A ioOB; + PCAL9535A ioTalon; + MCP79412 rtc; + PAC1934 csaAlpha; + PAC1934 csaBeta; + VEML3328 als; + Adafruit_SHT4x atmos; + MXC6655 accel; + PCA9634 led; + bool reportSensors = false; +}; + +// Helper functions for testing Kestrel +namespace KestrelTest { + // Setup a Kestrel instance with all mock dependencies properly configured + inline Kestrel* createKestrel() { + // Configure all mock return values for a successful initialization + PCAL9535A_begin_fake.return_val = true; + MCP79412_begin_fake.return_val = true; + MCP79412_getTime_fake.return_val = 1616161616; + PCA9634_begin_fake.return_val = true; + VEML3328_begin_fake.return_val = true; + PAC1934_begin_fake.return_val = true; + SHT4x_begin_fake.return_val = true; + MXC6655_begin_fake.return_val = true; + BMA456_begin_fake.return_val = true; + GNSS_begin_fake.return_val = true; + + // Create a Kestrel instance + return new Kestrel(true); // true = use sensors + } + + // Reset all mocks for a clean slate + inline void resetAllMocks() { + // Wire mocks + RESET_FAKE(Wire_begin); + RESET_FAKE(Wire_setClock); + RESET_FAKE(Wire_beginTransmission); + RESET_FAKE(Wire_endTransmission); + RESET_FAKE(Wire_requestFrom_3args); + RESET_FAKE(Wire_requestFrom_2args); + RESET_FAKE(Wire_available); + RESET_FAKE(Wire_read); + RESET_FAKE(Wire_write_uint8); + RESET_FAKE(Wire_write_buffer); + + // Arduino mocks + RESET_FAKE(pinMode); + RESET_FAKE(digitalWrite); + RESET_FAKE(digitalRead); + RESET_FAKE(millis); + RESET_FAKE(delay); + + // Hardware component mocks + RESET_FAKE(PCAL9535A_begin); + RESET_FAKE(PCAL9535A_pinMode); + RESET_FAKE(PCAL9535A_digitalWrite); + RESET_FAKE(PCAL9535A_digitalRead); + + RESET_FAKE(MCP79412_begin); + RESET_FAKE(MCP79412_getTime); + RESET_FAKE(MCP79412_setTime); + + RESET_FAKE(PCA9634_begin); + RESET_FAKE(PCA9634_setBrightness); + RESET_FAKE(PCA9634_setLEDOutputMode); + + RESET_FAKE(VEML3328_begin); + RESET_FAKE(VEML3328_readRed); + RESET_FAKE(VEML3328_readGreen); + RESET_FAKE(VEML3328_readBlue); + + RESET_FAKE(PAC1934_begin); + RESET_FAKE(PAC1934_readVoltage); + RESET_FAKE(PAC1934_readCurrent); + + RESET_FAKE(SHT4x_begin); + RESET_FAKE(SHT4x_readTemperature); + RESET_FAKE(SHT4x_readHumidity); + + RESET_FAKE(MXC6655_begin); + RESET_FAKE(MXC6655_readAcceleration); + + RESET_FAKE(BMA456_begin); + RESET_FAKE(BMA456_readAcceleration); + RESET_FAKE(BMA456_getStepCount); + + RESET_FAKE(GNSS_begin); + RESET_FAKE(GNSS_getLatitude); + RESET_FAKE(GNSS_getLongitude); + RESET_FAKE(GNSS_getAltitude); + } + + // Set default successful return values for hardware operations + inline void setDefaultMockBehavior() { + // Configure default successful behaviors + Wire_endTransmission_fake.return_val = 0; // Success + Wire_available_fake.return_val = 1; // Data available + + PCAL9535A_begin_fake.return_val = true; + PCAL9535A_digitalWrite_fake.return_val = true; + PCAL9535A_digitalRead_fake.return_val = 0; // Default to LOW + + MCP79412_begin_fake.return_val = true; + MCP79412_getTime_fake.return_val = 1616161616; + MCP79412_setTime_fake.return_val = true; + + PCA9634_begin_fake.return_val = true; + PCA9634_setBrightness_fake.return_val = true; + PCA9634_setLEDOutputMode_fake.return_val = true; + + VEML3328_begin_fake.return_val = true; + VEML3328_readRed_fake.return_val = 500; + VEML3328_readGreen_fake.return_val = 600; + VEML3328_readBlue_fake.return_val = 400; + + PAC1934_begin_fake.return_val = true; + PAC1934_readVoltage_fake.return_val = 3.3f; + PAC1934_readCurrent_fake.return_val = 0.1f; + + SHT4x_begin_fake.return_val = true; + + MXC6655_begin_fake.return_val = true; + BMA456_begin_fake.return_val = true; + + GNSS_begin_fake.return_val = true; + GNSS_isConnected_fake.return_val = true; + GNSS_getLatitude_fake.return_val = 449673925; + GNSS_getLongitude_fake.return_val = -932838386; + GNSS_getAltitude_fake.return_val = 25000; + GNSS_getSIV_fake.return_val = 8; + GNSS_getYear_fake.return_val = 2023; + GNSS_getMonth_fake.return_val = 5; + GNSS_getDay_fake.return_val = 15; + GNSS_getHour_fake.return_val = 10; + GNSS_getMinute_fake.return_val = 30; + GNSS_getSecond_fake.return_val = 0; + + millis_fake.return_val = 1000; // Start at 1 second + } +} \ No newline at end of file diff --git a/test/mocks/WProgram.h b/test/mocks/WProgram.h new file mode 100644 index 0000000..f638a01 --- /dev/null +++ b/test/mocks/WProgram.h @@ -0,0 +1,4 @@ +#pragma once + +// This is for older Arduino code that used WProgram.h instead of Arduino.h +#include "Arduino.h" \ No newline at end of file diff --git a/test/mocks/Wire.h b/test/mocks/Wire.h new file mode 100644 index 0000000..993e1f4 --- /dev/null +++ b/test/mocks/Wire.h @@ -0,0 +1,5 @@ +#pragma once + +#include "MockWireDeclare.h" + +// This is just a header to include our Wire mock \ No newline at end of file diff --git a/test/unit/Driver_-_Kestrel/Driver_-_KestrelFunctionTests.cpp b/test/unit/Driver_-_Kestrel/Driver_-_KestrelFunctionTests.cpp new file mode 100644 index 0000000..22d31c0 --- /dev/null +++ b/test/unit/Driver_-_Kestrel/Driver_-_KestrelFunctionTests.cpp @@ -0,0 +1,151 @@ +// FlightControl_Demo/test/unit/Driver-_-Kestrel/KestrelFunctionTests.cpp +// Tests for Kestrel's public functions + +#include "gtest/gtest.h" +#include "fff.h" +#include "TestKestrel.h" + +// Test fixture for Kestrel public function tests +class KestrelPublicTest : public ::testing::Test { +protected: + Kestrel* kestrel; + + void SetUp() override { + // Reset all mocks before each test + KestrelTest::resetAllMocks(); + + // Set default mock behaviors + KestrelTest::setDefaultMockBehavior(); + + // Create a Kestrel instance with properly configured mocks + kestrel = KestrelTest::createKestrel(); + } + + void TearDown() override { + // Clean up + delete kestrel; + } +}; + +// Test Kestrel initialization +TEST_F(KestrelPublicTest, BeginTest) { + // Set up additional mock behaviors if needed + bool criticalFault = false; + bool fault = false; + + // Call Kestrel's begin method + std::string result = kestrel->begin(1616161616, criticalFault, fault); + + // Verify begin was called on all dependent hardware components + EXPECT_EQ(PCAL9535A_begin_fake.call_count, 2); // 2 I/O expanders + EXPECT_EQ(MCP79412_begin_fake.call_count, 1); // RTC + EXPECT_EQ(PCA9634_begin_fake.call_count, 1); // LED controller + + // Verify no critical faults occurred + EXPECT_FALSE(criticalFault); + EXPECT_FALSE(fault); + + // Verify result contains expected success message + EXPECT_TRUE(result.find("Kestrel") != std::string::npos); +} + +// Test port power control +TEST_F(KestrelPublicTest, EnablePowerTest) { + // Set up mock behavior for I/O expanders + PCAL9535A_digitalWrite_fake.return_val = true; + + // Test enabling power on port 1 + bool result = kestrel->enablePower(1, true); + + // Verify the result and calls + EXPECT_TRUE(result); + EXPECT_EQ(PCAL9535A_digitalWrite_fake.call_count, 1); + + // Test disabling power + RESET_FAKE(PCAL9535A_digitalWrite); + PCAL9535A_digitalWrite_fake.return_val = true; + + result = kestrel->enablePower(1, false); + + EXPECT_TRUE(result); + EXPECT_EQ(PCAL9535A_digitalWrite_fake.call_count, 1); + + // Test with invalid port (should return false) + result = kestrel->enablePower(10, true); // Port 10 is out of range + EXPECT_FALSE(result); +} + +// Test data port control +TEST_F(KestrelPublicTest, EnableDataTest) { + // Set up mock behavior for I/O expanders + PCAL9535A_digitalWrite_fake.return_val = true; + + // Test enabling data on port 1 + bool result = kestrel->enableData(1, true); + + // Verify the result and calls + EXPECT_TRUE(result); + EXPECT_EQ(PCAL9535A_digitalWrite_fake.call_count, 1); + + // Test with invalid port (should return false) + result = kestrel->enableData(10, true); // Port 10 is out of range + EXPECT_FALSE(result); +} + +// Test time retrieval +TEST_F(KestrelPublicTest, GetTimeTest) { + // Set up mock behavior for time retrieval + MCP79412_getTime_fake.return_val = 1616161616; + + // Call the method under test + time_t time = kestrel->getTime(); + + // Verify the result matches what we expect + EXPECT_EQ(time, 1616161616); + EXPECT_EQ(MCP79412_getTime_fake.call_count, 1); +} + +// Test I2C bus control +TEST_F(KestrelPublicTest, I2CControlTest) { + // Set up mock behavior + PCAL9535A_digitalWrite_fake.return_val = true; + + // Test enabling I2C buses + EXPECT_TRUE(kestrel->enableI2C_Global(true)); + EXPECT_TRUE(kestrel->enableI2C_OB(true)); + EXPECT_TRUE(kestrel->enableI2C_External(true)); + + // Verify the calls occurred + // Note: Our simplified test implementation uses digitalWrite for Global, not PCAL9535A + EXPECT_EQ(digitalWrite_fake.call_count, 1); + EXPECT_EQ(PCAL9535A_digitalWrite_fake.call_count, 2); + + // Reset the fakes for the next series of tests + RESET_FAKE(PCAL9535A_digitalWrite); + RESET_FAKE(digitalWrite); + PCAL9535A_digitalWrite_fake.return_val = true; + + // Test disabling I2C buses + EXPECT_TRUE(kestrel->enableI2C_Global(false)); + EXPECT_TRUE(kestrel->enableI2C_OB(false)); + EXPECT_TRUE(kestrel->enableI2C_External(false)); + + // Verify the calls occurred + EXPECT_EQ(digitalWrite_fake.call_count, 1); + EXPECT_EQ(PCAL9535A_digitalWrite_fake.call_count, 2); +} + +// Test indicator light control +TEST_F(KestrelPublicTest, IndicatorLightTest) { + // Set up mock behavior + PCA9634_setBrightness_fake.return_val = true; + + // Test setting lights to different states + EXPECT_TRUE(kestrel->setIndicatorState(IndicatorLight::SENSORS, IndicatorMode::PASS)); + EXPECT_TRUE(kestrel->setIndicatorState(IndicatorLight::GPS, IndicatorMode::ERROR)); + + // Verify the right calls were made + EXPECT_EQ(PCA9634_setBrightness_fake.call_count, 2); +} + +// Add more tests for other public Kestrel functions as needed \ No newline at end of file diff --git a/test/unit/Driver_-_Kestrel/Driver_-_KestrelSetupTests.cpp b/test/unit/Driver_-_Kestrel/Driver_-_KestrelSetupTests.cpp new file mode 100644 index 0000000..87430fa --- /dev/null +++ b/test/unit/Driver_-_Kestrel/Driver_-_KestrelSetupTests.cpp @@ -0,0 +1,317 @@ +// FlightControl_Demo/test/unit/Driver-_-Kestrel/Driver-_-KestrelTest.cpp +#include "gtest/gtest.h" +#include "fff.h" +#include "MockWireDeclare.h" +#include "MockArduino.h" +#include "MockPCAL9535A.h" +#include "MockMCP79412.h" +#include "MockPCA9634.h" +#include "MockVEML3328.h" +#include "MockPAC1934.h" +#include "MockSHT4x.h" +#include "MockMXC6655.h" +#include "MockBMA456.h" +#include "MockGNSS.h" +#include "MockSensor.h" + +// Test fixture for basic mock testing +class KestrelBasicTest : public ::testing::Test { +protected: + void SetUp() override { + // Reset all Wire fakes before each test + RESET_FAKE(Wire_begin); + RESET_FAKE(Wire_setClock); + RESET_FAKE(Wire_beginTransmission); + RESET_FAKE(Wire_endTransmission); + RESET_FAKE(Wire_requestFrom_3args); + RESET_FAKE(Wire_requestFrom_2args); + RESET_FAKE(Wire_available); + RESET_FAKE(Wire_read); + RESET_FAKE(Wire_write_uint8); + RESET_FAKE(Wire_write_buffer); + + // Reset Arduino fakes + RESET_FAKE(pinMode); + RESET_FAKE(digitalWrite); + RESET_FAKE(digitalRead); + RESET_FAKE(millis); + RESET_FAKE(delay); + + // Reset PCAL9535A fakes + RESET_FAKE(PCAL9535A_begin); + RESET_FAKE(PCAL9535A_pinMode); + RESET_FAKE(PCAL9535A_digitalWrite); + RESET_FAKE(PCAL9535A_digitalRead); + + // Reset MCP79412 fakes + RESET_FAKE(MCP79412_begin); + RESET_FAKE(MCP79412_getTime); + RESET_FAKE(MCP79412_setTime); + + // Reset PCA9634 fakes + RESET_FAKE(PCA9634_begin); + RESET_FAKE(PCA9634_setBrightness); + RESET_FAKE(PCA9634_setLEDOutputMode); + + // Reset VEML3328 fakes + RESET_FAKE(VEML3328_begin); + RESET_FAKE(VEML3328_readRed); + RESET_FAKE(VEML3328_readGreen); + RESET_FAKE(VEML3328_readBlue); + + // Reset PAC1934 fakes + RESET_FAKE(PAC1934_begin); + RESET_FAKE(PAC1934_readVoltage); + RESET_FAKE(PAC1934_readCurrent); + + // Reset SHT4x fakes + RESET_FAKE(SHT4x_begin); + RESET_FAKE(SHT4x_readTemperature); + RESET_FAKE(SHT4x_readHumidity); + + // Reset MXC6655 fakes + RESET_FAKE(MXC6655_begin); + RESET_FAKE(MXC6655_readAcceleration); + + // Reset BMA456 fakes + RESET_FAKE(BMA456_begin); + RESET_FAKE(BMA456_readAcceleration); + RESET_FAKE(BMA456_getStepCount); + + // Reset GNSS fakes + RESET_FAKE(GNSS_begin); + RESET_FAKE(GNSS_getLatitude); + RESET_FAKE(GNSS_getLongitude); + RESET_FAKE(GNSS_getAltitude); + + // Set default mock behavior + PCAL9535A_begin_fake.return_val = true; + MCP79412_begin_fake.return_val = true; + MCP79412_getTime_fake.return_val = 1616161616; // Fixed timestamp for testing + Wire_endTransmission_fake.return_val = 0; // Success + Wire_available_fake.return_val = 1; // Data available + + PCA9634_begin_fake.return_val = true; + VEML3328_begin_fake.return_val = true; + PAC1934_begin_fake.return_val = true; + SHT4x_begin_fake.return_val = true; + MXC6655_begin_fake.return_val = true; + BMA456_begin_fake.return_val = true; + GNSS_begin_fake.return_val = true; + + // Set up return values for common functions + VEML3328_readRed_fake.return_val = 500; + VEML3328_readGreen_fake.return_val = 600; + VEML3328_readBlue_fake.return_val = 400; + + PAC1934_readVoltage_fake.return_val = 3.3f; + PAC1934_readCurrent_fake.return_val = 0.1f; + + GNSS_getLatitude_fake.return_val = 449673925; // Minneapolis ~44.96°N + GNSS_getLongitude_fake.return_val = -932838386; // Minneapolis ~-93.28°E + GNSS_getAltitude_fake.return_val = 25000; // 250m above MSL + } +}; + +// A simple test to verify that Google Test is working +TEST_F(KestrelBasicTest, GoogleTestWorks) { + EXPECT_TRUE(true); +} + +// A test to verify that FFF is working with our mocks +TEST_F(KestrelBasicTest, FFFWorks) { + // Call a mocked function + Wire.begin(); + + // Verify it was called + EXPECT_EQ(Wire_begin_fake.call_count, 1); +} + +// A test to verify PCAL9535A mock +TEST_F(KestrelBasicTest, PCAL9535AMockWorks) { + // Create a mock object + PCAL9535A io; + + // Use the mock + bool result = io.begin(); + io.pinMode(0, OUTPUT); + io.digitalWrite(0, HIGH); + + // Verify calls + EXPECT_TRUE(result); + EXPECT_EQ(PCAL9535A_begin_fake.call_count, 1); + EXPECT_EQ(PCAL9535A_pinMode_fake.call_count, 1); + EXPECT_EQ(PCAL9535A_digitalWrite_fake.call_count, 1); +} + +// A test to verify MCP79412 mock +TEST_F(KestrelBasicTest, MCP79412MockWorks) { + // Create a mock object + MCP79412 rtc; + + // Use the mock + bool beginResult = rtc.begin(); + time_t time = rtc.getTime(); + + // Verify calls + EXPECT_TRUE(beginResult); + EXPECT_EQ(MCP79412_begin_fake.call_count, 1); + EXPECT_EQ(MCP79412_getTime_fake.call_count, 1); + EXPECT_EQ(time, 1616161616); +} + +// Tests for the PCA9634 mock +TEST_F(KestrelBasicTest, PCA9634MockWorks) { + // Create a mock object + PCA9634 led; + + // Use the mock + bool result = led.begin(); + led.setLEDOutputMode(1, 0); + led.setBrightness(1, 100); + + // Verify calls + EXPECT_TRUE(result); + EXPECT_EQ(PCA9634_begin_fake.call_count, 1); + EXPECT_EQ(PCA9634_setLEDOutputMode_fake.call_count, 1); + EXPECT_EQ(PCA9634_setBrightness_fake.call_count, 1); +} + +// Tests for the VEML3328 mock +TEST_F(KestrelBasicTest, VEML3328MockWorks) { + // Create a mock object + VEML3328 als; + + // Use the mock + bool result = als.begin(); + uint16_t red = als.readRed(); + uint16_t green = als.readGreen(); + uint16_t blue = als.readBlue(); + + // Verify calls + EXPECT_TRUE(result); + EXPECT_EQ(VEML3328_begin_fake.call_count, 1); + EXPECT_EQ(VEML3328_readRed_fake.call_count, 1); + EXPECT_EQ(VEML3328_readGreen_fake.call_count, 1); + EXPECT_EQ(VEML3328_readBlue_fake.call_count, 1); + EXPECT_EQ(red, 500); + EXPECT_EQ(green, 600); + EXPECT_EQ(blue, 400); +} + +// Tests for the PAC1934 mock +TEST_F(KestrelBasicTest, PAC1934MockWorks) { + // Create a mock object + PAC1934 csa; + + // Use the mock + bool result = csa.begin(); + float voltage = csa.readVoltage(0); + float current = csa.readCurrent(0); + + // Verify calls + EXPECT_TRUE(result); + EXPECT_EQ(PAC1934_begin_fake.call_count, 1); + EXPECT_EQ(PAC1934_readVoltage_fake.call_count, 1); + EXPECT_EQ(PAC1934_readCurrent_fake.call_count, 1); + EXPECT_FLOAT_EQ(voltage, 3.3f); + EXPECT_FLOAT_EQ(current, 0.1f); +} + +// Tests for the SHT4x mock +TEST_F(KestrelBasicTest, SHT4xMockWorks) { + // Create a mock object + Adafruit_SHT4x sht; + + // Set test values + sht.temperature = 22.5f; + sht.humidity = 45.0f; + + // Use the mock + bool result = sht.begin(); + float temp = sht.readTemperature(); + float humid = sht.readHumidity(); + + // Verify calls + EXPECT_TRUE(result); + EXPECT_EQ(SHT4x_begin_fake.call_count, 1); + EXPECT_EQ(SHT4x_readTemperature_fake.call_count, 1); + EXPECT_EQ(SHT4x_readHumidity_fake.call_count, 1); + EXPECT_FLOAT_EQ(temp, 22.5f); + EXPECT_FLOAT_EQ(humid, 45.0f); +} + +// Tests for the MXC6655 mock +TEST_F(KestrelBasicTest, MXC6655MockWorks) { + // Create a mock object + MXC6655 accel; + + // Set test values in mock object + accel.x_acceleration = 0.1f; + accel.y_acceleration = -0.2f; + accel.z_acceleration = 0.98f; + + // Use the mock + bool result = accel.begin(); + float x, y, z; + bool readResult = accel.readAcceleration(&x, &y, &z); + + // Verify calls + EXPECT_TRUE(result); + EXPECT_EQ(MXC6655_begin_fake.call_count, 1); + EXPECT_EQ(MXC6655_readAcceleration_fake.call_count, 1); + EXPECT_FLOAT_EQ(x, 0.1f); + EXPECT_FLOAT_EQ(y, -0.2f); + EXPECT_FLOAT_EQ(z, 0.98f); +} + +// Tests for the BMA456 mock +TEST_F(KestrelBasicTest, BMA456MockWorks) { + // Access the global mock object + BMA456 accel = accel_bma456; + + // Set test values + accel.x_acceleration = 0.05f; + accel.y_acceleration = 0.1f; + accel.z_acceleration = 0.95f; + accel.step_count = 1234; + BMA456_getStepCount_fake.return_val = 1234; // Set the return value for the fake function + + // Use the mock + bool result = accel.begin(); + float x, y, z; + bool readResult = accel.readAcceleration(&x, &y, &z); + uint32_t steps = accel.getStepCount(); + + // Verify calls + EXPECT_TRUE(result); + EXPECT_EQ(BMA456_begin_fake.call_count, 1); + EXPECT_EQ(BMA456_readAcceleration_fake.call_count, 1); + EXPECT_EQ(BMA456_getStepCount_fake.call_count, 1); + EXPECT_FLOAT_EQ(x, 0.05f); + EXPECT_FLOAT_EQ(y, 0.1f); + EXPECT_FLOAT_EQ(z, 0.95f); + EXPECT_EQ(steps, 1234); +} + +// Tests for the GNSS mock +TEST_F(KestrelBasicTest, GNSSMockWorks) { + // Create a mock object + SFE_UBLOX_GNSS gps; + + // Use the mock + bool result = gps.begin(); + long lat = gps.getLatitude(); + long lon = gps.getLongitude(); + long alt = gps.getAltitude(); + + // Verify calls + EXPECT_TRUE(result); + EXPECT_EQ(GNSS_begin_fake.call_count, 1); + EXPECT_EQ(GNSS_getLatitude_fake.call_count, 1); + EXPECT_EQ(GNSS_getLongitude_fake.call_count, 1); + EXPECT_EQ(GNSS_getAltitude_fake.call_count, 1); + EXPECT_EQ(lat, 449673925); + EXPECT_EQ(lon, -932838386); + EXPECT_EQ(alt, 25000); +} \ No newline at end of file diff --git a/test/unit/FlightControl_Demo/FlightControl_DemoTest.cpp b/test/unit/FlightControl_Demo/FlightControl_DemoTest.cpp new file mode 100644 index 0000000..9c230f4 --- /dev/null +++ b/test/unit/FlightControl_Demo/FlightControl_DemoTest.cpp @@ -0,0 +1,48 @@ +// FlightControl_Demo/test/unit/Driver-_-Kestrel/kestrel_basic_test.cpp +#include "gtest/gtest.h" +#include "fff.h" +#include "MockWireDeclare.h" +#include "FlightControl_DemoTestHarness.h" + +// Test fixture +class FlightControlBasicTest : public ::testing::Test { +protected: + void SetUp() override { + // Reset all fakes before each test + RESET_FAKE(Wire_begin); + RESET_FAKE(Wire_setClock); + RESET_FAKE(Wire_beginTransmission); + RESET_FAKE(Wire_endTransmission); + RESET_FAKE(Wire_requestFrom_3args); + RESET_FAKE(Wire_requestFrom_2args); + RESET_FAKE(Wire_available); + RESET_FAKE(Wire_read); + RESET_FAKE(Wire_write_uint8); + RESET_FAKE(Wire_write_buffer); + } +}; + +// A simple test to verify that Google Test is working +TEST_F(FlightControlBasicTest, GoogleTestWorks) { + EXPECT_TRUE(true); +} + +// A test to verify that FFF is working with our mocks +TEST_F(FlightControlBasicTest, FFFWorks) { + // Call a mocked function + Wire.begin(); + + // Verify it was called + EXPECT_EQ(Wire_begin_fake.call_count, 1); +} + +TEST_F(FlightControlBasicTest, GetDataStringFormat) { + // Setup any necessary state first + + // Call the function through the harness + String result = test_harness::callGetDataString(); + + // Assert on expected format + EXPECT_TRUE(result.startsWith("{\"Data\":{")); + // More assertions +} \ No newline at end of file diff --git a/test/unit/FlightControl_Demo/FlightControl_DemoTestHarness.cpp b/test/unit/FlightControl_Demo/FlightControl_DemoTestHarness.cpp new file mode 100644 index 0000000..3ac3246 --- /dev/null +++ b/test/unit/FlightControl_Demo/FlightControl_DemoTestHarness.cpp @@ -0,0 +1,14 @@ +#include "FlightControl_DemoTestHarness.h" + +// Declare the functions from the original file +extern String getDataString(); +extern String getErrorString(); +extern String getDiagnosticString(uint8_t level); +extern void logEvents(uint8_t type, uint8_t destination); + +namespace FlightControl_DemoTestHarness { + String callGetDataString() { + return getDataString(); + } + // Other functions +} \ No newline at end of file diff --git a/test/unit/FlightControl_Demo/FlightControl_DemoTestHarness.h b/test/unit/FlightControl_Demo/FlightControl_DemoTestHarness.h new file mode 100644 index 0000000..beeefff --- /dev/null +++ b/test/unit/FlightControl_Demo/FlightControl_DemoTestHarness.h @@ -0,0 +1,12 @@ +// test/unit/FlightControl_Demo/test_harness.h +#pragma once + +#include "../../mocks/MockParticle.h" + +namespace test_harness { + String callGetDataString(); + String callGetErrorString(); + String callGetDiagnosticString(uint8_t level); + void callLogEvents(uint8_t type, uint8_t destination); + // Add more as needed +} \ No newline at end of file From f1e7ea386bf17f12fcb1228387ce3b0e0eac1a8d Mon Sep 17 00:00:00 2001 From: Zach Radlicz Date: Tue, 25 Mar 2025 13:47:03 -0500 Subject: [PATCH 2/2] removed Product ID macro based on workflow results --- src/FlightControl_Demo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FlightControl_Demo.cpp b/src/FlightControl_Demo.cpp index 1815ba8..8ddc3e7 100644 --- a/src/FlightControl_Demo.cpp +++ b/src/FlightControl_Demo.cpp @@ -95,7 +95,7 @@ namespace LogModes { }; /////////////////////////// BEGIN USER CONFIG //////////////////////// -PRODUCT_ID(18596) //Configured based on the target product, comment out if device has no product +//PRODUCT_ID(18596) //Configured based on the target product, comment out if device has no product PRODUCT_VERSION(30) //Configure based on the firmware version you wish to create, check product firmware page to see what is currently the highest number const int backhaulCount = 4; //Number of log events before backhaul is performed