diff --git a/.codechecker.json b/.codechecker.json new file mode 100644 index 00000000..4fc6da7e --- /dev/null +++ b/.codechecker.json @@ -0,0 +1,14 @@ +{ + "analyze": [ + "--analyzers", + "clangsa", + "cppcheck", + "--disable", + "severity:STYLE", + "--file", + "*/open-earable-v2/*", + "--capture-analysis-output", + "--report-hash", + "context-free-v2" + ] +} diff --git a/.github/scripts/render_unit_test_comment.py b/.github/scripts/render_unit_test_comment.py new file mode 100644 index 00000000..cb2ad8a7 --- /dev/null +++ b/.github/scripts/render_unit_test_comment.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +"""Render Twister results as a concise GitHub pull-request comment for CI.""" + +from __future__ import annotations + +import argparse +import html +import json +import re +from pathlib import Path + +MARKER = "" +UNITY_RESULT = re.compile(r"^.*:\d+:test_.+:(?:PASS|FAIL)(?::.*)?$") +IMPORTANT_OUTPUT = re.compile( + r"(?:\b(?:ERROR|FAIL|FAILED)\b|PROJECT EXECUTION|Tests? \d+ Failures)", + re.IGNORECASE, +) + + +def load_suites(report_path: Path) -> list[dict]: + if not report_path.is_file(): + return [] + + try: + report = json.loads(report_path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError): + return [] + return report.get("testsuites", []) + + +def status_icon(status: str) -> str: + return { + "passed": "✅", + "failed": "❌", + "error": "🚨", + "skipped": "⏭️", + "filtered": "⏭️", + "blocked": "🚫", + }.get(status.lower(), "❔") + + +def failure_details(console_path: Path, suites: list[dict]) -> str: + console = "" + if console_path.is_file(): + console = console_path.read_text(encoding="utf-8", errors="replace") + + selected = [] + for line in console.splitlines(): + stripped = line.strip() + if UNITY_RESULT.match(stripped) or IMPORTANT_OUTPUT.search(stripped): + selected.append(stripped) + + if not selected: + for suite in suites: + if suite.get("status", "").lower() not in {"passed", "skipped", "filtered"}: + reason = suite.get("reason") or "No detailed failure message was reported." + selected.append(f"{suite.get('name', 'unknown suite')}: {reason}") + + if not selected and console: + selected = [line for line in console.splitlines() if line.strip()][-40:] + + # Keep comments readable and below GitHub's comment-size limit. + return "\n".join(selected[-100:])[-12000:] + + +def render(args: argparse.Namespace) -> str: + suites = load_suites(args.report) + statuses = [str(suite.get("status", "unknown")).lower() for suite in suites] + failed = sum(status not in {"passed", "skipped", "filtered"} for status in statuses) + passed = statuses.count("passed") + skipped = sum(status in {"skipped", "filtered"} for status in statuses) + succeeded = args.outcome == "success" and failed == 0 + + heading = "✅ Unit tests passed" if succeeded else "❌ Unit tests failed" + lines = [MARKER, f"## {heading}", ""] + lines.append( + f"**{passed} passed**, **{failed} failed/error**, **{skipped} skipped** " + f"— [view workflow run]({args.run_url})" + ) + + if suites: + lines.extend(["", "| Test scenario | Platform | Result |", "|---|---|---|"]) + for suite in suites: + status = str(suite.get("status", "unknown")) + lines.append( + f"| `{suite.get('name', 'unknown')}` " + f"| `{suite.get('platform', 'unknown')}` " + f"| {status_icon(status)} {status} |" + ) + elif not succeeded: + lines.extend(["", "Twister did not produce a JSON report; this may be an infrastructure failure."]) + + if not succeeded: + details = failure_details(args.console, suites) + if details: + lines.extend( + [ + "", + "
", + "Failure details", + "", + f"
{html.escape(details)}
", + "
", + ] + ) + + lines.extend( + [ + "", + f"[Download the `unit-test-results` artifact]({args.artifact_url}) " + "for full Twister reports and logs.", + ] + ) + return "\n".join(lines) + "\n" + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument("--report", type=Path, required=True) + parser.add_argument("--console", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--outcome", choices=("success", "failure", "cancelled", "skipped"), required=True) + parser.add_argument("--run-url", required=True) + parser.add_argument("--artifact-url", required=True) + return parser.parse_args() + + +def main() -> None: + args = parse_args() + args.output.write_text(render(args), encoding="utf-8") + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/build_firmware.yaml b/.github/workflows/build_firmware.yaml index c2802f06..8a70d08f 100644 --- a/.github/workflows/build_firmware.yaml +++ b/.github/workflows/build_firmware.yaml @@ -28,6 +28,7 @@ jobs: # Clone the repo to a subdirectory, so we can initialize the Zephyr # workspace in the parent directory. path: zephyr-workspace/open-earable-v2 + submodules: recursive - name: Cache/Install APT Packages uses: awalsh128/cache-apt-pkgs-action@latest diff --git a/.github/workflows/codechecker.yaml b/.github/workflows/codechecker.yaml new file mode 100644 index 00000000..3f605c08 --- /dev/null +++ b/.github/workflows/codechecker.yaml @@ -0,0 +1,189 @@ +name: CodeChecker static analysis + +on: + pull_request: {} + workflow_dispatch: {} + +permissions: + contents: read + pull-requests: write + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + CODECHECKER_VERSION: "6.28.2" + ZEPHYR_SDK_VERSION: "0.17.0" + +jobs: + codechecker: + name: Advisory analysis + runs-on: ubuntu-latest + + steps: + - name: Check out application + uses: actions/checkout@v6 + with: + fetch-depth: 0 + path: zephyr-workspace/open-earable-v2 + submodules: recursive + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install --yes \ + ccache \ + clang \ + cppcheck \ + device-tree-compiler \ + g++-multilib \ + gcc-multilib \ + gperf \ + libmagic1 \ + make \ + ninja-build \ + wget \ + xz-utils + + - name: Install Python tools + run: | + python -m pip install --upgrade pip + python -m pip install "codechecker==${CODECHECKER_VERSION}" west + + - name: Install Zephyr SDK + working-directory: zephyr-workspace + run: | + wget --quiet \ + "https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v${ZEPHYR_SDK_VERSION}/zephyr-sdk-${ZEPHYR_SDK_VERSION}_linux-x86_64_minimal.tar.xz" + tar xf "zephyr-sdk-${ZEPHYR_SDK_VERSION}_linux-x86_64_minimal.tar.xz" + "${PWD}/zephyr-sdk-${ZEPHYR_SDK_VERSION}/setup.sh" -c -t arm-zephyr-eabi + + - name: Initialize Zephyr workspace + working-directory: zephyr-workspace + run: | + west init -l open-earable-v2 + west update --narrow -o=--depth=1 + python -m pip install -r zephyr/scripts/requirements.txt + + - name: Register custom board + working-directory: zephyr-workspace + run: | + mkdir -p zephyr/boards/arm + cp -R open-earable-v2/boards/teco/. zephyr/boards/arm/ + + - name: Run CodeChecker + id: analysis + working-directory: zephyr-workspace + shell: bash + run: | + mkdir -p codechecker-output + set -o pipefail + set +e + west build \ + --no-sysbuild \ + --board openearable_v2/nrf5340/cpuapp \ + --pristine=always \ + open-earable-v2 \ + -- \ + -DFILE_SUFFIX=fota \ + -DZEPHYR_SCA_VARIANT=codechecker \ + -DCODECHECKER_ANALYZE_JOBS=4 \ + -DCODECHECKER_CONFIG_FILE="${PWD}/open-earable-v2/.codechecker.json" \ + -DCODECHECKER_EXPORT=html,json \ + 2>&1 | tee codechecker-output/analysis.log + analysis_exit=${PIPESTATUS[0]} + echo "exit_code=${analysis_exit}" >> "${GITHUB_OUTPUT}" + echo "CodeChecker/build exit code: ${analysis_exit}" >> codechecker-output/analysis.log + if [ "${analysis_exit}" -ne 0 ]; then + echo "::error::CodeChecker build failed; see codechecker-output/analysis.log" + exit "${analysis_exit}" + fi + if [ ! -s build/sca/codechecker/codechecker.json ]; then + echo "::error::CodeChecker did not produce build/sca/codechecker/codechecker.json" + exit 1 + fi + if ! find build/sca/codechecker/codechecker.plist -maxdepth 1 -type f \ + -name '*.plist' -print -quit | grep -q .; then + echo "::error::CodeChecker completed without analyzing any compilation commands" + exit 1 + fi + + - name: Prepare PR annotations + if: always() + working-directory: zephyr-workspace/open-earable-v2 + env: + CODECHECKER_REPORT: ../build/sca/codechecker/codechecker.json + COMMENT_OUTPUT: ../codechecker-output/pr-comment.md + GITHUB_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + python scripts/ci/codechecker_pr_report.py \ + --report "${CODECHECKER_REPORT}" \ + --analysis-dir ../build/sca/codechecker/codechecker.plist \ + --comment-output "${COMMENT_OUTPUT}" \ + --base "${PR_BASE_SHA}" \ + --head "${PR_HEAD_SHA}" + + - name: Collect complete report + if: always() + working-directory: zephyr-workspace + run: | + mkdir -p codechecker-output + if [ -d build/sca/codechecker ]; then + cp -R build/sca/codechecker codechecker-output/report + fi + if [ -f build/compile_commands.json ]; then + cp build/compile_commands.json codechecker-output/compile_commands.json + fi + { + echo "CodeChecker ${CODECHECKER_VERSION}" + echo "Analysis exit code: ${{ steps.analysis.outputs.exit_code }}" + echo "Commit: ${{ github.sha }}" + } > codechecker-output/run-information.txt + + - name: Upload complete CodeChecker report + if: always() + uses: actions/upload-artifact@v7 + with: + name: codechecker-report-${{ github.run_number }} + path: zephyr-workspace/codechecker-output + if-no-files-found: error + retention-days: 30 + + - name: Publish advisory PR comment + if: always() && github.event_name == 'pull_request' + continue-on-error: true + uses: actions/github-script@v8 + env: + COMMENT_FILE: zephyr-workspace/codechecker-output/pr-comment.md + with: + script: | + const fs = require('fs'); + const marker = ''; + const body = fs.readFileSync(process.env.COMMENT_FILE, 'utf8'); + const { owner, repo } = context.repo; + const issue_number = context.issue.number; + const comments = await github.paginate( + github.rest.issues.listComments, + { owner, repo, issue_number, per_page: 100 } + ); + const previous = comments.find( + comment => comment.user.type === 'Bot' && comment.body.includes(marker) + ); + if (previous) { + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: previous.id, + body + }); + } else { + await github.rest.issues.createComment({ owner, repo, issue_number, body }); + } diff --git a/.github/workflows/compiler_warnings.yaml b/.github/workflows/compiler_warnings.yaml new file mode 100644 index 00000000..7e605d59 --- /dev/null +++ b/.github/workflows/compiler_warnings.yaml @@ -0,0 +1,143 @@ +name: Report Compiler Warnings + +on: + pull_request: {} + +permissions: + contents: read + pull-requests: write + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + warnings: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + path: zephyr-workspace/open-earable-v2 + submodules: recursive + + - name: Install build dependencies + uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: ccache cmake device-tree-compiler dfu-util file gcc gcc-multilib g++-multilib gperf libmagic1 libsdl2-dev make ninja-build python3-dev python3-pip python3-setuptools python3-tk python3-wheel wget xz-utils + version: 1.0 + execute_install_scripts: true + + - name: Set up Zephyr workspace + run: | + pip3 install west + wget -q https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.17.0/zephyr-sdk-0.17.0_linux-x86_64_minimal.tar.xz + tar xf zephyr-sdk-0.17.0_linux-x86_64_minimal.tar.xz -C ~/ + ~/zephyr-sdk-0.17.0/setup.sh -c -t arm-zephyr-eabi + cd zephyr-workspace + west init -l open-earable-v2 + west update --narrow -o=--depth=1 + pip3 install -r zephyr/scripts/requirements.txt + + - name: Register custom board + run: | + mkdir -p zephyr-workspace/zephyr/boards/arm + cp -R zephyr-workspace/open-earable-v2/boards/teco/. zephyr-workspace/zephyr/boards/arm/ + + - name: Build with extended warnings + id: build + working-directory: zephyr-workspace + run: | + set -o pipefail + west build -d build-warnings --board openearable_v2/nrf5340/cpuapp \ + --pristine=always open-earable-v2 -- \ + -DFILE_SUFFIX=fota \ + -DCMAKE_C_COMPILER_LAUNCHER="$PWD/open-earable-v2/tools/ci/extended-warning-compiler-launcher.sh" \ + -DCMAKE_CXX_COMPILER_LAUNCHER="$PWD/open-earable-v2/tools/ci/extended-warning-compiler-launcher.sh" \ + 2>&1 | tee warning-build.log + + - name: Prepare warning report + if: always() + run: | + mkdir -p warning-report + log=zephyr-workspace/warning-build.log + clean="${RUNNER_TEMP:-/tmp}/warning-build.log" + all=warning-report/compiler-warnings.txt + project=warning-report/project-warnings.txt + config=warning-report/configuration-warnings.txt + sed -E 's/\x1B\[[0-9;]*[mK]//g' "$log" 2>/dev/null > "$clean" + grep -Ei '(^|[[:space:]])warning:' "$clean" > "$all" || true + grep '/zephyr-workspace/open-earable-v2/' "$all" \ + | sed -E 's#^.*/zephyr-workspace/open-earable-v2/##' \ + | awk '!seen[$0]++' > "$project" || true + awk ' + function flush() { + if (capturing && !seen[warning]++) { + print warning "\n" + } + capturing = 0 + warning = "" + } + function is_configuration_warning() { + return $0 ~ /^[[:space:]]*(---[[:space:]]*)?[Ww][Aa][Rr][Nn][Ii][Nn][Gg]:/ || + $0 ~ /^CMake (Developer )?Warning([[:space:]]|$)/ + } + function is_devicetree_warning() { + return $0 ~ /:[[:space:]]+Warning \([^)]*\):/ + } + { + if (is_devicetree_warning()) { + flush() + if (!seen[$0]++) { + print $0 "\n" + } + next + } + if (capturing && ($0 ~ /^[[:space:]]*$/ || is_configuration_warning())) { + flush() + } + if (!capturing && is_configuration_warning()) { + capturing = 1 + } + if (capturing) { + warning = warning (warning == "" ? "" : "\n") $0 + } + } + END { flush() } + ' "$clean" > "$config" || true + + comment=warning-report/pr-comment.md + { + echo '## Compiler warnings' + echo + if [ '${{ steps.build.outcome }}' = success ]; then + echo 'The extended-warning build completed successfully.' + else + echo '⚠️ The extended-warning build failed; this report may be incomplete.' + fi + echo + echo '### Application compiler warnings' + if [ -s "$project" ]; then + echo '
Show warnings' + echo; echo '```text'; head -n 100 "$project"; echo '```'; echo '
' + else + echo 'None.' + fi + echo + echo "[View this workflow run]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID)" + } | tee "$comment" >> "$GITHUB_STEP_SUMMARY" + + - name: Upload complete warning report + if: always() + uses: actions/upload-artifact@v4 + with: + name: compiler-warnings + path: warning-report + if-no-files-found: error + + - name: Comment on pull request + if: always() + continue-on-error: true + uses: thollander/actions-comment-pull-request@v3 + with: + file-path: warning-report/pr-comment.md + comment-tag: compiler-warnings diff --git a/.github/workflows/release_firmware.yaml b/.github/workflows/release_firmware.yaml index 9f591176..ebe90ad5 100644 --- a/.github/workflows/release_firmware.yaml +++ b/.github/workflows/release_firmware.yaml @@ -43,6 +43,24 @@ jobs: ls openearable_v2_fota (cd "openearable_v2_fota" && ls && zip -r ../openearable_v2_fota.zip *) + - name: Add version to release asset names + id: release-assets + env: + RELEASE_TAG: ${{ inputs.release_tag }} + run: | + set -euo pipefail + + # v2.2.8 -> 2.2.8 + version="${RELEASE_TAG#v}" + firmware_name="openearable_v2_firmware_${version}.elf" + fota_name="openearable_v2_fota_${version}.zip" + + mv openearable_v2_firmware.elf "$firmware_name" + mv openearable_v2_fota.zip "$fota_name" + + echo "firmware=$firmware_name" >> "$GITHUB_OUTPUT" + echo "fota=$fota_name" >> "$GITHUB_OUTPUT" + - name: Create Github Release & Upload Firmware + FOTA if: startsWith(github.ref, 'refs/tags/${{ inputs.release_tag }}') uses: softprops/action-gh-release@v2 @@ -52,7 +70,7 @@ jobs: draft: false prerelease: false files: | - openearable_v2_firmware.elf - openearable_v2_fota.zip + ${{ steps.release-assets.outputs.firmware }} + ${{ steps.release-assets.outputs.fota }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/unit_tests.yaml b/.github/workflows/unit_tests.yaml new file mode 100644 index 00000000..68adca61 --- /dev/null +++ b/.github/workflows/unit_tests.yaml @@ -0,0 +1,141 @@ +name: Unit Tests + +on: + workflow_dispatch: + pull_request: {} + push: + branches: + - main + +permissions: + contents: read + issues: write + pull-requests: write + +concurrency: + group: unit-tests-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + ZEPHYR_TOOLCHAIN_VARIANT: host + +jobs: + unity-native-sim: + name: Unity tests (native_sim 64-bit) + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Check out application + uses: actions/checkout@v5 + with: + # Keep the application below the west workspace root. + path: zephyr-workspace/open-earable-v2 + + - name: Install host dependencies + uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: git cmake ninja-build gperf device-tree-compiler make gcc g++ ruby python3-dev python3-pip python3-setuptools python3-wheel + version: 1.0 + execute_install_scripts: true + + - name: Cache Python packages + uses: actions/cache@v5 + with: + path: ~/.cache/pip + key: unit-tests-${{ runner.os }}-pip-${{ hashFiles('zephyr-workspace/open-earable-v2/west.yml') }} + restore-keys: | + unit-tests-${{ runner.os }}-pip- + + - name: Initialize nRF Connect SDK workspace + run: | + python3 -m pip install west + cd zephyr-workspace + west init -l open-earable-v2 + west update --narrow -o=--depth=1 + python3 -m pip install -r zephyr/scripts/requirements.txt + + - name: Run Unity tests with Twister + id: twister + continue-on-error: true + run: | + set -o pipefail + cd zephyr-workspace + python3 zephyr/scripts/twister \ + -T open-earable-v2/tests/unit \ + -p native_sim/native/64 \ + --inline-logs \ + --outdir twister-out \ + 2>&1 | tee twister-console.log + + - name: Upload Twister reports and logs + id: unit_test_artifacts + if: always() + uses: actions/upload-artifact@v6 + with: + name: unit-test-results + path: | + zephyr-workspace/twister-console.log + zephyr-workspace/twister-out/twister*.xml + zephyr-workspace/twister-out/twister.json + zephyr-workspace/twister-out/**/handler.log + zephyr-workspace/twister-out/**/build.log + if-no-files-found: warn + retention-days: 14 + + - name: Prepare test summary + if: always() + env: + TWISTER_OUTCOME: ${{ steps.twister.outcome }} + WORKFLOW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + ARTIFACT_URL: ${{ steps.unit_test_artifacts.outputs.artifact-url }} + run: | + python3 zephyr-workspace/open-earable-v2/.github/scripts/render_unit_test_comment.py \ + --report zephyr-workspace/twister-out/twister.json \ + --console zephyr-workspace/twister-console.log \ + --output unit-test-comment.md \ + --outcome "$TWISTER_OUTCOME" \ + --run-url "$WORKFLOW_RUN_URL" \ + --artifact-url "$ARTIFACT_URL" + cat unit-test-comment.md >> "$GITHUB_STEP_SUMMARY" + + - name: Publish test summary on pull request + if: always() && github.event_name == 'pull_request' + uses: actions/github-script@v8 + with: + script: | + const fs = require('fs'); + const marker = ''; + const body = fs.readFileSync('unit-test-comment.md', 'utf8'); + const comments = await github.paginate( + github.rest.issues.listComments, + { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + per_page: 100, + } + ); + const existing = comments.find( + comment => comment.user.type === 'Bot' && comment.body.includes(marker) + ); + + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body, + }); + } + + - name: Fail workflow when tests failed + if: steps.twister.outcome == 'failure' + run: exit 1 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..5697f77b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "protocol"] + path = protocol + url = https://github.com/OpenEarable/protocol.git diff --git a/CMakeLists.txt b/CMakeLists.txt index ffa4b8fa..2d73b3c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,9 @@ add_subdirectory(src/SD_Card/SDLogger) add_subdirectory(src/SD_Card/Benchmark) add_subdirectory(src/SD_Card/SD_Card_Manager) add_subdirectory(src/time_sync) +add_subdirectory(protocol/generated/c) + +target_link_libraries(open_earable_protocols PRIVATE zephyr_interface) ## Cirrus Logic if (CONFIG_HW_CODEC_CIRRUS_LOGIC) @@ -88,3 +91,5 @@ endif() include(version.cmake) include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) + +target_link_libraries(app PRIVATE open_earable_protocols) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 350fdd3b..28dfd513 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,6 +14,9 @@ This repository contains the OpenEarable 2 firmware for the `openearable_v2/nrf5 The main firmware lives at the repository root and is built through Zephyr and the nRF Connect SDK. +Unit tests live under `tests/unit`. See [tests/README.md](tests/README.md) for the +GitHub Actions workflow, optional local execution, and suite structure. + - `src/audio`, `src/bluetooth`, `src/modules`: runtime audio, Bluetooth, and application modules. - `src/SensorManager`, `src/Battery`, `src/SD_Card`, `src/time_sync`: sensor, power, storage, and synchronization subsystems. - `src/drivers`, `src/Wire`, `src/utils`, `src/buttons`, `src/ParseInfo`: reusable device support and shared utilities. @@ -145,6 +148,33 @@ This repository expects code to be documented. - Avoid unrelated formatting churn. - Keep headers and source files aligned: declarations, ownership, and invariants should be easy to trace. +### Static Analysis + +GitHub Actions runs advisory CodeChecker analysis for pull requests; findings +and analyzer failures do not block merging. It analyzes the FOTA application +without sysbuild using Clang Static Analyzer and Cppcheck. Style findings, +Clang-Tidy, formatting, and automatic fixes are not enabled. The analyzer +configuration is in [.codechecker.json](.codechecker.json). + +Local execution is optional. In an nRF Connect SDK `v3.0.1` Linux or WSL +workspace, install CodeChecker `6.28.2`, Clang, and Cppcheck, then run: + +```bash +sudo apt-get install clang cppcheck +python -m pip install "codechecker==6.28.2" +codechecker_config="$(pwd)/.codechecker.json" +west build --no-sysbuild --board openearable_v2/nrf5340/cpuapp \ + --pristine=always . -- -DFILE_SUFFIX=fota \ + -DZEPHYR_SCA_VARIANT=codechecker \ + -DCODECHECKER_CONFIG_FILE="${codechecker_config}" \ + -DCODECHECKER_EXPORT=html,json +``` + +Reports are written to `/sca/codechecker/`. See Nordic's +[CodeChecker documentation for nRF Connect SDK 3.0.1](https://nrfconnectdocs.nordicsemi.com/ncs/3.0.1/zephyr/develop/sca/codechecker.html) +or the corresponding [upstream Zephyr 4.0 documentation](https://docs.zephyrproject.org/4.0.0/develop/sca/codechecker.html) +for the available options. + ## Validation Before Opening A Pull Request At minimum, contributors should validate that the firmware still builds with the repository's supported configuration. diff --git a/README.md b/README.md index 78b3f08a..1c032739 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,16 @@ - In the **nRF Connect** tab, select **"Manage SDK"**. - Install **SDK version 3.0.1**. -7. **Open the Firmware Folder in VS Code** +7. **Clone or Update the Firmware Repository and Open It in VS Code** + - For a new checkout, clone the repository together with its submodules: + ```bash + git clone --recurse-submodules https://github.com/OpenEarable/open-earable-2.git + ``` + - If the repository is already checked out, initialize or update its submodules from the repository root: + ```bash + git submodule update --init --recursive + ``` + - The `protocol` submodule provides the generated C sources in `protocol/generated/c` that are required during CMake configuration. - Use `File > Open Folder` or drag-and-drop the firmware directory into VS Code. - OR in the **APPLICATIONS** section of the nRF Connect tab: - Select `Open Exisiting Application`. @@ -195,4 +204,3 @@ If you are using OpenEarable, please cite is as follows: ``` - diff --git a/VERSION b/VERSION index 8ba3fd9f..7b19d147 100644 --- a/VERSION +++ b/VERSION @@ -1,5 +1,5 @@ VERSION_MAJOR = 2 VERSION_MINOR = 2 -PATCHLEVEL = 7 +PATCHLEVEL = 8 VERSION_TWEAK = 0 EXTRAVERSION = diff --git a/doc/dmic_gain_reference.rst b/doc/dmic_gain_reference.rst new file mode 100644 index 00000000..627973a0 --- /dev/null +++ b/doc/dmic_gain_reference.rst @@ -0,0 +1,189 @@ +.. _dmic_gain_reference: + +DMIC Microphone Gain Control Reference +####################################### + +.. contents:: + :local: + :depth: 2 + +Overview +******** + +The DMIC (Digital Microphone) gain on the ADAU1860 codec can now be adjusted over Bluetooth using the Audio Config Service characteristic ``BT_UUID_DMIC_GAIN`` (UUID: ``1410df99-5f68-4ebb-a7c7-5e0fb9ae7557``). + +Hardware Register Mapping +************************** + +The ADAU186x names the digital microphone inputs by channel number. This +firmware keeps those chip-level names at the driver boundary and maps them to +board-specific microphone positions in ``hw_codec_adau1860.cpp``: + +- ``DMIC_VOL0`` / DMIC channel 0 controls the **external** microphone. +- ``DMIC_VOL1`` / DMIC channel 1 controls the **internal** microphone. + +Based on the ADAU186x datasheet (register DMIC_VOL0 @ address 0x4000C045): + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - Register Value + - Gain (dB) + - Description + * - 0x00 + - +24 + - Maximum positive gain + * - 0x01 + - +23.625 + - + * - 0x02 + - +23.25 + - + * - ... + - ... + - Each step = -0.375 dB + * - 0x3F + - +0.375 + - + * - **0x40** + - **0** + - **Reset/Default value** + * - 0x41 + - -0.375 + - + * - ... + - ... + - Each step = -0.375 dB + * - 0xFD + - -70.875 + - + * - 0xFE + - -71.25 + - + * - 0xFF + - ∞ + - Mute (infinite attenuation) + +Gain Calculation +***************** + +For any register value ``reg`` (0x00–0x3F for positive gains): + +- **Gain (dB) = 24 - (reg × 0.375)** + +For negative gains (0x41–0xFD): + +- **Gain (dB) = -(reg - 0x40) × 0.375** + +Examples +======== + +- ``0x00`` → 24 - (0 × 0.375) = **+24 dB** +- ``0x10`` → 24 - (16 × 0.375) = **+18 dB** +- ``0x20`` → 24 - (32 × 0.375) = **+12 dB** (initial value in this firmware) +- ``0x40`` → **0 dB** +- ``0x50`` → -(80 - 64) × 0.375 = **-6 dB** +- ``0x60`` → -(96 - 64) × 0.375 = **-12 dB** + +BLE Protocol +************ + +Characteristic Details +====================== + +- **UUID**: ``0x1410df99-5f68-4ebb-a7c7-5e0fb9ae7557`` (under Audio Config Service) +- **Properties**: Read, Write +- **Payload**: 2 bytes + +Write Command +============= + +Send 2 bytes ``[external_gain_reg, internal_gain_reg]``: + +.. code-block:: text + + Byte 0: External microphone gain register (DMIC_VOL0, 0x00–0xFF) + Byte 1: Internal microphone gain register (DMIC_VOL1, 0x00–0xFF) + +Read Response +============= + +Returns 2 bytes showing current DMIC gain as ``[external_gain_reg, internal_gain_reg]``. + +Usage Examples +************** + +Set both microphones to +12 dB +=============================== + +Send: ``[0x20, 0x20]`` + +Set external to +18 dB, internal to +6 dB +========================================= + +Send: ``[0x10, 0x30]`` + +(0x10 = 24 - 16×0.375 = +18 dB, 0x30 = 24 - 48×0.375 = +6 dB) + +Set to 0 dB (neutral/reset) +============================ + +Send: ``[0x40, 0x40]`` + +Mute both microphones +===================== + +Send: ``[0xFF, 0xFF]`` + +Set external to -12 dB (96 = 0x60), internal to 0 dB +===================================================== + +Send: ``[0x60, 0x40]`` + +API Reference (C/C++) +********************* + +Functions +========= + +.. code-block:: c + + // Set DMIC gain for both microphones + // gain_external_reg, gain_internal_reg: register values (0x00–0xFF) + int hw_codec_mic_gain_set(uint8_t gain_external_reg, uint8_t gain_internal_reg); + + // Get current DMIC gain register value for external/internal microphone + uint8_t hw_codec_mic_gain_get_external(void); + uint8_t hw_codec_mic_gain_get_internal(void); + +Example C Code +============== + +.. code-block:: cpp + + // Set external and internal DMIC to +12 dB + hw_codec_mic_gain_set(0x20, 0x20); + + // Read back the values + uint8_t external = hw_codec_mic_gain_get_external(); // Should return 0x20 + uint8_t internal = hw_codec_mic_gain_get_internal(); // Should return 0x20 + +Implementation Details +********************** + +- **File**: ``src/modules/hw_codec_adau1860.cpp`` +- **Registers Written**: external mic ``DMIC_VOL0`` (0x4000C045), internal mic ``DMIC_VOL1`` (0x4000C046) +- **BLE Handler**: ``src/bluetooth/gatt_services/audio_config_service.c`` +- **Logging**: INFO level logs show when gain is changed via BLE + +Notes +***** + +- Each register value change takes effect immediately on the hardware. +- Default at startup: 0x20 (+12 dB) for both channels (set in ``ADAU1860::begin()``). +- The characteristic is read/write; clients can query current gain and set new values. +- The 0.375 dB step provides fine-grained control with 256 possible values. +- To verify the physical mapping experimentally, set one microphone to mute + (0xFF) and the other to 0 dB (0x40), then speak into the external and + internal microphones separately. diff --git a/include/openearable_common.h b/include/openearable_common.h index 12c728a5..40a1c38c 100644 --- a/include/openearable_common.h +++ b/include/openearable_common.h @@ -115,7 +115,6 @@ struct sensor_config { uint8_t storageOptions; } __attribute__((packed)); - struct battery_settings { float u_nominal; float u_term; diff --git a/prj.conf b/prj.conf index 5741e028..aae3b8f7 100644 --- a/prj.conf +++ b/prj.conf @@ -72,8 +72,7 @@ CONFIG_SD_LOG_LEVEL_OFF=y # Suppress LOG_INF messages from hci_core CONFIG_BT_HCI_CORE_LOG_LEVEL_WRN=y -# Michael: missing somehow -CONFIG_NRF5340_AUDIO_CS47L63_DRIVER=y +CONFIG_NRF5340_AUDIO_ADAU1860_DRIVER=y CONFIG_NRF5340_AUDIO_POWER_MEASUREMENT=n CONFIG_NRF5340_AUDIO_SD_CARD_MODULE=y @@ -114,6 +113,10 @@ CONFIG_SFLOAT=y CONFIG_FPU=y +CONFIG_CMSIS_DSP=y +CONFIG_CMSIS_DSP_TRANSFORM=y +CONFIG_CMSIS_DSP_COMPLEXMATH=y + CONFIG_NEWLIB_LIBC=y CONFIG_NEWLIB_LIBC_FLOAT_PRINTF=y CONFIG_NEWLIB_LIBC_FLOAT_SCANF=y @@ -137,6 +140,7 @@ CONFIG_BT_USER_PHY_UPDATE=y CONFIG_BT_CTLR_DATA_LENGTH_MAX=251 CONFIG_BT_BUF_ACL_RX_SIZE=502 +CONFIG_BT_BUF_ACL_RX_COUNT_EXTRA=8 CONFIG_BT_BUF_ACL_TX_SIZE=502 CONFIG_BT_L2CAP_TX_MTU=498 CONFIG_BT_CTLR_PHY_2M=y @@ -197,7 +201,13 @@ CONFIG_FS_FATFS_EXFAT=y # Enable exFAT support CONFIG_FDSP=y +# CMSIS-DSP Configuration +CONFIG_CMSIS_DSP=y +CONFIG_CMSIS_DSP_TRANSFORM=y +CONFIG_CMSIS_DSP_COMPLEXMATH=y + CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 +CONFIG_AUDIO_RESPONSE_MAX_SAMPLES=32000 #CONFIG_USB_MASS_STORAGE=y #CONFIG_MASS_STORAGE_STACK_SIZE=1024 diff --git a/prj_fota.conf b/prj_fota.conf index 0aee2b1b..746c7a26 100644 --- a/prj_fota.conf +++ b/prj_fota.conf @@ -85,8 +85,7 @@ CONFIG_FLASH_MAP=y CONFIG_SPI_NOR=y CONFIG_SPI_NOR_SFDP_DEVICETREE=y -# Michael: missing somehow -CONFIG_NRF5340_AUDIO_CS47L63_DRIVER=y +CONFIG_NRF5340_AUDIO_ADAU1860_DRIVER=y CONFIG_NRF5340_AUDIO_POWER_MEASUREMENT=n CONFIG_NRF5340_AUDIO_SD_CARD_MODULE=y @@ -136,6 +135,7 @@ CONFIG_BT_USER_PHY_UPDATE=y CONFIG_BT_CTLR_DATA_LENGTH_MAX=251 CONFIG_BT_BUF_ACL_RX_SIZE=502 +CONFIG_BT_BUF_ACL_RX_COUNT_EXTRA=8 CONFIG_BT_BUF_ACL_TX_SIZE=502 CONFIG_BT_L2CAP_TX_MTU=498 CONFIG_BT_CTLR_PHY_2M=y @@ -262,3 +262,9 @@ CONFIG_MCUMGR_GRP_OS_RESET_HOOK=y #CONFIG_MCUMGR_GRP_SETTINGS_ACCESS_HOOK=y CONFIG_SETUP_FUEL_GAUGE=y + +CONFIG_CMSIS_DSP=y +CONFIG_CMSIS_DSP_TRANSFORM=y +CONFIG_CMSIS_DSP_COMPLEXMATH=y + +CONFIG_AUDIO_RESPONSE_MAX_SAMPLES=32000 diff --git a/protocol b/protocol new file mode 160000 index 00000000..53221c84 --- /dev/null +++ b/protocol @@ -0,0 +1 @@ +Subproject commit 53221c847f2c00924ed80e8f0b1b0c90d3a5eba0 diff --git a/scripts/ci/codechecker_pr_report.py b/scripts/ci/codechecker_pr_report.py new file mode 100644 index 00000000..2c405740 --- /dev/null +++ b/scripts/ci/codechecker_pr_report.py @@ -0,0 +1,575 @@ +#!/usr/bin/env python3 +"""Create advisory GitHub output from a CodeChecker JSON report.""" + +from __future__ import annotations + +import argparse +from collections import Counter +from html import escape as html_escape +import json +import os +import plistlib +import re +import subprocess +from pathlib import Path +from typing import Any +from urllib.parse import quote + + +MARKER = "" +SOURCE_SUFFIXES = {".c", ".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".hxx"} +DIFF_HEADER = re.compile(r"^\+\+\+ b/(.+)$") +DIFF_RANGE = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@") +ANALYZER_RESULT = re.compile( + r"_(?P[A-Za-z0-9_-]+)_[0-9a-f]+\.plist(?P\.err)?$" +) +ANSI_ESCAPE = re.compile(r"(?:\\x1b|\x1b)\[[0-9;]*m") +DIAGNOSTIC = re.compile( + r"(?P[^\r\n]+?):(?P\d+):(?P\d+):\s*" + r"(?:fatal\s+)?error:\s*(?P[^\r\n]+)" +) +MAX_DETAILS = 100 +CONTEXT_LINES = 1 +MAX_SNIPPET_LINE_LENGTH = 180 + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument("--report", type=Path, required=True) + parser.add_argument("--analysis-dir", type=Path) + parser.add_argument("--comment-output", type=Path, required=True) + parser.add_argument("--base", default="") + parser.add_argument("--head", default="HEAD") + parser.add_argument("--repo-root", type=Path, default=Path.cwd()) + return parser.parse_args() + + +def changed_lines(repo_root: Path, base: str, head: str) -> dict[str, list[range]]: + if not base: + return {} + + command = [ + "git", + "diff", + "--unified=0", + "--no-color", + f"{base}...{head}", + "--", + "*.c", + "*.cc", + "*.cpp", + "*.cxx", + "*.h", + "*.hh", + "*.hpp", + "*.hxx", + ] + result = subprocess.run( + command, + cwd=repo_root, + check=True, + capture_output=True, + text=True, + ) + + current_file: str | None = None + changed: dict[str, list[range]] = {} + for raw_line in result.stdout.splitlines(): + header = DIFF_HEADER.match(raw_line) + if header: + current_file = header.group(1) + if Path(current_file).suffix.lower() not in SOURCE_SUFFIXES: + current_file = None + continue + + hunk = DIFF_RANGE.match(raw_line) + if current_file and hunk: + start = int(hunk.group(1)) + count = int(hunk.group(2) or "1") + if count: + changed.setdefault(current_file, []).append(range(start, start + count)) + + return changed + + +def report_path(report: dict[str, Any], repo_root: Path) -> str | None: + file_value = report.get("file", {}) + candidates: list[str] = [] + if isinstance(file_value, dict): + candidates.extend( + str(file_value.get(key, "")) for key in ("path", "original_path", "id") + ) + elif file_value: + candidates.append(str(file_value)) + + repo_root = repo_root.resolve() + repo_name = repo_root.name + for candidate in candidates: + if not candidate: + continue + normalized = candidate.replace("\\", "/") + candidate_path = Path(candidate) + if candidate_path.is_absolute(): + try: + return candidate_path.resolve().relative_to(repo_root).as_posix() + except ValueError: + pass + + if (repo_root / normalized).exists(): + return normalized + parts = Path(normalized).parts + for index in range(1, len(parts)): + suffix = Path(*parts[index:]) + if (repo_root / suffix).exists(): + return suffix.as_posix() + marker = f"/{repo_name}/" + if marker in normalized: + return normalized.rsplit(marker, 1)[1] + if normalized.startswith(f"{repo_name}/"): + return normalized[len(repo_name) + 1 :] + + return None + + +def load_reports(report_file: Path) -> list[dict[str, Any]]: + if not report_file.is_file(): + return [] + data = json.loads(report_file.read_text(encoding="utf-8")) + if isinstance(data, dict): + reports = data.get("reports", []) + else: + reports = data + return [report for report in reports if isinstance(report, dict)] + + +def analyzer_results(analysis_dir: Path | None) -> tuple[Counter[str], Counter[str]]: + completed: Counter[str] = Counter() + failed: Counter[str] = Counter() + if not analysis_dir or not analysis_dir.is_dir(): + return completed, failed + + for path in analysis_dir.iterdir(): + match = ANALYZER_RESULT.search(path.name) + if not match: + continue + destination = failed if match.group("failed") else completed + destination[match.group("analyzer")] += 1 + return completed, failed + + +def analyzer_failures( + analysis_dir: Path | None, repo_root: Path +) -> list[dict[str, str | int]]: + failures: list[dict[str, str | int]] = [] + if not analysis_dir or not analysis_dir.is_dir(): + return failures + + for path in sorted(analysis_dir.glob("*.plist.err")): + match = ANALYZER_RESULT.search(path.name) + analyzer = match.group("analyzer") if match else "unknown" + source = path.name.split(f"_{analyzer}_", 1)[0] + line = 1 + message = "Analyzer invocation failed; see the artifact for diagnostics." + try: + data = plistlib.loads(path.read_bytes()) + stderr = ANSI_ESCAPE.sub("", str(data.get("stderr", ""))) + diagnostic = DIAGNOSTIC.search(stderr) + if diagnostic: + candidate = report_path( + {"file": {"original_path": diagnostic.group("path")}}, repo_root + ) + source = candidate or Path(diagnostic.group("path")).name + line = int(diagnostic.group("line")) + message = diagnostic.group("message").strip() + except (OSError, plistlib.InvalidFileException, ValueError): + pass + failures.append( + {"analyzer": analyzer, "path": source, "line": line, "message": message} + ) + return failures + + +def escape_annotation(value: str) -> str: + return ( + value.replace("%", "%25") + .replace("\r", "%0D") + .replace("\n", "%0A") + .replace(":", "%3A") + .replace(",", "%2C") + ) + + +def markdown_escape(value: str) -> str: + return value.replace("|", "\\|").replace("\n", " ").replace("\r", " ") + + +def source_url(path: str, line: int, repository: str, revision: str) -> str | None: + if not repository or not revision: + return None + return f"https://github.com/{repository}/blob/{revision}/{quote(path, safe='/')}#L{line}" + + +def source_location(path: str, line: int, repository: str, revision: str) -> str: + label = f"{path}:{line}" + url = source_url(path, line, repository, revision) + if not url: + return f"`{label}`" + return f"[`{label}`]({url})" + + +def source_location_html(path: str, line: int, repository: str, revision: str) -> str: + label = html_escape(f"{path}:{line}") + url = source_url(path, line, repository, revision) + if not url: + return f"{label}" + return f'{label}' + + +def finding_table( + reports: list[dict[str, Any]], repository: str, revision: str +) -> list[str]: + lines = [ + "| Severity | Location | Analyzer/checker | Message |", + "| --- | --- | --- | --- |", + ] + for finding in reports: + severity = markdown_escape(str(finding.get("severity") or "UNSPECIFIED")) + location = source_location( + str(finding["repo_path"]), int(finding["line"]), repository, revision + ) + analyzer = markdown_escape(str(finding.get("analyzer_name") or "unknown")) + checker = markdown_escape(str(finding.get("checker_name") or "CodeChecker")) + message = markdown_escape(str(finding.get("message") or "Static analysis finding")) + lines.append(f"| {severity} | {location} | `{analyzer}/{checker}` | {message} |") + return lines + + +def finding_range( + finding: dict[str, Any], repo_root: Path +) -> tuple[int, int, int, int]: + line = int(finding["line"]) + start_col = int(finding.get("column") or 1) + end_line = line + end_col = start_col + for event in reversed(finding.get("bug_path_events") or []): + if not isinstance(event, dict): + continue + event_path = report_path(event, repo_root) + event_range = event.get("range") or {} + if event_path != finding["repo_path"] or int(event.get("line") or 0) != line: + continue + start_col = int(event_range.get("start_col") or event.get("column") or start_col) + end_line = int(event_range.get("end_line") or line) + end_col = int(event_range.get("end_col") or start_col) + break + return line, start_col, end_line, end_col + + +def source_excerpt(finding: dict[str, Any], repo_root: Path) -> str | None: + source = repo_root / str(finding["repo_path"]) + if not source.is_file(): + return None + try: + source_lines = source.read_text(encoding="utf-8", errors="replace").splitlines() + except OSError: + return None + + line, start_col, end_line, end_col = finding_range(finding, repo_root) + if line < 1 or line > len(source_lines): + return None + first = max(1, line - CONTEXT_LINES) + last = min(len(source_lines), max(line, end_line) + CONTEXT_LINES) + width = len(str(last)) + excerpt: list[str] = [] + for current in range(first, last + 1): + prefix = ">" if line <= current <= end_line else " " + rendered = source_lines[current - 1].expandtabs(4) + if len(rendered) > MAX_SNIPPET_LINE_LENGTH: + rendered = f"{rendered[: MAX_SNIPPET_LINE_LENGTH - 3]}..." + excerpt.append(f"{prefix} {current:>{width}} | {rendered}") + if current == line: + original = source_lines[current - 1] + expanded_prefix = original[: max(0, start_col - 1)].expandtabs(4) + if end_line == line: + selected = original[ + max(0, start_col - 1) : max(start_col, end_col) + ].expandtabs(4) + marker_width = max(1, len(selected)) + else: + marker_width = max(1, len(rendered) - len(expanded_prefix)) + excerpt.append( + f" {'':>{width}} | {' ' * len(expanded_prefix)}{'^' * marker_width}" + ) + return "\n".join(excerpt) + + +def context_section( + reports: list[dict[str, Any]], + repo_root: Path, + repository: str, + revision: str, +) -> list[str]: + details = reports[:MAX_DETAILS] + if not details: + return [] + + lines = [ + "### Complete-codebase findings", + "", + "Each finding includes source context. The `>` line and carets identify the " + "expression reported by the analyzer.", + "", + "
", + f"Show detailed findings with source context ({len(details)}" + f" of {len(reports)})", + "", + ] + for finding in details: + severity = html_escape(str(finding.get("severity") or "UNSPECIFIED")) + message = html_escape(str(finding.get("message") or "Static analysis finding")) + location = source_location_html( + str(finding["repo_path"]), int(finding["line"]), repository, revision + ) + excerpt = source_excerpt(finding, repo_root) + lines.extend( + [ + "
", + f"{severity} — {location} — {message}", + "", + ] + ) + if excerpt: + lines.extend(["```text", excerpt, "```", ""]) + else: + lines.extend(["Source context is unavailable in the workflow checkout.", ""]) + lines.extend(["
", ""]) + lines.extend(["
", ""]) + return lines + + +def main() -> int: + args = parse_args() + repo_root = args.repo_root.resolve() + try: + changed = changed_lines(repo_root, args.base, args.head) + except subprocess.CalledProcessError as error: + print(f"Could not calculate changed lines: {error}") + changed = {} + + all_reports = [ + report + for report in load_reports(args.report) + if str(report.get("severity", "")).upper() != "STYLE" + ] + completed_runs, failed_runs = analyzer_results(args.analysis_dir) + failures = analyzer_failures(args.analysis_dir, repo_root) + analyzer_errors = sum(failed_runs.values()) + findings: list[dict[str, Any]] = [] + for report in all_reports: + path = report_path(report, repo_root) + line = int(report.get("line") or 1) + if path not in changed or not any(line in lines for lines in changed[path]): + continue + finding = dict(report) + finding["repo_path"] = path + finding["line"] = line + findings.append(finding) + + severity_order = { + "CRITICAL": 0, + "HIGH": 1, + "MEDIUM": 2, + "LOW": 3, + "UNSPECIFIED": 4, + } + findings.sort( + key=lambda item: ( + severity_order.get(str(item.get("severity", "UNSPECIFIED")).upper(), 5), + item["repo_path"], + item["line"], + ) + ) + + normalized_reports: list[dict[str, Any]] = [] + for report in all_reports: + path = report_path(report, repo_root) + if not path: + continue + normalized = dict(report) + normalized["repo_path"] = path + normalized["line"] = int(report.get("line") or 1) + normalized_reports.append(normalized) + normalized_reports.sort( + key=lambda item: ( + severity_order.get(str(item.get("severity", "UNSPECIFIED")).upper(), 5), + item["repo_path"], + item["line"], + ) + ) + + for finding in findings[:50]: + severity = str(finding.get("severity") or "UNSPECIFIED").upper() + level = "warning" if severity in {"CRITICAL", "HIGH", "MEDIUM"} else "notice" + checker = str(finding.get("checker_name") or "CodeChecker") + message = str(finding.get("message") or "Static analysis finding") + print( + f"::{level} file={escape_annotation(finding['repo_path'])}," + f"line={finding['line']},title={escape_annotation(checker)}::" + f"{escape_annotation(f'[{severity}] {message}')}" + ) + + run_url = os.environ.get("GITHUB_RUN_URL", "") + repository = os.environ.get("GITHUB_REPOSITORY", "") + revision = args.head + report_available = args.report.is_file() + severity_counts = Counter( + str(report.get("severity") or "UNSPECIFIED").upper() + for report in normalized_reports + ) + analyzer_counts = Counter( + str(report.get("analyzer_name") or "unknown") for report in normalized_reports + ) + lines = [ + MARKER, + "## CodeChecker static analysis (advisory)", + "", + "> [!NOTE]", + "> This report is informational. It does not block merging and does not change code.", + "", + "### At a glance", + "", + "| Scope | Result |", + "| --- | ---: |", + f"| Findings on lines changed by this PR | **{len(findings)}** |", + f"| Findings in the complete codebase | **{len(normalized_reports)}** |", + f"| Analyzer invocations completed | **{sum(completed_runs.values())}** |", + f"| Analyzer invocations failed | **{analyzer_errors}** |", + "", + ] + if not report_available: + lines.extend( + [ + "CodeChecker did not produce a JSON report. Check the downloadable analysis log " + "for build or analyzer errors.", + "", + ] + ) + elif findings: + lines.extend( + [ + "### Findings on changed lines", + "", + f"Found **{len(findings)}** non-style issue(s) on lines changed by this PR.", + "", + ] + ) + lines.extend(finding_table(findings[:50], repository, revision)) + if len(findings) > 50: + lines.extend(["", f"Only the first 50 of {len(findings)} findings are shown here."]) + lines.append("") + else: + lines.extend( + [ + "### Findings on changed lines", + "", + "No non-style findings were reported on lines changed by this PR.", + "", + ] + ) + + if completed_runs or failed_runs: + lines.extend( + [ + "### Analyzer coverage", + "", + "| Analyzer | Completed | Failed |", + "| --- | ---: | ---: |", + ] + ) + for analyzer in sorted(completed_runs.keys() | failed_runs.keys()): + lines.append( + f"| `{analyzer}` | {completed_runs[analyzer]} | {failed_runs[analyzer]} |" + ) + lines.append("") + + if failures: + lines.extend( + [ + "
", + f"{len(failures)} failed analyzer invocation(s)", + "", + "These files received reduced analysis coverage. The first compiler diagnostic " + "from each invocation is shown below.", + "The reason is that the clang frontend was not able to link them because of issues like implicit declaration. Target gcc build may still succeed.", + "", + "| Analyzer | Source | First diagnostic |", + "| --- | --- | --- |", + ] + ) + for failure in failures: + location = source_location( + str(failure["path"]), int(failure["line"]), repository, revision + ) + lines.append( + f"| `{markdown_escape(str(failure['analyzer']))}` | {location} | " + f"{markdown_escape(str(failure['message']))} |" + ) + lines.extend(["", "
", ""]) + + if report_available: + severity_summary = ", ".join( + f"**{severity_counts.get(severity, 0)} {severity.lower()}**" + for severity in ("CRITICAL", "HIGH", "MEDIUM", "LOW", "UNSPECIFIED") + if severity_counts.get(severity, 0) + ) or "no findings" + analyzer_summary = ", ".join( + f"`{name}`: {count}" for name, count in sorted(analyzer_counts.items()) + ) or "none" + lines.extend( + [ + "### Complete-codebase summary", + "", + f"Severity: {severity_summary} ", + f"Reported by analyzer: {analyzer_summary}", + "", + ] + ) + lines.extend( + context_section(normalized_reports, repo_root, repository, revision) + ) + + if run_url: + lines.append( + "Open the workflow run and download the complete CodeChecker " + f"[report]({run_url}) for a detailed view. Check out " + "`codechecker.html/index.html`." + ) + lines.append("") + + # Keep successful, finding-free runs deliberately terse. The marker remains so + # the workflow can update the same PR comment on subsequent runs. + if ( + report_available + and not normalized_reports + and not failures + and not analyzer_errors + ): + lines = [ + MARKER, + "## CodeChecker static analysis", + "", + "✅ No non-style issues found.", + ] + + args.comment_output.parent.mkdir(parents=True, exist_ok=True) + args.comment_output.write_text("\n".join(lines), encoding="utf-8") + + summary_file = os.environ.get("GITHUB_STEP_SUMMARY") + if summary_file: + with Path(summary_file).open("a", encoding="utf-8") as summary: + summary.write("\n".join(lines[1:])) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/Battery/BQ25120a.cpp b/src/Battery/BQ25120a.cpp index 71da49d9..b03a3847 100644 --- a/src/Battery/BQ25120a.cpp +++ b/src/Battery/BQ25120a.cpp @@ -153,28 +153,28 @@ void BQ25120a::setup(const battery_settings &_battery_settings) { uint8_t BQ25120a::read_charging_state() { uint8_t status = 0; - bool ret = readReg(registers::CTRL, (uint8_t *) &status, sizeof(status)); + (void)readReg(registers::CTRL, (uint8_t *) &status, sizeof(status)); return status; } uint8_t BQ25120a::read_fault() { uint8_t status = 0; - bool ret = readReg(registers::FAULT, (uint8_t *) &status, sizeof(status)); + (void)readReg(registers::FAULT, (uint8_t *) &status, sizeof(status)); return status; } uint8_t BQ25120a::read_ts_fault() { uint8_t status = 0; - bool ret = readReg(registers::TS_FAULT, (uint8_t *) &status, sizeof(status)); + (void)readReg(registers::TS_FAULT, (uint8_t *) &status, sizeof(status)); return status; } chrg_state BQ25120a::read_charging_control() { uint8_t status = 0; - bool ret = readReg(registers::CHARGE_CTRL, (uint8_t *) &status, sizeof(status)); + (void)readReg(registers::CHARGE_CTRL, (uint8_t *) &status, sizeof(status)); chrg_state chrg; @@ -200,13 +200,13 @@ chrg_state BQ25120a::read_charging_control() { uint8_t BQ25120a::write_charging_control(float mA) { uint8_t status = 0; - bool ret = readReg(registers::CHARGE_CTRL, &status, sizeof(status)); + (void)readReg(registers::CHARGE_CTRL, &status, sizeof(status)); status &= 0x3; if (mA >= 40) { if (mA > 300) mA = 300; - status |= (((uint16_t)((mA - 40) / 10 + EPS)) & 0x1F) << 2; + status |= (((uint16_t)((mA - 40.0f) / 10.0f + EPS)) & 0x1F) << 2; status |= 1 << 7; } else { if (mA > 35) mA = 35; @@ -243,10 +243,8 @@ uint8_t BQ25120a::write_LDO_voltage_control(float volt) { readReg(registers::LS_LDO_CTRL, &status, sizeof(status)); - //status |= (((uint16_t)((volt - 0.8) * 10)) & 0x1F) << 2; status &= 1 << 7; - status |= ((uint8_t)((volt - 0.8f) * 10 + EPS)) << 2; - //status |= 1 << 7; + status |= ((uint8_t)((volt - 0.8f) * 10.0f + EPS)) << 2; writeReg(registers::LS_LDO_CTRL, &status, sizeof(status)); @@ -255,7 +253,7 @@ uint8_t BQ25120a::write_LDO_voltage_control(float volt) { float BQ25120a::read_ldo_voltage() { uint8_t status = 0; - bool ret = readReg(registers::LS_LDO_CTRL, (uint8_t *) &status, sizeof(status)); + (void)readReg(registers::LS_LDO_CTRL, (uint8_t *) &status, sizeof(status)); float voltage = 0.8f + ((status >> 2 & 0x1F)) * 0.1f; @@ -264,7 +262,7 @@ float BQ25120a::read_ldo_voltage() { float BQ25120a::read_battery_voltage_control() { uint8_t status = 0; - bool ret = readReg(registers::BAT_VOL_CTRL, (uint8_t *) &status, sizeof(status)); + (void)readReg(registers::BAT_VOL_CTRL, (uint8_t *) &status, sizeof(status)); float voltage = 3.6f + (status >> 1) * 0.01f; @@ -279,7 +277,7 @@ uint8_t BQ25120a::write_battery_voltage_control(float volt) { volt = CLAMP(volt, 3.6f, 4.65f); - status |= (((uint16_t)((volt - 3.6f) * 100 + EPS)) & 0x7F) << 1; + status |= (((uint16_t)((volt - 3.6f) * 100.0f + EPS)) & 0x7F) << 1; writeReg(registers::BAT_VOL_CTRL, &status, sizeof(status)); @@ -288,12 +286,10 @@ uint8_t BQ25120a::write_battery_voltage_control(float volt) { chrg_state BQ25120a::read_termination_control() { uint8_t status = 0; - bool ret = readReg(registers::TERM_CTRL, (uint8_t *) &status, sizeof(status)); + (void)readReg(registers::TERM_CTRL, (uint8_t *) &status, sizeof(status)); struct chrg_state chrg; - // if (!ret) printk("failed to read\n"); - chrg.enabled = status & 0x2; //chrg.high_impedance = status & 0x1; @@ -305,7 +301,7 @@ chrg_state BQ25120a::read_termination_control() { if (status & (1 << 7)) { mAh = 6 + mAh * 1; } else { - mAh = 0.5 + mAh * 0.5; + mAh = 0.5f + mAh * 0.5f; } chrg.mAh = mAh; @@ -325,7 +321,7 @@ uint8_t BQ25120a::write_termination_control(float mA, bool enable_termination) { status |= 1 << 7; } else { if (mA > 5) mA = 5; - status |= (((uint16_t)(2 * (mA - 0.5))) & 0x1F) << 2; + status |= (((uint16_t)(2.0f * (mA - 0.5f))) & 0x1F) << 2; } if (enable_termination) { @@ -341,24 +337,22 @@ ilim_uvlo BQ25120a::read_uvlo_ilim() { struct ilim_uvlo param; uint8_t status = 0; - bool ret = readReg(registers::ILIM_UVLO, (uint8_t *) &status, sizeof(status)); - - // if (!ret) printk("failed to read\n"); + (void)readReg(registers::ILIM_UVLO, (uint8_t *) &status, sizeof(status)); - param.uvlo_v = CLAMP(3.0f- 0.2f * ((status & 0x7) - 2), 2.2, 3.0); + param.uvlo_v = CLAMP(3.0f- 0.2f * ((status & 0x7) - 2), 2.2f, 3.0f); param.lim_mA = 50.f + 50.f * ((status >> 3) & 0x7); return param; } uint8_t BQ25120a::write_uvlo_ilim(ilim_uvlo param) { - float mA = CLAMP(param.lim_mA, 50, 400); - float v = CLAMP(param.uvlo_v, 2.2, 3.0); + float mA = CLAMP(param.lim_mA, 50.0f, 400.0f); + float v = CLAMP(param.uvlo_v, 2.2f, 3.0f); uint8_t status = 0; - status |= ((uint16_t)(mA / 50 - 1) & 0x7) << 3; - status |= ((uint16_t)((3.0 - v) * 5 + 2) & 0x7); + status |= ((uint16_t)(mA / 50.0f - 1.0f) & 0x7) << 3; + status |= ((uint16_t)((3.0f - v) * 5.0f + 2.0f) & 0x7); writeReg(registers::ILIM_UVLO, &status, sizeof(status)); @@ -408,9 +402,7 @@ button_state BQ25120a::read_button_state() { struct button_state btn; uint8_t status = 0; - bool ret = readReg(registers::BTN_CTRL, (uint8_t *) &status, sizeof(status)); - - // if (!ret) printk("failed to read\n"); + (void)readReg(registers::BTN_CTRL, (uint8_t *) &status, sizeof(status)); btn.wake_1 = status & 0x2; btn.wake_2 = status & 0x1; @@ -426,4 +418,4 @@ int BQ25120a::set_power_connect_callback(gpio_callback_handler_t handler) { int BQ25120a::set_int_callback(gpio_callback_handler_t handler) { gpio_init_callback(&int_cb_data, handler, int_cb_data.pin_mask | BIT(int_pin.pin)); return gpio_add_callback(int_pin.port, &int_cb_data); -} \ No newline at end of file +} diff --git a/src/Battery/BQ25120a.h b/src/Battery/BQ25120a.h index 38eb75f3..79ac3314 100644 --- a/src/Battery/BQ25120a.h +++ b/src/Battery/BQ25120a.h @@ -13,7 +13,7 @@ #define BQ25120a_I2C_TIMEOUT_US 66 #define BQ25120a_HIGH_Z_TIMEOUT_US 1000 -#define EPS 1e-3 +#define EPS 1e-3f struct chrg_state { float mAh = 0; @@ -107,4 +107,4 @@ class BQ25120a { extern BQ25120a battery_controller; -#endif \ No newline at end of file +#endif diff --git a/src/Battery/BQ27220.cpp b/src/Battery/BQ27220.cpp index 715f22c1..cf21a764 100644 --- a/src/Battery/BQ27220.cpp +++ b/src/Battery/BQ27220.cpp @@ -96,7 +96,7 @@ void BQ27220::writeReg(uint8_t reg, uint8_t *buffer, uint16_t len) { bat_status BQ27220::battery_status() { bat_status status; uint16_t val = 0; - bool ret = readReg(registers::FLAGS, (uint8_t *) &val, sizeof(val)); + (void)readReg(registers::FLAGS, (uint8_t *) &val, sizeof(val)); status.DSG = val & 0x1; status.SYSDWN = val & (1 << 1); @@ -135,7 +135,7 @@ gauge_status BQ27220::gauging_state() { float BQ27220::temperature() { uint16_t temp_K = 0; - bool ret = readReg(registers::TEMP, (uint8_t *) &temp_K, sizeof(temp_K)); + (void)readReg(registers::TEMP, (uint8_t *) &temp_K, sizeof(temp_K)); float temp = temp_K / 10.0 - 273.15; return temp; @@ -143,7 +143,7 @@ float BQ27220::temperature() { float BQ27220::voltage() { uint16_t mV = 0; - bool ret = readReg(registers::VOLT, (uint8_t *) &mV, sizeof(mV)); + (void)readReg(registers::VOLT, (uint8_t *) &mV, sizeof(mV)); float v = mV / 1000.0; return v; @@ -151,74 +151,74 @@ float BQ27220::voltage() { float BQ27220::capacity() { uint16_t mAh = 0; - bool ret = readReg(registers::FCC, (uint8_t *) &mAh, sizeof(mAh)); + (void)readReg(registers::FCC, (uint8_t *) &mAh, sizeof(mAh)); return mAh; } float BQ27220::time_to_full() { uint16_t minutes = 0; - bool ret = readReg(registers::TTF, (uint8_t *) &minutes, sizeof(minutes)); + (void)readReg(registers::TTF, (uint8_t *) &minutes, sizeof(minutes)); return minutes; } float BQ27220::time_to_empty() { uint16_t minutes = 0; - bool ret = readReg(registers::TTE, (uint8_t *) &minutes, sizeof(minutes)); + (void)readReg(registers::TTE, (uint8_t *) &minutes, sizeof(minutes)); return minutes; } float BQ27220::state_of_charge() { uint16_t soc = 0; - bool ret = readReg(registers::SOC, (uint8_t *) &soc, sizeof(soc)); + (void)readReg(registers::SOC, (uint8_t *) &soc, sizeof(soc)); return soc; } float BQ27220::state_of_health() { uint16_t soc = 0; - bool ret = readReg(registers::SOH, (uint8_t *) &soc, sizeof(soc)); + (void)readReg(registers::SOH, (uint8_t *) &soc, sizeof(soc)); return soc; } float BQ27220::current() { int16_t mA = 0; - bool ret = readReg(registers::NAC, (uint8_t *) &mA, sizeof(mA)); + (void)readReg(registers::NAC, (uint8_t *) &mA, sizeof(mA)); return mA; } float BQ27220::average_current() { int16_t mA = 0; - bool ret = readReg(registers::AI, (uint8_t *) &mA, sizeof(mA)); + (void)readReg(registers::AI, (uint8_t *) &mA, sizeof(mA)); return mA; } float BQ27220::design_cap() { uint16_t mAh = 0; - bool ret = readReg(registers::DCAP, (uint8_t *) &mAh, sizeof(mAh)); + (void)readReg(registers::DCAP, (uint8_t *) &mAh, sizeof(mAh)); return mAh; } float BQ27220::remaining_cap() { uint16_t mAh = 0; - bool ret = readReg(registers::RM, (uint8_t *) &mAh, sizeof(mAh)); + (void)readReg(registers::RM, (uint8_t *) &mAh, sizeof(mAh)); return mAh; } float BQ27220::charge_current() { int16_t mA = 0; - bool ret = readReg(registers::CC, (uint8_t *) &mA, sizeof(mA)); + (void)readReg(registers::CC, (uint8_t *) &mA, sizeof(mA)); return mA; } int BQ27220::cycle_count() { uint16_t n_cycles = 0; - bool ret = readReg(registers::CYCT, (uint8_t *) &n_cycles, sizeof(n_cycles)); + (void)readReg(registers::CYCT, (uint8_t *) &n_cycles, sizeof(n_cycles)); return n_cycles; } float BQ27220::standby_current() { int16_t mA = 0; - bool ret = readReg(registers::SI, (uint8_t *) &mA, sizeof(mA)); + (void)readReg(registers::SI, (uint8_t *) &mA, sizeof(mA)); return mA; } @@ -226,7 +226,7 @@ float BQ27220::standby_current() { op_state BQ27220::operation_state() { op_state state; uint16_t status = 0; - bool ret = readReg(registers::OP_STAT, (uint8_t *) &status, sizeof(status)); + (void)readReg(registers::OP_STAT, (uint8_t *) &status, sizeof(status)); state.CALD = status & 0x01; state.SEC = (status >> 1) & 0x3; @@ -302,11 +302,9 @@ void BQ27220::exit_config_update(bool init) { } void BQ27220::read_RAM(uint16_t ram_address, uint8_t * data, int len) { - bool ret; - writeReg(0x3E, (uint8_t *) &ram_address, sizeof(ram_address)); k_usleep(BQ27220_RAM_TIMEOUT_US); - ret = readReg(0x40, data, len); + (void)readReg(0x40, data, len); } int BQ27220::write_RAM(uint16_t ram_address, uint8_t * data, int len, bool check) { @@ -314,15 +312,13 @@ int BQ27220::write_RAM(uint16_t ram_address, uint8_t * data, int len, bool check uint8_t data_len=0; uint8_t buf[len]; - bool ret; - writeReg(0x3E, (uint8_t *) &ram_address, sizeof(ram_address)); k_usleep(BQ27220_RAM_TIMEOUT_US); - ret = readReg(0x61, (uint8_t *) &data_len, sizeof(data_len)); - ret = readReg(0x40, buf, len); - ret = readReg(0x60, (uint8_t *) &check_sum, sizeof(check_sum)); + (void)readReg(0x61, (uint8_t *) &data_len, sizeof(data_len)); + (void)readReg(0x40, buf, len); + (void)readReg(0x60, (uint8_t *) &check_sum, sizeof(check_sum)); uint8_t my_check = (uint8_t)0xFF-check_sum; // - data[0] - data[1]; @@ -367,8 +363,6 @@ int BQ27220::write_RAM(uint16_t ram_address, uint16_t val, bool check) { } void BQ27220::setup(const battery_settings &_battery_settings, bool init) { - int ret; - // unseal write_command(0x0414); k_msleep(100); @@ -385,29 +379,29 @@ void BQ27220::setup(const battery_settings &_battery_settings, bool init) { //ret = write_RAM(0x9220, 0); // design and full charge capacity - ret = write_RAM(0x929F, _battery_settings.capacity); - ret = write_RAM(0x929D, _battery_settings.capacity); //130 + (void)write_RAM(0x929F, _battery_settings.capacity); + (void)write_RAM(0x929D, _battery_settings.capacity); //130 // near full - ret = write_RAM(0x926B, 5); + (void)write_RAM(0x926B, 5); - ret = write_RAM(0x91F5, _battery_settings.temp_min * 10); - ret = write_RAM(0x91F7, _battery_settings.temp_max * 10); + (void)write_RAM(0x91F5, _battery_settings.temp_min * 10); + (void)write_RAM(0x91F7, _battery_settings.temp_max * 10); // charge current - ret = write_RAM(0x91FB, _battery_settings.i_charge); + (void)write_RAM(0x91FB, _battery_settings.i_charge); // charge voltage - ret = write_RAM(0x91FD, _battery_settings.u_term * 1000); + (void)write_RAM(0x91FD, _battery_settings.u_term * 1000); // taper current - ret = write_RAM(0x9201, _battery_settings.i_term); + (void)write_RAM(0x9201, _battery_settings.i_term); // experimental: min taper capacity - ret = write_RAM(0x9203, 4); // standard: 25 + (void)write_RAM(0x9203, 4); // standard: 25 // deadband uint8_t val = 1; - ret = write_RAM(0x91DE, &val, sizeof(uint8_t)); + (void)write_RAM(0x91DE, &val, sizeof(uint8_t)); // deadband CC (verursacht Probleme, rm zählt zu schnell?) /*val = 5; @@ -415,14 +409,14 @@ void BQ27220::setup(const battery_settings &_battery_settings, bool init) { */ // sleep current - ret = write_RAM(0x9217, 1); + (void)write_RAM(0x9217, 1); // dischage current trd - ret = write_RAM(0x9228, 2); + (void)write_RAM(0x9228, 2); // charge current trd - ret = write_RAM(0x922A, 2); + (void)write_RAM(0x922A, 2); // quit current - ret = write_RAM(0x922C, 1); + (void)write_RAM(0x922C, 1); //dod 0%: 4287 //dod 10%: 4125 @@ -444,29 +438,29 @@ void BQ27220::setup(const battery_settings &_battery_settings, bool init) { //dod: 103.25%: 3089 // sysDown set Voltage - ret = write_RAM(0x9240, _battery_settings.u_vlo * 1000 + CONFIG_BATTERY_SYSDOWN_SET_OFFSET); + (void)write_RAM(0x9240, _battery_settings.u_vlo * 1000 + CONFIG_BATTERY_SYSDOWN_SET_OFFSET); // sysDown clear Voltage - ret = write_RAM(0x9243, _battery_settings.u_vlo * 1000 + CONFIG_BATTERY_SYSDOWN_SET_OFFSET + CONFIG_BATTERY_SYSDOWN_HYSTERESIS); + (void)write_RAM(0x9243, _battery_settings.u_vlo * 1000 + CONFIG_BATTERY_SYSDOWN_SET_OFFSET + CONFIG_BATTERY_SYSDOWN_HYSTERESIS); // FD set - ret = write_RAM(0x9282, _battery_settings.u_vlo * 1000 + CONFIG_BATTERY_FD_SET_OFFSET); + (void)write_RAM(0x9282, _battery_settings.u_vlo * 1000 + CONFIG_BATTERY_FD_SET_OFFSET); // FD clear - ret = write_RAM(0x9284, _battery_settings.u_vlo * 1000 + CONFIG_BATTERY_FD_SET_OFFSET + CONFIG_BATTERY_FD_HYSTERESIS); + (void)write_RAM(0x9284, _battery_settings.u_vlo * 1000 + CONFIG_BATTERY_FD_SET_OFFSET + CONFIG_BATTERY_FD_HYSTERESIS); // FC Voltage - ret = write_RAM(0x9288, _battery_settings.u_term * 1000 - CONFIG_BATTERY_FC_VOLTAGE_OFFSET); + (void)write_RAM(0x9288, _battery_settings.u_term * 1000 - CONFIG_BATTERY_FC_VOLTAGE_OFFSET); // Electonic Load in 3µA steps - ret = write_RAM(0x9269, 6); // 18 µA + (void)write_RAM(0x9269, 6); // 18 µA // EMF //write_RAM(0x92A7, 36001); //C0 - ret = write_RAM(0x92A9, 480); //bat1:250 + (void)write_RAM(0x92A9, 480); //bat1:250 //R0 - ret = write_RAM(0x92AB, 19941); //bat1: 19941 //22542 //new bat: 17340 + (void)write_RAM(0x92AB, 19941); //bat1: 19941 //22542 //new bat: 17340 //R1 //write_RAM(0x92AF, 3160); @@ -478,16 +472,16 @@ void BQ27220::setup(const battery_settings &_battery_settings, bool init) { // do not use, only on CT makes sense: // SOC Flag, enable FC voltage detection uint8_t flags_b = 0x8C; - ret = write_RAM(0x9281, &flags_b, sizeof(flags_b)); + (void)write_RAM(0x9281, &flags_b, sizeof(flags_b)); // Overload current - ret = write_RAM(0x9264, _battery_settings.i_max); + (void)write_RAM(0x9264, _battery_settings.i_max); // CEDV Smoothing Config uint8_t cedv_conf = 0x0D; //Default: 0x08, Enable SMEXT, SMEN 0x0D - ret = write_RAM(0x9271, &cedv_conf, sizeof(cedv_conf)); + (void)write_RAM(0x9271, &cedv_conf, sizeof(cedv_conf)); - ret = write_RAM(0x9272, 3700); + (void)write_RAM(0x9272, 3700); exit_config_update(init); @@ -498,4 +492,4 @@ void BQ27220::setup(const battery_settings &_battery_settings, bool init) { int BQ27220::set_int_callback(gpio_callback_handler_t handler) { gpio_init_callback(&int_cb_data, handler, int_cb_data.pin_mask | BIT(gpout_pin.pin)); return gpio_add_callback(gpout_pin.port, &int_cb_data); -} \ No newline at end of file +} diff --git a/src/Battery/PowerManager.cpp b/src/Battery/PowerManager.cpp index 628739da..77ba0760 100644 --- a/src/Battery/PowerManager.cpp +++ b/src/Battery/PowerManager.cpp @@ -2,6 +2,7 @@ #include "macros_common.h" +#include #include #include #include @@ -54,15 +55,24 @@ static struct battery_data msg; //LoadSwitch PowerManager::v1_8_switch(GPIO_DT_SPEC_GET(DT_NODELABEL(load_switch), gpios)); void PowerManager::fuel_gauge_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins) { + ARG_UNUSED(dev); + ARG_UNUSED(cb); + ARG_UNUSED(pins); LOG_DBG("Fuel Gauge GPOUT Interrupt"); k_work_submit(&fuel_gauge_work); } void PowerManager::battery_controller_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins) { + ARG_UNUSED(dev); + ARG_UNUSED(cb); + ARG_UNUSED(pins); k_work_submit(&battery_controller_work); } void PowerManager::power_good_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins) { + ARG_UNUSED(dev); + ARG_UNUSED(cb); + ARG_UNUSED(pins); bool power_good = battery_controller.power_connected(); k_work_submit(&fuel_gauge_work); @@ -77,16 +87,19 @@ void PowerManager::power_good_callback(const struct device *dev, struct gpio_cal } void PowerManager::power_down_work_handler(struct k_work * work) { + ARG_UNUSED(work); power_manager.power_down(); } void PowerManager::charge_ctrl_work_handler(struct k_work * work) { + ARG_UNUSED(work); power_manager.charge_task(); // Schedule next execution k_work_schedule(&charge_ctrl_delayable, power_manager.chrg_interval); } void PowerManager::battery_controller_work_handler(struct k_work * work) { + ARG_UNUSED(work); button_state state; //uint8_t val = gpio_pin_get_dt(&power_manager.error_led); @@ -106,6 +119,7 @@ void PowerManager::battery_controller_work_handler(struct k_work * work) { } void PowerManager::fuel_gauge_work_handler(struct k_work * work) { + ARG_UNUSED(work); int ret; battery_level_status status; @@ -168,16 +182,16 @@ void PowerManager::fuel_gauge_work_handler(struct k_work * work) { msg.charging_state = POWER_CONNECTED; - LOG_DBG("Voltage: %.3f V", voltage); - LOG_DBG("Charging current: %.3f mA", current); - LOG_DBG("Target current: %.3f mA", target_current); - LOG_DBG("State of charge: %.3f %%", fuel_gauge.state_of_charge()); + LOG_DBG("Voltage: %.3f V", (double)voltage); + LOG_DBG("Charging current: %.3f mA", (double)current); + LOG_DBG("Target current: %.3f mA", (double)target_current); + LOG_DBG("State of charge: %.3f %%", (double)fuel_gauge.state_of_charge()); // check if target current is met (if not tapering) - if (current > 0.8 * target_current - 2 * power_manager._battery_settings.i_term) { + if (current > 0.8f * target_current - 2.0f * power_manager._battery_settings.i_term) { msg.charging_state = CHARGING; } - else if (voltage > power_manager._battery_settings.u_term - 0.02) { + else if (voltage > power_manager._battery_settings.u_term - 0.02f) { #ifdef CONFIG_BATTERY_ENABLE_TRICKLE_CHARGE msg.charging_state = TRICKLE_CHARGING; #else @@ -196,7 +210,7 @@ void PowerManager::fuel_gauge_work_handler(struct k_work * work) { uint8_t fault = battery_controller.read_fault(); // Battery fuel gauge status - bat_status status = fuel_gauge.battery_status(); + bat_status fault_status = fuel_gauge.battery_status(); voltage = fuel_gauge.voltage(); current = fuel_gauge.current(); @@ -208,10 +222,10 @@ void PowerManager::fuel_gauge_work_handler(struct k_work * work) { // as long as fault exists if (fault & (1 << 5)) { bool power_connected = battery_controller.power_connected(); - if (power_connected && current > 0.5 * power_manager._battery_settings.i_term) { + if (power_connected && current > 0.5f * power_manager._battery_settings.i_term) { msg.charging_state = PRECHARGING; } - LOG_WRN("Battery under voltage: %.3f V", voltage); + LOG_WRN("Battery under voltage: %.3f V", (double)voltage); } // cleared after read @@ -234,12 +248,12 @@ void PowerManager::fuel_gauge_work_handler(struct k_work * work) { LOG_DBG("------------------ Battery Info ------------------"); LOG_DBG("Battery Status:"); LOG_DBG(" Present: %d, Full Charge: %d, Full Discharge: %d", - status.BATTPRES, status.FC, status.FD); + fault_status.BATTPRES, fault_status.FC, fault_status.FD); // Basic measurements LOG_DBG("Basic Measurements:"); - LOG_DBG(" Voltage: %.3f V", voltage); - LOG_DBG(" Current: %.3f mA", current); + LOG_DBG(" Voltage: %.3f V", (double)voltage); + LOG_DBG(" Current: %.3f mA", (double)current); break; } @@ -262,10 +276,12 @@ void PowerManager::fuel_gauge_work_handler(struct k_work * work) { } int PowerManager::begin() { - earable_state oe_state; + earable_state oe_state{}; oe_state.charging_state = DISCHARGING; oe_state.pairing_state = PAIRED; + oe_state.sd_state = SD_IDLE; + oe_state.led_mode = STATE_INDICATION; battery_controller.begin(); fuel_gauge.begin(); @@ -404,7 +420,7 @@ int PowerManager::begin() { // check if fuel gauge has wrong value float capacity = fuel_gauge.capacity(); - if (abs(capacity - _battery_settings.capacity) > 1e-4) { + if (std::fabs(capacity - _battery_settings.capacity) > 1e-4F) { fuel_gauge.setup(_battery_settings); set_error_led(); } @@ -540,7 +556,7 @@ void bt_disconnect_handler(struct bt_conn *conn, void * data) { if (ret != 0) return; if (info.state == BT_CONN_STATE_CONNECTED) { - ret = bt_mgmt_conn_disconnect(conn, *((uint8_t*)data)); + (void)bt_mgmt_conn_disconnect(conn, *((uint8_t*)data)); } } @@ -551,7 +567,7 @@ void PowerManager::reboot() { uint8_t data = BT_HCI_ERR_REMOTE_USER_TERM_CONN; bt_conn_foreach(BT_CONN_TYPE_ALL, bt_disconnect_handler, &data); - ret = bt_le_adv_stop(); + (void)bt_le_adv_stop(); stop_sensor_manager(); @@ -570,7 +586,7 @@ int PowerManager::power_down(bool fault) { uint8_t data = BT_HCI_ERR_REMOTE_USER_TERM_CONN; bt_conn_foreach(BT_CONN_TYPE_ALL, bt_disconnect_handler, &data); - ret = bt_le_adv_stop(); + (void)bt_le_adv_stop(); // power disonnected // prepare interrupts @@ -611,7 +627,7 @@ int PowerManager::power_down(bool fault) { } LOG_PANIC(); - ret = bt_mgmt_stop_watchdog(); + (void)bt_mgmt_stop_watchdog(); //ERR_CHK(ret); dac.end(); @@ -628,10 +644,10 @@ int PowerManager::power_down(bool fault) { return 0; } - ret = pm_device_action_run(ls_sd, PM_DEVICE_ACTION_SUSPEND); - ret = pm_device_action_run(ls_3_3, PM_DEVICE_ACTION_SUSPEND); - ret = pm_device_action_run(ls_1_8, PM_DEVICE_ACTION_SUSPEND); - ret = pm_device_action_run(cons, PM_DEVICE_ACTION_SUSPEND); + (void)pm_device_action_run(ls_sd, PM_DEVICE_ACTION_SUSPEND); + (void)pm_device_action_run(ls_3_3, PM_DEVICE_ACTION_SUSPEND); + (void)pm_device_action_run(ls_1_8, PM_DEVICE_ACTION_SUSPEND); + (void)pm_device_action_run(cons, PM_DEVICE_ACTION_SUSPEND); /*const struct device *const i2c = DEVICE_DT_GET(DT_NODELABEL(i2c1)); ret = pm_device_action_run(i2c, PM_DEVICE_ACTION_SUSPEND); @@ -666,32 +682,13 @@ void PowerManager::charge_task() { battery_controller.enable_charge(); } - //if (last_charging_state != charging_state || ) { - k_work_submit(&fuel_gauge_work); - //state_inidicator.set_state() - /*switch (charging_state) { - case 0: - LOG_INF("charging state: ready"); - break; - case 1: - LOG_INF("charging state: charging"); - break; - case 2: - LOG_INF("charging state: done"); - break; - case 3: - LOG_WRN("charging state: fault"); - - //battery_controller.setup(_battery_settings); - - break; - }*/ - //} + k_work_submit(&fuel_gauge_work); last_charging_state = charging_state; } int cmd_setup_fuel_gauge(const struct shell *shell, size_t argc, const char **argv) { + ARG_UNUSED(shell); ARG_UNUSED(argc); ARG_UNUSED(argv); @@ -715,17 +712,17 @@ static int cmd_battery_info(const struct shell *shell, size_t argc, const char * // Basic measurements shell_print(shell, "Basic Measurements:"); - shell_print(shell, " Voltage: %.3f V", fuel_gauge.voltage()); - shell_print(shell, " Temperature: %.1f °C", fuel_gauge.temperature()); + shell_print(shell, " Voltage: %.3f V", (double)fuel_gauge.voltage()); + shell_print(shell, " Temperature: %.1f °C", (double)fuel_gauge.temperature()); shell_print(shell, " Current: %.1f mA (avg: %.1f mA)", - fuel_gauge.current(), fuel_gauge.average_current()); - shell_print(shell, " State of Charge: %.1f%%", fuel_gauge.state_of_charge()); + (double)fuel_gauge.current(), (double)fuel_gauge.average_current()); + shell_print(shell, " State of Charge: %.1f%%", (double)fuel_gauge.state_of_charge()); // Capacity info shell_print(shell, "Capacity Information:"); - shell_print(shell, " Design Capacity: %.1f mAh", fuel_gauge.design_cap()); - shell_print(shell, " Full Charge Capacity: %.1f mAh", fuel_gauge.capacity()); - shell_print(shell, " Remaining Capacity: %.1f mAh", fuel_gauge.remaining_cap()); + shell_print(shell, " Design Capacity: %.1f mAh", (double)fuel_gauge.design_cap()); + shell_print(shell, " Full Charge Capacity: %.1f mAh", (double)fuel_gauge.capacity()); + shell_print(shell, " Remaining Capacity: %.1f mAh", (double)fuel_gauge.remaining_cap()); // Time estimates float ttf = fuel_gauge.time_to_full(); @@ -744,7 +741,7 @@ static int cmd_battery_info(const struct shell *shell, size_t argc, const char * struct chrg_state charge_ctrl = battery_controller.read_charging_control(); shell_print(shell, " Charge Control: enabled=%i, current=%.1f mA", - charge_ctrl.enabled, charge_ctrl.mAh); + charge_ctrl.enabled, (double)charge_ctrl.mAh); battery_controller.enter_high_impedance(); @@ -758,4 +755,4 @@ SHELL_STATIC_SUBCMD_SET_CREATE(battery_cmd, SHELL_CMD_REGISTER(battery, &battery_cmd, "Power Manager Commands", NULL); -PowerManager power_manager; \ No newline at end of file +PowerManager power_manager; diff --git a/src/ParseInfo/DefaultSensors.h b/src/ParseInfo/DefaultSensors.h index 97ce4166..e6daef36 100644 --- a/src/ParseInfo/DefaultSensors.h +++ b/src/ParseInfo/DefaultSensors.h @@ -18,8 +18,8 @@ #define MICRO_CHANNEL_COUNT 2 SensorComponent microComponenents[MICRO_CHANNEL_COUNT] = { + { .name = "OUTER", .unit = "ADC", .parseType = PARSE_TYPE_UINT16 }, { .name = "INNER", .unit = "ADC", .parseType = PARSE_TYPE_UINT16 }, - { .name = "Outer", .unit = "ADC", .parseType = PARSE_TYPE_UINT16 }, }; #define MICRO_GROUP_COUNT 1 diff --git a/src/ParseInfo/ParseType.h b/src/ParseInfo/ParseType.h index 01dc819a..e92361f7 100644 --- a/src/ParseInfo/ParseType.h +++ b/src/ParseInfo/ParseType.h @@ -15,7 +15,7 @@ enum ParseType { PARSE_TYPE_DOUBLE, }; -const int parseTypeSizes[] = { +static const int parseTypeSizes[] = { 1, // PARSE_TYPE_INT8 1, // PARSE_TYPE_UINT8 diff --git a/src/ParseInfo/SensorScheme.cpp b/src/ParseInfo/SensorScheme.cpp index 292dc403..6639af5a 100644 --- a/src/ParseInfo/SensorScheme.cpp +++ b/src/ParseInfo/SensorScheme.cpp @@ -54,6 +54,9 @@ static ssize_t write_sensor_request(struct bt_conn *conn, uint16_t len, uint16_t offset, uint8_t flags) { + ARG_UNUSED(attr); + ARG_UNUSED(offset); + ARG_UNUSED(flags); if (len != sizeof(uint8_t)) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } @@ -73,6 +76,7 @@ static ssize_t write_sensor_request(struct bt_conn *conn, void scheme_ccc_cfg(const struct bt_gatt_attr *attr, uint16_t value) { + ARG_UNUSED(attr); notify_enabled = (value == BT_GATT_CCC_NOTIFY); } diff --git a/src/SD_Card/Benchmark/Benchmark.cpp b/src/SD_Card/Benchmark/Benchmark.cpp index 54a84ed5..48c0750e 100644 --- a/src/SD_Card/Benchmark/Benchmark.cpp +++ b/src/SD_Card/Benchmark/Benchmark.cpp @@ -1,7 +1,7 @@ #include "Benchmark.h" -Benchmark::Benchmark(void (*function)()) { - this->function = function; +Benchmark::Benchmark(void (*benchmark_function)()) { + this->function = benchmark_function; } void Benchmark::start() { @@ -18,4 +18,4 @@ uint32_t Benchmark::get_time() { uint32_t Benchmark::get_cycles() { return this->end_cycles - this->start_cycles; -} \ No newline at end of file +} diff --git a/src/SD_Card/SDLogger/SDLogger.cpp b/src/SD_Card/SDLogger/SDLogger.cpp index 335d4b1b..4635c1da 100644 --- a/src/SD_Card/SDLogger/SDLogger.cpp +++ b/src/SD_Card/SDLogger/SDLogger.cpp @@ -102,6 +102,7 @@ void sensor_listener_cb(const struct zbus_channel *chan) { void sd_listener_callback(const struct zbus_channel *chan) { + ARG_UNUSED(chan); const struct sd_msg * sd_msg_event = (sd_msg *)zbus_chan_const_msg(&sd_card_chan); if (sdlogger.is_open && sd_msg_event->removed) { @@ -438,9 +439,9 @@ int SDLogger::write_sensor_data(const void* const* data_blocks, const size_t* le return 0; } -int SDLogger::write_sensor_data(const sensor_data& msg) { - const size_t data_size = sizeof(sensor_data) - sizeof(msg.data) + msg.size; - const void* msg_ptr = &msg; +int SDLogger::write_sensor_data(const sensor_data& sensor_message) { + const size_t data_size = sizeof(sensor_data) - sizeof(sensor_message.data) + sensor_message.size; + const void* msg_ptr = &sensor_message; return write_sensor_data(&msg_ptr, &data_size, 1); } diff --git a/src/SD_Card/SDLogger/SDLogger.h b/src/SD_Card/SDLogger/SDLogger.h index 279fdae0..0b037e87 100644 --- a/src/SD_Card/SDLogger/SDLogger.h +++ b/src/SD_Card/SDLogger/SDLogger.h @@ -23,11 +23,6 @@ static_assert(BUFFER_SIZE % SD_BLOCK_SIZE == 0, "BUFFER_SIZE must be a multiple // Singleton pattern class SDLogger { -protected: - // Add static instance pointer for work handler - //static SDLogger* instance_ptr; - //friend void sd_work_handler(struct k_work* work); - private: SDCardManager* sd_card = nullptr; @@ -84,14 +79,23 @@ class SDLogger { */ int write_sensor_data(const void* const* data_blocks, const size_t* lengths, size_t block_count); + /** + * @brief Write a single sensor_data message to the log file + * @param msg The sensor_data message to write + * @return 0 on success, negative error code on failure + */ int write_sensor_data(const sensor_data& msg); /** - * @brief End logging and close the current file - * @return 0 on success, negative error code on failure - */ + * @brief End logging and close the current file + * @return 0 on success, negative error code on failure + */ int end(); + /** + * @brief Check if the logger is currently recording + * @return true if active, false otherwise + */ bool is_active(); SDLogger(SDLogger const&) = delete; diff --git a/src/SD_Card/SD_Card_Manager/SD_Card_Manager.cpp b/src/SD_Card/SD_Card_Manager/SD_Card_Manager.cpp index 9620a2eb..eb2a8ac8 100644 --- a/src/SD_Card/SD_Card_Manager/SD_Card_Manager.cpp +++ b/src/SD_Card/SD_Card_Manager/SD_Card_Manager.cpp @@ -35,6 +35,7 @@ bool SDCardManager::sd_inserted() { } void SDCardManager::unmount_work_handler(struct k_work *work) { + ARG_UNUSED(work); int ret; bool _inserted = sdcard_manager.sd_inserted(); @@ -43,6 +44,9 @@ void SDCardManager::unmount_work_handler(struct k_work *work) { if (!_inserted) { ret = sdcard_manager.unmount(); + if (ret != 0) { + LOG_ERR("Failed to unmount SD card: %d", ret); + } LOG_INF("SD card unmounted due to card removal."); ret = zbus_chan_pub(&sd_card_chan, &msg, K_FOREVER); @@ -55,10 +59,13 @@ void SDCardManager::unmount_work_handler(struct k_work *work) { K_WORK_DELAYABLE_DEFINE(SDCardManager::unmount_work, SDCardManager::unmount_work_handler); void SDCardManager::sd_card_state_change_isr(const struct device *dev, struct gpio_callback *cb, uint32_t pins) { + ARG_UNUSED(dev); + ARG_UNUSED(cb); + ARG_UNUSED(pins); k_work_reschedule(&sdcard_manager.unmount_work, SD_DEBOUNCE_MS); } -SDCardManager::SDCardManager(): path(SD_ROOT_PATH) { +SDCardManager::SDCardManager(): current_path(SD_ROOT_PATH) { fs_dir_t_init(&this->dirp); } @@ -108,13 +115,11 @@ int SDCardManager::aquire_ls() { } int SDCardManager::release_ls() { - int ret; - if (!ls_aquired) return -EALREADY; - ret = pm_device_runtime_put(ls_1_8); - ret = pm_device_runtime_put(ls_3_3); - ret = pm_device_runtime_put(ls_sd); + (void)pm_device_runtime_put(ls_1_8); + (void)pm_device_runtime_put(ls_3_3); + (void)pm_device_runtime_put(ls_sd); ls_aquired = false; @@ -125,7 +130,7 @@ void SDCardManager::init() { int ret; if (!device_is_ready(sd_state_pin.port)) { - ret = aquire_ls(); + (void)aquire_ls(); LOG_ERR("SD state GPIO device not ready\n"); return; } @@ -175,7 +180,7 @@ int SDCardManager::mount() { uint32_t sector_count; size_t sector_size; - ret = aquire_ls(); + (void)aquire_ls(); bool _sd_inserted = sd_inserted(); @@ -239,8 +244,8 @@ int SDCardManager::mount() { return ret; } - LOG_DBG("Root dir: %s", this->path.c_str()); - ret = fs_opendir(&this->dirp, this->path.c_str()); + LOG_DBG("Root dir: %s", this->current_path.c_str()); + ret = fs_opendir(&this->dirp, this->current_path.c_str()); k_mutex_unlock(&m_sem_sd_mngr_oper_ongoing); if (ret) { release_ls(); @@ -282,7 +287,7 @@ int SDCardManager::cd(std::string path) { return ret; } - std::string abs_path_name = create_path(this->path, path); + std::string abs_path_name = create_path(this->current_path, path); LOG_DBG("abs path name:\t%s", abs_path_name.c_str()); @@ -292,8 +297,8 @@ int SDCardManager::cd(std::string path) { if (ret) { LOG_ERR("Open SD card dir failed: %d", ret); // Try to revert to the previous path if the new one fails - if (this->path != path) { - int rret = fs_opendir(&this->dirp, this->path.c_str()); + if (this->current_path != path) { + int rret = fs_opendir(&this->dirp, this->current_path.c_str()); if (rret) { LOG_ERR("Failed to cd back to previous dir: %d", rret); } @@ -302,7 +307,7 @@ int SDCardManager::cd(std::string path) { return ret; } - this->path = abs_path_name; + this->current_path = abs_path_name; k_mutex_unlock(&m_sem_sd_mngr_oper_ongoing); return 0; @@ -325,7 +330,7 @@ int SDCardManager::ls(char *buf, size_t *buf_size) { return -ENODEV; } - if (this->path.length() > CONFIG_FS_FATFS_MAX_LFN) { + if (this->current_path.length() > CONFIG_FS_FATFS_MAX_LFN) { LOG_ERR("Path is too long"); k_mutex_unlock(&m_sem_sd_mngr_oper_ongoing); return -FR_INVALID_NAME; @@ -348,7 +353,7 @@ int SDCardManager::ls(char *buf, size_t *buf_size) { &buf[used_buf_size], remaining_buf_size, "[%s]\t%s\n", entry.type == FS_DIR_ENTRY_DIR ? "DIR " : "FILE", entry.name); - if (len >= remaining_buf_size) { + if (len < 0 || (size_t)len >= remaining_buf_size) { LOG_ERR("Failed to append to buffer, error: %d", len); k_mutex_unlock(&m_sem_sd_mngr_oper_ongoing); return -EINVAL; @@ -385,7 +390,7 @@ int SDCardManager::mkdir(std::string path) { return -FR_INVALID_NAME; } - std::string abs_path_name = create_path(this->path, path); + std::string abs_path_name = create_path(this->current_path, path); ret = fs_mkdir(abs_path_name.c_str()); if (ret) { @@ -417,7 +422,7 @@ int SDCardManager::open_file(std::string path, bool write, bool append, bool cre return -ENAMETOOLONG; } - std::string abs_path_name = create_path(this->path, path); + std::string abs_path_name = create_path(this->current_path, path); if (this->tracked_file.is_open) { LOG_ERR("File is already open"); @@ -446,7 +451,7 @@ int SDCardManager::open_file(std::string path, bool write, bool append, bool cre } this->tracked_file.is_open = true; - this->path = abs_path_name; + this->current_path = abs_path_name; k_mutex_unlock(&m_sem_sd_mngr_oper_ongoing); return 0; @@ -478,10 +483,10 @@ int SDCardManager::close_file() { return ret; } - LOG_DBG("File %s closed", this->path.c_str()); - size_t last_slash_pos = this->path.find_last_of("/"); + LOG_DBG("File %s closed", this->current_path.c_str()); + size_t last_slash_pos = this->current_path.find_last_of("/"); if (last_slash_pos != std::string::npos) { - this->path = this->path.substr(0, last_slash_pos); + this->current_path = this->current_path.substr(0, last_slash_pos); } this->tracked_file.is_open = false; @@ -639,7 +644,7 @@ int SDCardManager::rm(std::string path) { return -FR_INVALID_NAME; } - std::string abs_path_name = create_path(this->path, path); + std::string abs_path_name = create_path(this->current_path, path); ret = fs_unlink(abs_path_name.c_str()); if (ret) { @@ -675,4 +680,4 @@ int SDCardManager::sync() { return 0; } -SDCardManager sdcard_manager; \ No newline at end of file +SDCardManager sdcard_manager; diff --git a/src/SD_Card/SD_Card_Manager/SD_Card_Manager.h b/src/SD_Card/SD_Card_Manager/SD_Card_Manager.h index a83a8534..804cc504 100644 --- a/src/SD_Card/SD_Card_Manager/SD_Card_Manager.h +++ b/src/SD_Card/SD_Card_Manager/SD_Card_Manager.h @@ -162,7 +162,7 @@ class SDCardManager { bool is_mounted() { return this->mounted; } private: - std::string path; + std::string current_path; bool mounted = false; struct fs_dir_t dirp; @@ -173,14 +173,21 @@ class SDCardManager { FATFS fat_fs; struct fs_mount_t mnt_pt = { + .node = {}, .type = FS_FATFS, + .mnt_point = nullptr, .fs_data = &fat_fs, + .storage_dev = nullptr, + .mountp_len = 0, + .fs = nullptr, + .flags = 0, }; struct tracked_fs_file_t { struct fs_file_t filep; bool is_open; } tracked_file = { + .filep = {}, .is_open = false, }; @@ -199,4 +206,4 @@ class SDCardManager { extern SDCardManager sdcard_manager; -#endif \ No newline at end of file +#endif diff --git a/src/SensorManager/.DS_Store b/src/SensorManager/.DS_Store deleted file mode 100644 index dd6b9a17..00000000 Binary files a/src/SensorManager/.DS_Store and /dev/null differ diff --git a/src/SensorManager/BMA580/BMA580_Sensor.cpp b/src/SensorManager/BMA580/BMA580_Sensor.cpp index 779f69ed..a3e4cc76 100644 --- a/src/SensorManager/BMA580/BMA580_Sensor.cpp +++ b/src/SensorManager/BMA580/BMA580_Sensor.cpp @@ -61,14 +61,14 @@ BMA580_dev_inf dev_info = { BMA5_INTF_RET_TYPE bma5_i2c_read(uint8_t reg_addr, uint8_t *reg_data, uint32_t len, void *intf_ptr) { int ret; - BMA580_dev_inf * dev_info = (BMA580_dev_inf *) intf_ptr; + BMA580_dev_inf * device_info = (BMA580_dev_inf *) intf_ptr; - dev_info->i2c_dev->aquire(); + device_info->i2c_dev->aquire(); - ret = i2c_burst_read(dev_info->i2c_dev->master, dev_info->addr, reg_addr, reg_data, len); + ret = i2c_burst_read(device_info->i2c_dev->master, device_info->addr, reg_addr, reg_data, len); if (ret) LOG_WRN("I2C read failed: %d\n", ret); - dev_info->i2c_dev->release(); + device_info->i2c_dev->release(); return 0; } @@ -77,14 +77,14 @@ BMA5_INTF_RET_TYPE bma5_i2c_read(uint8_t reg_addr, uint8_t *reg_data, uint32_t l */ BMA5_INTF_RET_TYPE bma5_i2c_write(uint8_t reg_addr, const uint8_t *reg_data, uint32_t len, void *intf_ptr) { - BMA580_dev_inf * dev_info = (BMA580_dev_inf *) intf_ptr; + BMA580_dev_inf * device_info = (BMA580_dev_inf *) intf_ptr; - dev_info->i2c_dev->aquire(); + device_info->i2c_dev->aquire(); - int ret = i2c_burst_write(dev_info->i2c_dev->master, dev_info->addr, reg_addr, reg_data, len); + int ret = i2c_burst_write(device_info->i2c_dev->master, device_info->addr, reg_addr, reg_data, len); if (ret) LOG_WRN("I2C write failed: %d", ret); - dev_info->i2c_dev->release(); + device_info->i2c_dev->release(); return 0; } @@ -94,6 +94,7 @@ BMA5_INTF_RET_TYPE bma5_i2c_write(uint8_t reg_addr, const uint8_t *reg_data, uin */ void bma5_delay_us(uint32_t period, void *intf_ptr) { + ARG_UNUSED(intf_ptr); k_usleep(period); } @@ -160,7 +161,7 @@ int8_t bma5_interface_init(struct bma5_dev *bma5, uint8_t intf, enum bma5_contex /*! * @brief This internal API is used to enable accel and interrupt configuration settings. */ -int8_t BMA580::get_accel_and_int_settings(struct bma5_dev *dev) +int8_t BMA580::get_accel_and_int_settings(struct bma5_dev *device) { int8_t rslt; uint8_t n_ints = 1; @@ -171,10 +172,10 @@ int8_t BMA580::get_accel_and_int_settings(struct bma5_dev *dev) int_config.int_src = BMA5_INT_1; /* Get accel configurations */ - rslt = bma5_get_acc_conf_0(&sensor_ctrl, dev); + rslt = bma5_get_acc_conf_0(&sensor_ctrl, device); bma5_check_rslt("bma5_get_acc_conf_0", rslt); - rslt = bma5_get_acc_conf(&acc_cfg, dev); + rslt = bma5_get_acc_conf(&acc_cfg, device); bma5_check_rslt("bma5_get_acc_conf", rslt); /* Set accel configurations */ @@ -188,7 +189,7 @@ int8_t BMA580::get_accel_and_int_settings(struct bma5_dev *dev) acc_cfg.noise_mode = BMA5_NOISE_MODE_LOWER_NOISE; acc_cfg.acc_drdy_int_auto_clear = BMA5_ACC_DRDY_INT_AUTO_CLEAR_DISABLED; - rslt = bma5_set_acc_conf(&acc_cfg, dev); + rslt = bma5_set_acc_conf(&acc_cfg, device); bma5_check_rslt("bma5_get_acc_conf", rslt); /*LOG_DBG("Accel configurations"); @@ -203,7 +204,7 @@ int8_t BMA580::get_accel_and_int_settings(struct bma5_dev *dev) /* Enable accel */ sensor_ctrl = BMA5_SENSOR_CTRL_ENABLE; - rslt = bma5_set_acc_conf_0(sensor_ctrl, dev); + rslt = bma5_set_acc_conf_0(sensor_ctrl, device); bma5_check_rslt("bma5_set_acc_conf_0", rslt); if (rslt == BMA5_OK) @@ -211,20 +212,20 @@ int8_t BMA580::get_accel_and_int_settings(struct bma5_dev *dev) LOG_DBG("Accel enabled"); } - rslt = bma5_get_acc_conf_0(&sensor_ctrl, dev); + rslt = bma5_get_acc_conf_0(&sensor_ctrl, device); bma5_check_rslt("bma5_set_acc_conf_0", rslt); - rslt = bma5_get_acc_conf(&get_acc_cfg, dev); + rslt = bma5_get_acc_conf(&get_acc_cfg, device); bma5_check_rslt("bma5_get_acc_conf", rslt); - rslt = bma5_get_int_conf(&int_config, n_ints, dev); + rslt = bma5_get_int_conf(&int_config, n_ints, device); bma5_check_rslt("bma5_get_int_conf", rslt); int_config.int_conf.int_mode = BMA5_INT1_MODE_LATCHED; int_config.int_conf.int_od = BMA5_INT1_OD_PUSH_PULL; int_config.int_conf.int_lvl = BMA5_INT1_LVL_ACTIVE_HIGH; - rslt = bma5_set_int_conf(&int_config, n_ints, dev); + rslt = bma5_set_int_conf(&int_config, n_ints, device); bma5_check_rslt("bma5_set_int_conf", rslt); /*LOG_DBG("\nInt Configurations"); @@ -238,24 +239,24 @@ int8_t BMA580::get_accel_and_int_settings(struct bma5_dev *dev) /*! * @brief This internal API gets FIFO configurations. */ -int8_t BMA580::get_fifo_conf(const struct bma5_fifo_conf *fifo_conf, struct bma5_dev *dev) +int8_t BMA580::get_fifo_conf(const struct bma5_fifo_conf *fifo_config, struct bma5_dev *device) { int8_t rslt; - struct bma5_fifo_conf read_fifo_conf = { 0 }; + struct bma5_fifo_conf read_fifo_conf{}; /* Set FIFO configuration. * NOTE 1: FIFO works only on header mode */ - rslt = bma5_set_fifo_conf(fifo_conf, dev); + rslt = bma5_set_fifo_conf(fifo_config, device); bma5_check_rslt("bma5_set_fifo_conf", rslt); /* Get FIFO configuration register */ - rslt = bma5_get_fifo_conf(&read_fifo_conf, dev); + rslt = bma5_get_fifo_conf(&read_fifo_conf, device); bma5_check_rslt("bma5_get_fifo_conf", rslt); return rslt; } -int BMA580::init(int odr, int fifo_watermark_level) { +int BMA580::init(int odr, int watermark_level) { int8_t rslt; struct bma580_int_map int_map, get_int_map; //struct bma5_fifo_conf fifo_conf; @@ -315,7 +316,7 @@ int BMA580::init(int odr, int fifo_watermark_level) { /* Update FIFO structure */ fifoframe.data = fifo_data; - rslt = bma5_set_fifo_wm(fifo_watermark_level, &dev); + rslt = bma5_set_fifo_wm(watermark_level, &dev); bma5_check_rslt("bma5_set_fifo_wm", rslt); return rslt; @@ -336,7 +337,7 @@ int BMA580::stop() { int BMA580::read(bma5_sens_fifo_axes_data_16_bit *fifo_accel_data) { int8_t rslt = BMA5_OK; uint8_t n_status = 1; - struct bma580_int_status_types int_status = { 0 }; + struct bma580_int_status_types int_status{}; fifoframe.fifo_avail_frames = 0; @@ -364,4 +365,4 @@ int BMA580::read(bma5_sens_fifo_axes_data_16_bit *fifo_accel_data) { } return fifoframe.fifo_avail_frames; -} \ No newline at end of file +} diff --git a/src/SensorManager/BMA580/BMA580_Sensor.h b/src/SensorManager/BMA580/BMA580_Sensor.h index d8c0574d..1ff93349 100644 --- a/src/SensorManager/BMA580/BMA580_Sensor.h +++ b/src/SensorManager/BMA580/BMA580_Sensor.h @@ -164,7 +164,7 @@ class BMA580 { uint8_t fifo_data[BMA580_FIFO_RAW_DATA_BUFFER_SIZE] = { 0 }; /* Initialize FIFO frame structure */ - struct bma5_fifo_frame fifoframe = { 0 }; + struct bma5_fifo_frame fifoframe = {}; /* Set FIFO water-mark level */ uint16_t fifo_watermark_level = WATERMARK_LEVEL; diff --git a/src/SensorManager/BMA580/bma5.c b/src/SensorManager/BMA580/bma5.c index 62aa8f95..f47a2034 100644 --- a/src/SensorManager/BMA580/bma5.c +++ b/src/SensorManager/BMA580/bma5.c @@ -1927,9 +1927,7 @@ static int8_t unpack_accel_sensor_time_16_bit_data(uint8_t frame_header, if ((((*data_start_index) + BMA5_FIFO_16_BIT_ACCEL_XYZ_DATA) <= fifo->fifo_avail_len) && (frame_header != BMA5_FIFO_SENS_TIME_EN_DEDI_FRM)) { - if (frame_header & BMA5_FIFO_ACC_X_MSK) - if (frame_header & BMA5_FIFO_ACC_X_MSK) - { + if (frame_header & BMA5_FIFO_ACC_X_MSK) { /* Accel raw x data */ data_lsb = fifo->data[(*data_start_index)++]; data_msb = fifo->data[(*data_start_index)++]; diff --git a/src/SensorManager/BMP388/Adafruit_BMP3XX.cpp b/src/SensorManager/BMP388/Adafruit_BMP3XX.cpp index 95748ee2..9a11960f 100644 --- a/src/SensorManager/BMP388/Adafruit_BMP3XX.cpp +++ b/src/SensorManager/BMP388/Adafruit_BMP3XX.cpp @@ -112,9 +112,11 @@ bool Adafruit_BMP3XX::_init(void) { if (rslt != BMP3_OK) return false; - rslt = bmp3_init(&the_sensor); #ifdef BMP3XX_DEBUG - printk("Init result: %i\n", rslt); + const int8_t init_rslt = bmp3_init(&the_sensor); + printk("Init result: %i\n", init_rslt); +#else + (void)bmp3_init(&the_sensor); #endif rslt = validate_trimming_param(&the_sensor); @@ -432,7 +434,10 @@ int8_t i2c_write(uint8_t reg_addr, const uint8_t *reg_data, uint32_t len, return 0; } -static void delay_usec(uint32_t us, void *intf_ptr) { k_usleep(us); } +static void delay_usec(uint32_t us, void *intf_ptr) { + ARG_UNUSED(intf_ptr); + k_usleep(us); +} static int8_t validate_trimming_param(struct bmp3_dev *dev) { int8_t rslt; @@ -478,4 +483,4 @@ static int8_t cal_crc(uint8_t seed, uint8_t data) { } return (int8_t)seed; -} \ No newline at end of file +} diff --git a/src/SensorManager/BMP388/bmp3.c b/src/SensorManager/BMP388/bmp3.c index 2b614f3a..1e3b494b 100644 --- a/src/SensorManager/BMP388/bmp3.c +++ b/src/SensorManager/BMP388/bmp3.c @@ -1028,17 +1028,17 @@ int8_t bmp3_get_fifo_data(struct bmp3_dev *dev) /* Get the total no of bytes available in FIFO */ rslt = bmp3_get_fifo_length(&fifo_len, dev); - /* For sensor time frame */ - if (dev->fifo->settings.time_en == TRUE) + if (rslt == BMP3_OK) { - fifo_len = fifo_len + 4; - } + /* For sensor time frame */ + if (dev->fifo->settings.time_en == TRUE) + { + fifo_len = fifo_len + 4; + } - /* Update the fifo length in the fifo structure */ - dev->fifo->data.byte_count = fifo_len; + /* Update the fifo length in the fifo structure */ + dev->fifo->data.byte_count = fifo_len; - if (rslt == BMP3_OK) - { /* Read the fifo data */ rslt = bmp3_get_regs(BMP3_REG_FIFO_DATA, fifo->data.buffer, fifo_len, dev); } @@ -1089,7 +1089,7 @@ int8_t bmp3_extract_fifo_data(struct bmp3_data *data, struct bmp3_dev *dev) uint8_t header; uint8_t parsed_frames = 0; uint8_t t_p_frame; - struct bmp3_uncomp_data uncomp_data; + struct bmp3_uncomp_data uncomp_data = { 0 }; rslt = null_ptr_check(dev); @@ -2377,18 +2377,18 @@ static double compensate_pressure(const struct bmp3_uncomp_data *uncomp_data, co double partial_out2; partial_data1 = quantized_calib_data->par_p6 * quantized_calib_data->t_lin; - partial_data2 = quantized_calib_data->par_p7 * pow_bmp3(quantized_calib_data->t_lin, 2); - partial_data3 = quantized_calib_data->par_p8 * pow_bmp3(quantized_calib_data->t_lin, 3); + partial_data2 = (double)quantized_calib_data->par_p7 * (double)pow_bmp3(quantized_calib_data->t_lin, 2); + partial_data3 = (double)quantized_calib_data->par_p8 * (double)pow_bmp3(quantized_calib_data->t_lin, 3); partial_out1 = quantized_calib_data->par_p5 + partial_data1 + partial_data2 + partial_data3; partial_data1 = quantized_calib_data->par_p2 * quantized_calib_data->t_lin; - partial_data2 = quantized_calib_data->par_p3 * pow_bmp3(quantized_calib_data->t_lin, 2); - partial_data3 = quantized_calib_data->par_p4 * pow_bmp3(quantized_calib_data->t_lin, 3); + partial_data2 = (double)quantized_calib_data->par_p3 * (double)pow_bmp3(quantized_calib_data->t_lin, 2); + partial_data3 = (double)quantized_calib_data->par_p4 * (double)pow_bmp3(quantized_calib_data->t_lin, 3); partial_out2 = uncomp_data->pressure * (quantized_calib_data->par_p1 + partial_data1 + partial_data2 + partial_data3); partial_data1 = pow_bmp3((double)uncomp_data->pressure, 2); partial_data2 = quantized_calib_data->par_p9 + quantized_calib_data->par_p10 * quantized_calib_data->t_lin; partial_data3 = partial_data1 * partial_data2; - partial_data4 = partial_data3 + pow_bmp3((double)uncomp_data->pressure, 3) * quantized_calib_data->par_p11; + partial_data4 = partial_data3 + (double)pow_bmp3((double)uncomp_data->pressure, 3) * (double)quantized_calib_data->par_p11; comp_press = partial_out1 + partial_out2 + partial_data4; return comp_press; diff --git a/src/SensorManager/BMX160/DFRobot_BMX160.cpp b/src/SensorManager/BMX160/DFRobot_BMX160.cpp index ace06844..659b38a2 100644 --- a/src/SensorManager/BMX160/DFRobot_BMX160.cpp +++ b/src/SensorManager/BMX160/DFRobot_BMX160.cpp @@ -92,15 +92,7 @@ void DFRobot_BMX160::wakeUp(){ bool DFRobot_BMX160::softReset() { - int8_t rslt=BMX160_OK; - if (Obmx160 == NULL){ - rslt = BMX160_E_NULL_PTR; - } - rslt = _softReset(Obmx160); - if (rslt == 0) - return true; - else - return false; + return _softReset(Obmx160) == BMX160_OK; } int8_t DFRobot_BMX160:: _softReset(sBmx160Dev_t *dev) diff --git a/src/SensorManager/Baro.cpp b/src/SensorManager/Baro.cpp index 955b9519..f8340a08 100644 --- a/src/SensorManager/Baro.cpp +++ b/src/SensorManager/Baro.cpp @@ -35,6 +35,7 @@ const SampleRateSetting<18> Baro::sample_rates = { }; void Baro::update_sensor(struct k_work *work) { + ARG_UNUSED(work); int ret; bmp.performReading(); @@ -51,7 +52,10 @@ void Baro::update_sensor(struct k_work *work) { msg_baro.data.size = 2 * sizeof(float); msg_baro.data.time = micros(); - float data[2] = {bmp.temperature, bmp.pressure}; + float data[2] = { + static_cast(bmp.temperature), + static_cast(bmp.pressure), + }; memcpy(msg_baro.data.data, data, 2 * sizeof(float)); @@ -66,6 +70,7 @@ void Baro::update_sensor(struct k_work *work) { */ void Baro::sensor_timer_handler(struct k_timer *dummy) { + ARG_UNUSED(dummy); k_work_submit_to_queue(&sensor_work_q, &sensor.sensor_work); }; @@ -93,7 +98,7 @@ bool Baro::init(struct k_msgq * queue) { void Baro::start(int sample_rate_idx) { baro_initial_discard = 1; - k_timeout_t t = K_USEC(1e6 / sample_rates.true_sample_rates[sample_rate_idx]); + k_timeout_t t = K_USEC(1000000.0f / sample_rates.true_sample_rates[sample_rate_idx]); //bmp.set_interrogation_rate(setting.reg_val); //bmp.start(); @@ -112,4 +117,4 @@ void Baro::stop() { k_timer_stop(&sensor.sensor_timer); pm_device_runtime_put(ls_1_8); -} \ No newline at end of file +} diff --git a/src/SensorManager/BoneConduction.cpp b/src/SensorManager/BoneConduction.cpp index dbbad21c..26d4f648 100644 --- a/src/SensorManager/BoneConduction.cpp +++ b/src/SensorManager/BoneConduction.cpp @@ -48,6 +48,7 @@ void BoneConduction::reset() { } void BoneConduction::update_sensor(struct k_work *work) { + ARG_UNUSED(work); uint64_t _time_stamp = micros(); BoneConduction::sensor._sample_count += (_time_stamp - BoneConduction::sensor._last_time_stamp) / BoneConduction::sensor.t_sample_us; @@ -103,18 +104,19 @@ void BoneConduction::update_sensor(struct k_work *work) { * @brief Submit a k_work on timer expiry. */ void BoneConduction::sensor_timer_handler(struct k_timer *dummy) { + ARG_UNUSED(dummy); k_work_submit_to_queue(&sensor_work_q, &sensor.sensor_work); } void BoneConduction::start(int sample_rate_idx) { if (!_active) return; - t_sample_us = 1e6 / sample_rates.true_sample_rates[sample_rate_idx]; + t_sample_us = 1000000.0f / sample_rates.true_sample_rates[sample_rate_idx]; k_timeout_t t = K_USEC(t_sample_us); int word_size = 3 * sizeof(int16_t) + 1; - _num_samples_buffered = MIN(MAX(1, (int) (CONFIG_SENSOR_LATENCY_MS * 1e3 / t_sample_us)), 512 / word_size - 8); // Buffer size is 512 bytes + _num_samples_buffered = MIN(MAX(1, (int) (CONFIG_SENSOR_LATENCY_MS * 1000.0f / t_sample_us)), 512 / word_size - 8); // Buffer size is 512 bytes bma580.init(sample_rates.reg_vals[sample_rate_idx], _num_samples_buffered * word_size); bma580.start(); @@ -137,4 +139,4 @@ void BoneConduction::stop() { bma580.stop(); pm_device_runtime_put(ls_1_8); -} \ No newline at end of file +} diff --git a/src/SensorManager/IMU.cpp b/src/SensorManager/IMU.cpp index 1f0a59e8..cf7e9e49 100644 --- a/src/SensorManager/IMU.cpp +++ b/src/SensorManager/IMU.cpp @@ -25,6 +25,7 @@ const SampleRateSetting<6> IMU::sample_rates = { }; void IMU::update_sensor(struct k_work *work) { + ARG_UNUSED(work); int ret; sBmx160SensorData_t magno_data; @@ -57,6 +58,7 @@ void IMU::update_sensor(struct k_work *work) { */ void IMU::sensor_timer_handler(struct k_timer *dummy) { + ARG_UNUSED(dummy); k_work_submit_to_queue(&sensor_work_q, &sensor.sensor_work); }; @@ -86,7 +88,7 @@ bool IMU::init(struct k_msgq * queue) { void IMU::start(int sample_rate_idx) { if (!_active) return; - k_timeout_t t = K_USEC(1e6 / sample_rates.true_sample_rates[sample_rate_idx]); + k_timeout_t t = K_USEC(1000000.0f / sample_rates.true_sample_rates[sample_rate_idx]); imu.setAccelODR(sample_rates.reg_vals[sample_rate_idx]); @@ -107,4 +109,4 @@ void IMU::stop() { imu.softReset(); pm_device_runtime_put(ls_1_8); -} \ No newline at end of file +} diff --git a/src/SensorManager/MAXM86161/MAXM86161.cpp b/src/SensorManager/MAXM86161/MAXM86161.cpp index 7a199066..8774d01f 100644 --- a/src/SensorManager/MAXM86161/MAXM86161.cpp +++ b/src/SensorManager/MAXM86161/MAXM86161.cpp @@ -146,7 +146,7 @@ int MAXM86161::read(ppg_sample * buffer) { if (status == 0){ number_of_bytes = num_samples / LED_NUM * LED_NUM * BYTES_PER_CH; - status = _read_block(REG_FIFO_DATA, number_of_bytes, (uint8_t *) databuffer); + (void)_read_block(REG_FIFO_DATA, number_of_bytes, (uint8_t *) databuffer); for (int i=0; i < num_samples / LED_NUM * LED_NUM; i++) { int idx = BYTES_PER_CH * i; @@ -436,7 +436,7 @@ int MAXM86161::_clear_interrupt(void) int MAXM86161::read_interrupt_state(int &value) { int status; - status = _read_from_reg(REG_IRQ_STATUS2, value); + (void)_read_from_reg(REG_IRQ_STATUS2, value); status = _read_from_reg(REG_IRQ_STATUS1, value); return status; } diff --git a/src/SensorManager/MLX90632/MLX90632.cpp b/src/SensorManager/MLX90632/MLX90632.cpp index ab98e9de..b794e952 100644 --- a/src/SensorManager/MLX90632/MLX90632.cpp +++ b/src/SensorManager/MLX90632/MLX90632.cpp @@ -244,11 +244,9 @@ float MLX90632::getObjectTemp(status& returnError) double AMB = (sixRAM / 12.0) / VRta * pow(2, 19); - double sensorTemp = P_O + (AMB - P_R) / P_G + P_T * pow((AMB - P_R), 2); - - float S = (float)(lowerRAM + upperRAM) / 2.0; + float S = (float)(lowerRAM + upperRAM) / 2.0f; double VRto = nineRAM + Ka * (sixRAM / 12.0); - double Sto = (S / 12.0) / VRto * (double)pow(2, 19); + double Sto = ((double)S / 12.0) / VRto * pow(2, 19); double TAdut = (AMB - Eb) / Ea + 25.0; @@ -270,7 +268,7 @@ float MLX90632::getObjectTemp(status& returnError) float MLX90632::getObjectTempF() { float tempC = getObjectTemp(); - float tempF = tempC * 9.0/5.0 + 32.0; + float tempF = tempC * 9.0f / 5.0f + 32.0f; return(tempF); } @@ -324,9 +322,9 @@ float MLX90632::gatherSensorTemp(status &returnError) double AMB = (sixRAM / 12.0) / VRta * pow(2, 19); - double sensorTemp = P_O + (AMB - P_R) / P_G + P_T * pow((AMB - P_R), 2); + double calculated_sensor_temp = P_O + (AMB - P_R) / P_G + P_T * pow((AMB - P_R), 2); - return(sensorTemp); + return(calculated_sensor_temp); } //Returns true if device is busy doing measurement @@ -348,9 +346,9 @@ bool MLX90632::eepromBusy() //Returns the cycle_pos from status register. cycle_pos is 0 to 31 uint8_t MLX90632::getCyclePosition() { - uint16_t status = getStatus() >> BIT_CYCLE_POS; //Shave off last two bits - status &= 0x1F; //Get lower 5 bits. - return (status); + uint16_t cycle_status = getStatus() >> BIT_CYCLE_POS; //Shave off last two bits + cycle_status &= 0x1F; //Get lower 5 bits. + return (cycle_status); } //Returns true if new data is available @@ -447,6 +445,7 @@ uint8_t MLX90632::getMode(status &returnError) //to something unknown you'll need to use the I2C scanner sketch to detect it again void MLX90632::writeI2CAddress(uint8_t newAddress) { + ARG_UNUSED(newAddress); } @@ -623,4 +622,4 @@ float MLX90632::getSampleRate() { result = (1 << val1) / 2; return result; -} \ No newline at end of file +} diff --git a/src/SensorManager/Microphone.cpp b/src/SensorManager/Microphone.cpp index aaddcdb9..ed1c4f1a 100644 --- a/src/SensorManager/Microphone.cpp +++ b/src/SensorManager/Microphone.cpp @@ -56,11 +56,9 @@ bool Microphone::init(struct k_msgq * queue) { void Microphone::start(int sample_rate_idx) { //ARG_UNUSED(sample_rate_idx); - int ret; - if (!_active) return; - LOG_INF("Starting Microphone at %f Hz", sample_rates.sample_rates[sample_rate_idx]); + LOG_INF("Starting Microphone at %f Hz", (double)sample_rates.sample_rates[sample_rate_idx]); record_to_sd(_sd_logging); audio_datapath_aquire(&fifo_rx); @@ -85,4 +83,4 @@ void Microphone::stop() { //audio_datapath_decimator_cleanup(); _running = false; -} \ No newline at end of file +} diff --git a/src/SensorManager/PPG.cpp b/src/SensorManager/PPG.cpp index 33132b3e..1358fbec 100644 --- a/src/SensorManager/PPG.cpp +++ b/src/SensorManager/PPG.cpp @@ -66,6 +66,7 @@ bool PPG::init(struct k_msgq * queue) { } void PPG::update_sensor(struct k_work *work) { + ARG_UNUSED(work); int int_status; int status; @@ -130,17 +131,18 @@ void PPG::update_sensor(struct k_work *work) { * @brief Submit a k_work on timer expiry. */ void PPG::sensor_timer_handler(struct k_timer *dummy) { + ARG_UNUSED(dummy); k_work_submit_to_queue(&sensor_work_q, &sensor.sensor_work); } void PPG::start(int sample_rate_idx) { if (!_active) return; - t_sample_us = 1e6 / sample_rates.true_sample_rates[sample_rate_idx]; + t_sample_us = 1000000.0f / sample_rates.true_sample_rates[sample_rate_idx]; k_timeout_t t = K_USEC(t_sample_us); - _num_samples_buffered = MIN(MAX(1, (int) (CONFIG_SENSOR_LATENCY_MS * 1e3 / t_sample_us)), FIFO_SIZE / LED_NUM - 2); + _num_samples_buffered = MIN(MAX(1, (int) (CONFIG_SENSOR_LATENCY_MS * 1000.0f / t_sample_us)), FIFO_SIZE / LED_NUM - 2); ppg.set_interrogation_rate(sample_rates.reg_vals[sample_rate_idx]); ppg.set_watermark(FIFO_SIZE - _num_samples_buffered * LED_NUM); @@ -165,4 +167,4 @@ void PPG::stop() { pm_device_runtime_put(ls_1_8); pm_device_runtime_put(ls_3_3); -} \ No newline at end of file +} diff --git a/src/SensorManager/SensorManager.cpp b/src/SensorManager/SensorManager.cpp index 1eea6daf..d5d8364c 100644 --- a/src/SensorManager/SensorManager.cpp +++ b/src/SensorManager/SensorManager.cpp @@ -71,10 +71,13 @@ static const char sensor_manager_auto_off_token[] = "SensorManager"; static void config_work_handler(struct k_work *work); void sensor_chan_update(void *p1, void *p2, void *p3) { + ARG_UNUSED(p1); + ARG_UNUSED(p2); + ARG_UNUSED(p3); int ret; while (1) { - ret = k_poll(&sensor_manager_evt, 1, K_FOREVER); + (void)k_poll(&sensor_manager_evt, 1, K_FOREVER); k_msgq_get(&sensor_queue, &msg, K_FOREVER); @@ -190,6 +193,7 @@ EdgeMlSensor * get_sensor(enum sensor_id id) { // Worker-Funktion für die Sensor-Konfiguration static void config_work_handler(struct k_work *work) { + ARG_UNUSED(work); int ret; struct sensor_config config; @@ -200,7 +204,7 @@ static void config_work_handler(struct k_work *work) { float sampleRate = getSampleRateForSensorId(config.sensorId, config.sampleRateIndex); if (sampleRate <= 0) { - LOG_ERR("Invalid sample rate %f for sensor %i", sampleRate, config.sensorId); + LOG_ERR("Invalid sample rate %f for sensor %i", (double)sampleRate, config.sensorId); return; } @@ -243,8 +247,9 @@ static void config_work_handler(struct k_work *work) { LOG_INF("Starting SDLogger with recording name prefix: %s", recording_name_prefix); // Start SDLogger with timestamp-based filename std::string filename = recording_name_prefix + std::to_string(micros()); - int ret = sdlogger.begin(filename); - if (ret == 0) state_indicator.set_sd_state(SD_RECORDING); + int logger_ret = sdlogger.begin(filename); + if (logger_ret == 0) state_indicator.set_sd_state(SD_RECORDING); + else LOG_ERR("Failed to start SDLogger, ret: %d", logger_ret); } } else if (sd_sensors.find(config.sensorId) != sd_sensors.end()) { sd_sensors.erase(config.sensorId); @@ -277,4 +282,4 @@ void config_sensor(struct sensor_config * config) { //k_work_queue_drain(&sensor_work_q, true); k_work_submit(&config_work); //k_work_queue_unplug(&sensor_work_q); -} \ No newline at end of file +} diff --git a/src/SensorManager/Temp.cpp b/src/SensorManager/Temp.cpp index 24862dfd..935d4812 100644 --- a/src/SensorManager/Temp.cpp +++ b/src/SensorManager/Temp.cpp @@ -49,6 +49,7 @@ bool Temp::init(struct k_msgq * queue) { } void Temp::update_sensor(struct k_work *work) { + ARG_UNUSED(work); if (!temp.dataAvailable()) return; MLX90632::status returnError; @@ -78,13 +79,14 @@ void Temp::update_sensor(struct k_work *work) { * @brief Submit a k_work on timer expiry. */ void Temp::sensor_timer_handler(struct k_timer *dummy) { + ARG_UNUSED(dummy); k_work_submit_to_queue(&sensor_work_q, &sensor.sensor_work); } void Temp::start(int sample_rate_idx) { if (!_active) return; - k_timeout_t t = K_USEC(1e6 / sample_rates.true_sample_rates[sample_rate_idx]); + k_timeout_t t = K_USEC(1000000.0f / sample_rates.true_sample_rates[sample_rate_idx]); temp.setSampleRateRegVal(sample_rates.reg_vals[sample_rate_idx]); temp.continuousMode(); @@ -106,4 +108,4 @@ void Temp::stop() { pm_device_runtime_put(ls_1_8); pm_device_runtime_put(ls_3_3); -} \ No newline at end of file +} diff --git a/src/Wire/TWIM.cpp b/src/Wire/TWIM.cpp index 057dada0..93959680 100644 --- a/src/Wire/TWIM.cpp +++ b/src/Wire/TWIM.cpp @@ -7,8 +7,6 @@ LOG_MODULE_REGISTER(twim, CONFIG_AUDIO_DATAPATH_LOG_LEVEL); TWIM::TWIM(const struct device * _device) : master(_device) {} void TWIM::begin() { - int ret; - if (_active) return; _active = true; @@ -52,4 +50,4 @@ void TWIM::release() { TWIM I2C1(DEVICE_DT_GET(DT_NODELABEL(i2c1))); TWIM I2C2(DEVICE_DT_GET(DT_NODELABEL(i2c2))); -TWIM I2C3(DEVICE_DT_GET(DT_NODELABEL(i2c3))); \ No newline at end of file +TWIM I2C3(DEVICE_DT_GET(DT_NODELABEL(i2c3))); diff --git a/src/audio/audio_datapath.c b/src/audio/audio_datapath.c index eac3eb65..ee944999 100644 --- a/src/audio/audio_datapath.c +++ b/src/audio/audio_datapath.c @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #include #include @@ -29,7 +31,14 @@ #include "Equalizer.h" #include "sdlogger_wrapper.h" #include "decimation_filter.h" +#include "../SensorManager/SensorManager.h" #include "arm_math.h" +#include "hw_codec.h" +//#include "../drivers/ADAU1860.h" + +#include "../SensorManager/SensorManager.h" +#include "openearable_common.h" +#include "SensorScheme.h" #include LOG_MODULE_REGISTER(audio_datapath, CONFIG_AUDIO_DATAPATH_LOG_LEVEL); @@ -95,6 +104,10 @@ LOG_MODULE_REGISTER(audio_datapath, CONFIG_AUDIO_DATAPATH_LOG_LEVEL); /* How often to print under-run warning */ #define UNDERRUN_LOG_INTERVAL_BLKS 5000 +/* Smooth the transition from silence to local buffer playback. */ +#define BUFFER_PLAY_FADE_IN_MS 5U +#define BUFFER_PLAY_FADE_IN_SAMPLES ((CONFIG_AUDIO_SAMPLE_RATE_HZ * BUFFER_PLAY_FADE_IN_MS) / 1000U) + enum drift_comp_state { DRIFT_STATE_INIT, /* Waiting for data to be received */ DRIFT_STATE_CALIB, /* Calibrate and zero out local delay */ @@ -175,10 +188,8 @@ static struct { static struct k_msgq * sensor_queue; -//extern struct audio_data fifo_rx; - //K_MSGQ_DEFINE(rx_queue, sizeof(struct audio_data), 16, 4); -extern struct k_msgq_t encoder_queue; +extern struct k_msgq encoder_queue; // Definition eines zbus-Kanals ZBUS_CHAN_DEFINE(audio_channel, struct audio_data, NULL, NULL, ZBUS_OBSERVERS_EMPTY, ZBUS_MSG_INIT(0)); @@ -190,17 +201,44 @@ static k_tid_t data_thread_id; bool _record_to_sd = false; +// Buffer recording variables +static bool _record_to_buffer = false; +static int16_t *_record_buffer = NULL; +static int _record_num_samples = 0; +static int _record_current_index = 0; +static bool _record_left = false; +static bool _record_right = false; +static void (*_record_callback)(void) = NULL; + int _count = 0; +static int16_t *buffer_play_data = NULL; +static uint32_t buffer_play_pos; +static uint32_t buffer_play_fade_pos; +static uint32_t buffer_play_num_samples; +static float buffer_play_amplitude; +static bool buffer_play_loop; +static void (*buffer_play_callback)(void) = NULL; + extern struct k_poll_signal encoder_sig; extern struct k_poll_event logger_sig; -/* Output buffer sized for the largest processed output: decimation factor 2. */ -static int16_t decimated_audio[BLOCK_SIZE_BYTES / sizeof(int16_t) / 2]; +/* + * Decimation output for one interleaved stereo audio block. + * + * The decimator supports factors down to 1, so its worst-case output contains + * every input sample. Seal check currently uses factor 3 (48 kHz -> 16 kHz); + * sizing this buffer for the old fixed factor 4 overflowed it on every block. + */ +static int16_t decimated_audio[BLOCK_SIZE_BYTES / sizeof(int16_t)]; // Funktion für den neuen Thread static void data_thread(void *arg1, void *arg2, void *arg3) { + ARG_UNUSED(arg1); + ARG_UNUSED(arg2); + ARG_UNUSED(arg3); + //struct audio_data audio_item; void *tmp_pcm_raw_data[CONFIG_FIFO_FRAME_SPLIT_NUM]; //char pcm_raw_data[FRAME_SIZE_BYTES]; @@ -224,9 +262,7 @@ static void data_thread(void *arg1, void *arg2, void *arg3) data_fifo_block_free(ctrl_blk.in.fifo, tmp_pcm_raw_data[i]); - unsigned int logger_signaled; - - if (_record_to_sd) { + if (_record_to_sd || _record_to_buffer) { /* Decimate audio data from 48kHz to the desired sampling rate */ int16_t *audio_block = (int16_t *)(audio_item.data + (i * BLOCK_SIZE_BYTES)); uint32_t num_frames = BLOCK_SIZE_BYTES / sizeof(int16_t) / 2; /* stereo frames */ @@ -238,6 +274,48 @@ static void data_thread(void *arg1, void *arg2, void *arg3) continue; } + // Generic buffer recording + if (_record_to_buffer && _record_buffer != NULL) { + for(int frame_index = 0; frame_index < decimated_frames; frame_index++) { + _record_current_index++; + + // Skip samples during initial drop period + if (_record_current_index <= 0) { + continue; + } + + // Calculate actual buffer index (after initial drop) + int buffer_index = _record_current_index - 1; + + if (buffer_index < _record_num_samples) { + if (_record_left && _record_right) { + // Stereo recording - store both channels + if (buffer_index * 2 + 1 < _record_num_samples) { + _record_buffer[buffer_index * 2] = decimated_audio[2 * frame_index]; // Left + _record_buffer[buffer_index * 2 + 1] = decimated_audio[2 * frame_index + 1]; // Right + } + } else if (_record_left) { + // Left channel only + _record_buffer[buffer_index] = decimated_audio[2 * frame_index]; + } else if (_record_right) { + // Right channel only + _record_buffer[buffer_index] = decimated_audio[2 * frame_index + 1]; + } + + } + + // Check if recording is complete + if (buffer_index >= _record_num_samples || + (_record_left && _record_right && buffer_index * 2 >= _record_num_samples)) { + _record_to_buffer = false; + if (_record_callback) { + _record_callback(); + } + break; + } + } + } + struct sensor_msg audio_msg; audio_msg.sd = true; @@ -253,17 +331,17 @@ static void data_thread(void *arg1, void *arg2, void *arg3) audio_msg.data.size }; - void *data_ptrs[2] = { + const void *data_ptrs[2] = { &audio_msg.data, decimated_audio }; - if (decimated_frames == num_frames) { + if ((uint32_t)decimated_frames == num_frames) { data_ptrs[1] = audio_block; } if (decimated_frames > 0) { - sdlogger_write_data(&data_ptrs, data_size, 2); + sdlogger_write_data(data_ptrs, data_size, 2); } } @@ -293,12 +371,41 @@ void record_to_sd(bool active) { } void audio_datapath_stop_recording(void) { + // Stop buffer recording + _record_to_buffer = false; + _record_buffer = NULL; + _record_callback = NULL; + // Stop SD recording _record_to_sd = false; LOG_DBG("Audio recording stopped safely"); } +void record_to_buffer_stop(void) { + _record_to_buffer = false; + _record_buffer = NULL; + _record_callback = NULL; + LOG_DBG("Buffer recording stopped"); +} + +void record_to_buffer(int16_t *buffer, int num_samples, int initial_drop, bool left, bool right, void (*callback)(void)) { + if (buffer == NULL || num_samples <= 0) { + LOG_ERR("Invalid buffer recording parameters"); + return; + } + + _record_buffer = buffer; + _record_num_samples = num_samples; + _record_current_index = -initial_drop; + _record_left = left; + _record_right = right; + _record_callback = callback; + _record_to_buffer = true; + + LOG_INF("Started buffer recording: %d samples, initial_drop=%d, left=%d, right=%d", num_samples, initial_drop, left, right); +} + // Funktion, um den neuen Thread zu starten void start_data_thread(void) { @@ -330,6 +437,29 @@ static bool tone_active; static uint16_t test_tone_buf[CONFIG_AUDIO_SAMPLE_RATE_HZ / 100]; static size_t test_tone_size; +struct auxiliary_audio_state { + bool suspended; + bool record_to_sd; + bool record_to_buffer; + int16_t *record_buffer; + int record_num_samples; + int record_current_index; + bool record_left; + bool record_right; + void (*record_callback)(void); + bool tone_active; + uint32_t tone_remaining_ms; + int16_t *buffer_data; + uint32_t buffer_pos; + uint32_t buffer_fade_pos; + int buffer_num_samples; + float buffer_amplitude; + bool buffer_loop; + void (*buffer_callback)(void); +}; + +static struct auxiliary_audio_state auxiliary_audio_state; + /** * @brief Calculate error between sdu_ref and frame_start_ts_us. * @@ -494,8 +624,6 @@ static void audio_datapath_drift_compensation(uint32_t frame_start_ts_us) static void pres_comp_state_set(enum pres_comp_state new_state) { - int ret; - if (new_state == ctrl_blk.pres_comp.state) { return; } @@ -505,6 +633,8 @@ static void pres_comp_state_set(enum pres_comp_state new_state) LOG_INF("Pres comp state: %s", pres_comp_state_names[new_state]); #if CONFIG_BOARD_NRF5340_AUDIO_DK_NRF5340_CPUAPP + int ret; + if (new_state == PRES_STATE_LOCKED) { ret = led_on(LED_APP_2_GREEN); } else { @@ -649,8 +779,20 @@ static void audio_datapath_presentation_compensation(uint32_t recv_frame_ts_us, static void tone_stop_worker(struct k_work *work) { + ARG_UNUSED(work); + tone_active = false; memset(test_tone_buf, 0, sizeof(test_tone_buf)); + + LOG_INF("Tone playback stopped"); + + // Call buffer playback callback if set + if (buffer_play_callback) { + buffer_play_callback(); + buffer_play_callback = NULL; + } + buffer_play_data = NULL; + LOG_DBG("Tone stopped"); struct sensor_config mic = {ID_MICRO, 0, 0}; @@ -661,11 +803,92 @@ K_WORK_DEFINE(tone_stop_work, tone_stop_worker); static void tone_stop_timer_handler(struct k_timer *dummy) { + ARG_UNUSED(dummy); + k_work_submit(&tone_stop_work); }; K_TIMER_DEFINE(tone_stop_timer, tone_stop_timer_handler, NULL); +int audio_datapath_auxiliary_suspend(void) +{ + if (auxiliary_audio_state.suspended) { + return -EBUSY; + } + + auxiliary_audio_state = (struct auxiliary_audio_state) { + .suspended = true, + .record_to_sd = _record_to_sd, + .record_to_buffer = _record_to_buffer, + .record_buffer = _record_buffer, + .record_num_samples = _record_num_samples, + .record_current_index = _record_current_index, + .record_left = _record_left, + .record_right = _record_right, + .record_callback = _record_callback, + .tone_active = tone_active, + .tone_remaining_ms = k_timer_remaining_get(&tone_stop_timer), + .buffer_data = buffer_play_data, + .buffer_pos = buffer_play_pos, + .buffer_fade_pos = buffer_play_fade_pos, + .buffer_num_samples = buffer_play_num_samples, + .buffer_amplitude = buffer_play_amplitude, + .buffer_loop = buffer_play_loop, + .buffer_callback = buffer_play_callback, + }; + + k_timer_stop(&tone_stop_timer); + (void)k_work_cancel(&tone_stop_work); + _record_to_sd = false; + _record_to_buffer = false; + _record_buffer = NULL; + _record_callback = NULL; + tone_active = false; + buffer_play_data = NULL; + buffer_play_callback = NULL; + + LOG_INF("Auxiliary audio suspended: tone=%d buffer=%d buffer_recording=%d sd_recording=%d", + auxiliary_audio_state.tone_active, auxiliary_audio_state.buffer_data != NULL, + auxiliary_audio_state.record_to_buffer, auxiliary_audio_state.record_to_sd); + return 0; +} + +int audio_datapath_auxiliary_resume(void) +{ + if (!auxiliary_audio_state.suspended) { + return -EALREADY; + } + + /* A completion queued by measurement playback must not clear restored state. */ + (void)k_work_cancel(&tone_stop_work); + _record_to_sd = auxiliary_audio_state.record_to_sd; + _record_to_buffer = auxiliary_audio_state.record_to_buffer; + _record_buffer = auxiliary_audio_state.record_buffer; + _record_num_samples = auxiliary_audio_state.record_num_samples; + _record_current_index = auxiliary_audio_state.record_current_index; + _record_left = auxiliary_audio_state.record_left; + _record_right = auxiliary_audio_state.record_right; + _record_callback = auxiliary_audio_state.record_callback; + tone_active = auxiliary_audio_state.tone_active; + buffer_play_data = auxiliary_audio_state.buffer_data; + buffer_play_pos = auxiliary_audio_state.buffer_pos; + buffer_play_fade_pos = auxiliary_audio_state.buffer_fade_pos; + buffer_play_num_samples = auxiliary_audio_state.buffer_num_samples; + buffer_play_amplitude = auxiliary_audio_state.buffer_amplitude; + buffer_play_loop = auxiliary_audio_state.buffer_loop; + buffer_play_callback = auxiliary_audio_state.buffer_callback; + + if (tone_active && auxiliary_audio_state.tone_remaining_ms > 0U) { + k_timer_start(&tone_stop_timer, K_MSEC(auxiliary_audio_state.tone_remaining_ms), + K_NO_WAIT); + } + + LOG_INF("Auxiliary audio resumed: tone=%d buffer=%d buffer_recording=%d sd_recording=%d", + tone_active, buffer_play_data != NULL, _record_to_buffer, _record_to_sd); + memset(&auxiliary_audio_state, 0, sizeof(auxiliary_audio_state)); + return 0; +} + int audio_datapath_tone_play(uint16_t freq, uint16_t dur_ms, float amplitude) { int ret; @@ -695,25 +918,100 @@ int audio_datapath_tone_play(uint16_t freq, uint16_t dur_ms, float amplitude) return 0; } +int audio_datapath_buffer_play(int16_t *buffer, int num_samples, bool loop, float amplitude, void (*callback)(void)) +{ + if (buffer_play_data != NULL) { + return -EBUSY; + } + + if (!IS_ENABLED(CONFIG_AUDIO_TEST_TONE)) { + LOG_WRN("Test tone disabled"); + return -ENOTSUP; + } + + if (buffer == NULL || num_samples <= 0) { + LOG_ERR("Invalid buffer play parameters"); + return -EINVAL; + } + + buffer_play_data = buffer; + buffer_play_pos = 0; + buffer_play_fade_pos = 0; + buffer_play_num_samples = (uint32_t)num_samples; + buffer_play_amplitude = amplitude; + buffer_play_loop = loop; + buffer_play_callback = callback; + + LOG_DBG("Buffer playback started: %d samples, loop=%d, amplitude=%.2f", num_samples, loop, (double)amplitude); + return 0; +} + void audio_datapath_tone_stop(void) { k_timer_stop(&tone_stop_timer); k_work_submit(&tone_stop_work); } +void audio_datapath_buffer_stop(void) +{ + k_timer_stop(&tone_stop_timer); + buffer_play_data = NULL; + if (buffer_play_callback) { + buffer_play_callback(); + buffer_play_callback = NULL; + } + LOG_DBG("Buffer playback stopped"); +} + static void tone_mix(uint8_t *tx_buf) { int ret; - int8_t tone_buf_continuous[BLK_MONO_SIZE_OCTETS]; - static uint32_t finite_pos; - ret = contin_array_create(tone_buf_continuous, BLK_MONO_SIZE_OCTETS, test_tone_buf, - test_tone_size, &finite_pos); - ERR_CHK(ret); + if (tone_active) { + int8_t tone_buf_continuous[BLK_MONO_SIZE_OCTETS]; + static uint32_t finite_pos; + + ret = contin_array_create(tone_buf_continuous, BLK_MONO_SIZE_OCTETS, test_tone_buf, + test_tone_size, &finite_pos); + ERR_CHK(ret); + + ret = pcm_mix(tx_buf, BLK_STEREO_SIZE_OCTETS, tone_buf_continuous, BLK_MONO_SIZE_OCTETS, + B_MONO_INTO_A_STEREO_L); + ERR_CHK(ret); + } else if (buffer_play_data != NULL) { + int8_t buffer_play_buf[BLK_MONO_SIZE_OCTETS]; + int samples_per_block = BLK_MONO_SIZE_OCTETS / sizeof(int16_t); + + /* Copy buffer samples to playback buffer with amplitude scaling */ + for (int i = 0; i < samples_per_block; i++) { + float gain = buffer_play_amplitude; + + if (buffer_play_pos >= buffer_play_num_samples) { + if (buffer_play_loop) { + buffer_play_pos = 0; /* Loop the buffer */ + } else { + /* Stop after one complete playback */ + k_work_submit(&tone_stop_work); + memset(&buffer_play_buf[i * 2], 0, (samples_per_block - i) * 2); + break; + } + } - ret = pcm_mix(tx_buf, BLK_STEREO_SIZE_OCTETS, tone_buf_continuous, BLK_MONO_SIZE_OCTETS, - B_MONO_INTO_A_STEREO_L); - ERR_CHK(ret); + if (buffer_play_fade_pos < BUFFER_PLAY_FADE_IN_SAMPLES) { + gain *= (float)buffer_play_fade_pos / BUFFER_PLAY_FADE_IN_SAMPLES; + buffer_play_fade_pos++; + } + + int16_t sample = (int16_t)(buffer_play_data[buffer_play_pos] * gain); + buffer_play_buf[i * 2] = sample & 0xFF; + buffer_play_buf[i * 2 + 1] = (sample >> 8) & 0xFF; + buffer_play_pos++; + } + + ret = pcm_mix(tx_buf, BLK_STEREO_SIZE_OCTETS, buffer_play_buf, BLK_MONO_SIZE_OCTETS, + B_MONO_INTO_A_STEREO_L); + ERR_CHK(ret); + } } /* Alternate-buffers used when there is no active audio stream. @@ -774,6 +1072,8 @@ static void alt_buffer_free_both(void) } __attribute__((weak)) void bt_mgmt_report_audio_underrun(uint32_t count) { + ARG_UNUSED(count); + LOG_ERR("Audio underrun reported to bt_mgmt"); } @@ -793,6 +1093,7 @@ static void audio_datapath_i2s_blk_complete(uint32_t frame_start_ts_us, uint32_t { int ret; static bool underrun_condition; + static uint32_t released_tx_reuse_count; alt_buffer_free(tx_buf_released); @@ -838,12 +1139,24 @@ static void audio_datapath_i2s_blk_complete(uint32_t frame_start_ts_us, uint32_t * use alternative buffers */ ret = alt_buffer_get((void **)&tx_buf); - ERR_CHK(ret); + if (ret) { + released_tx_reuse_count++; + if (released_tx_reuse_count == 1U || + (released_tx_reuse_count % UNDERRUN_LOG_INTERVAL_BLKS) == 0U) { + LOG_WRN("No alternative I2S TX buffer available; " + "reusing released buffer as silence, total: %u", + (unsigned int)released_tx_reuse_count); + } + /* I2S no longer owns this buffer; recycle it as silence + * instead of leaving tx_buf NULL. + */ + tx_buf = (uint8_t *)tx_buf_released; + } memset(tx_buf, 0, BLK_STEREO_SIZE_OCTETS); } - if (tone_active) { + if (tone_active || buffer_play_data != NULL) { tone_mix(tx_buf); } } @@ -1252,7 +1565,7 @@ int audio_datapath_aquire(struct data_fifo *fifo_rx) { return ret; } -int audio_datapath_release() { +int audio_datapath_release(void) { int ret = 0; _count --; diff --git a/src/audio/audio_datapath.h b/src/audio/audio_datapath.h index 6f758fad..211f7be1 100644 --- a/src/audio/audio_datapath.h +++ b/src/audio/audio_datapath.h @@ -35,6 +35,40 @@ int audio_datapath_tone_play(uint16_t freq, uint16_t dur_ms, float amplitude); */ void audio_datapath_tone_stop(void); + + + + +/** + * @brief Plays a buffer over I2S TX stream + * + * @param buffer Pointer to audio buffer to play + * @param num_samples Number of samples in buffer + * @param loop Whether to loop the buffer continuously + * @param amplitude Playback amplitude [0, 1] + * @param callback Callback function called when playback completes (optional) + * + * @return 0 if successful, error otherwise + */ +int audio_datapath_buffer_play(int16_t *buffer, int num_samples, bool loop, float amplitude, void (*callback)(void)); + +/** + * @brief Stops buffer playback + */ +void audio_datapath_buffer_stop(void); + +/** + * @brief Records audio data to a buffer + * + * @param buffer Pointer to buffer where audio will be stored + * @param num_samples Number of samples to record + * @param initial_drop Number of initial samples to drop before recording + * @param left Whether to record left channel + * @param right Whether to record right channel + * @param callback Callback function called when recording completes (optional) + */ +void record_to_buffer(int16_t *buffer, int num_samples, int initial_drop, bool left, bool right, void (*callback)(void)); + /** * @brief Stops buffer recording safely */ @@ -45,6 +79,24 @@ void record_to_buffer_stop(void); */ void audio_datapath_stop_recording(void); +/** + * @brief Suspend local playback and recording without losing their state. + * + * The datapath must be stopped before this function is called. A matching call + * to audio_datapath_auxiliary_resume() restores playback positions, callbacks, + * and recording configuration. + * + * @return 0 on success, or -EBUSY if an auxiliary suspension is already active. + */ +int audio_datapath_auxiliary_suspend(void); + +/** + * @brief Restore local playback and recording after an auxiliary suspension. + * + * @return 0 on success, or -EALREADY if no suspension is active. + */ +int audio_datapath_auxiliary_resume(void); + /** * @brief Set the presentation delay * @@ -109,7 +161,7 @@ void record_to_sd(bool active); void set_sensor_queue(struct k_msgq *queue); int audio_datapath_aquire(struct data_fifo *fifo_rx); -int audio_datapath_release(); +int audio_datapath_release(void); //void set_ring_buffer(struct ring_buf *ring_buf); diff --git a/src/audio/audio_datapath_decimator.cpp b/src/audio/audio_datapath_decimator.cpp index 73c9e43d..5650f406 100644 --- a/src/audio/audio_datapath_decimator.cpp +++ b/src/audio/audio_datapath_decimator.cpp @@ -10,24 +10,24 @@ LOG_MODULE_DECLARE(audio_datapath); #ifdef __cplusplus -/* CascadedDecimator instance for direct C++ usage */ -static CascadedDecimator* g_audio_decimator = nullptr; +/* Fixed-capacity storage avoids C++ allocation failure in the audio work queue. */ +static CascadedDecimator g_audio_decimator; +static bool g_audio_decimator_initialized; /* Mutex to protect access to the decimator during cleanup */ K_MUTEX_DEFINE(decimator_mutex); -/* Flag to indicate decimator is being used - provides fast-path check */ -static volatile bool g_decimator_in_use = false; - extern "C" { /** * @brief Reset the audio decimator filter state */ void audio_datapath_decimator_reset(void) { - if (g_audio_decimator) { - g_audio_decimator->reset(); + k_mutex_lock(&decimator_mutex, K_FOREVER); + if (g_audio_decimator_initialized) { + g_audio_decimator.reset(); LOG_DBG("CascadedDecimator state reset"); } + k_mutex_unlock(&decimator_mutex); } /** @@ -35,7 +35,10 @@ void audio_datapath_decimator_reset(void) { * @return Current total decimation factor, or 0 if not initialized */ uint8_t audio_datapath_decimator_get_factor(void) { - return g_audio_decimator ? g_audio_decimator->getTotalFactor() : 0; + k_mutex_lock(&decimator_mutex, K_FOREVER); + uint8_t factor = g_audio_decimator_initialized ? g_audio_decimator.getTotalFactor() : 0; + k_mutex_unlock(&decimator_mutex); + return factor; } /** @@ -44,30 +47,27 @@ uint8_t audio_datapath_decimator_get_factor(void) { * @return 0 on success, negative on error */ int audio_datapath_decimator_init(uint8_t factor) { - /* Lock mutex to ensure no concurrent access during init */ k_mutex_lock(&decimator_mutex, K_FOREVER); - - if (g_audio_decimator) { - delete g_audio_decimator; - g_audio_decimator = nullptr; - } - - g_audio_decimator = new CascadedDecimator(factor); - if (!g_audio_decimator) { - LOG_ERR("Failed to create CascadedDecimator with factor %d", factor); + + g_audio_decimator_initialized = false; + g_audio_decimator.cleanup(); + + int ret = g_audio_decimator.configure(factor); + if (ret != 0) { + LOG_ERR("Failed to configure CascadedDecimator: %d", ret); k_mutex_unlock(&decimator_mutex); - return -ENOMEM; + return ret; } - - int ret = g_audio_decimator->init(); + + ret = g_audio_decimator.init(); if (ret != 0) { LOG_ERR("Failed to initialize CascadedDecimator: %d", ret); - delete g_audio_decimator; - g_audio_decimator = nullptr; + g_audio_decimator.cleanup(); k_mutex_unlock(&decimator_mutex); return ret; } - + + g_audio_decimator_initialized = true; LOG_DBG("CascadedDecimator (%dx) initialized successfully", factor); k_mutex_unlock(&decimator_mutex); return 0; @@ -88,16 +88,14 @@ int audio_datapath_decimator_process(const int16_t* input, int16_t* output, uint return 0; } - if (!g_audio_decimator) { + if (!g_audio_decimator_initialized) { k_mutex_unlock(&decimator_mutex); LOG_WRN("CascadedDecimator not available, returning 0 frames"); return 0; } - - g_decimator_in_use = true; - int result = g_audio_decimator->process(input, output, num_frames); - g_decimator_in_use = false; - + + int result = g_audio_decimator.process(input, output, num_frames); + k_mutex_unlock(&decimator_mutex); return result; } @@ -106,23 +104,16 @@ int audio_datapath_decimator_process(const int16_t* input, int16_t* output, uint * @brief Cleanup the audio decimator */ void audio_datapath_decimator_cleanup(void) { - /* Acquire mutex to ensure no processing is happening */ k_mutex_lock(&decimator_mutex, K_FOREVER); - - if (g_audio_decimator) { - /* Wait briefly if decimator was recently in use (safety margin) */ - if (g_decimator_in_use) { - LOG_WRN("Decimator still in use, waiting..."); - k_sleep(K_MSEC(5)); - } - + + if (g_audio_decimator_initialized) { LOG_DBG("Cleaning up CascadedDecimator"); - delete g_audio_decimator; - g_audio_decimator = nullptr; + g_audio_decimator.cleanup(); + g_audio_decimator_initialized = false; } - + k_mutex_unlock(&decimator_mutex); } }; -#endif \ No newline at end of file +#endif diff --git a/src/audio/audio_system.c b/src/audio/audio_system.c index 493ee96f..6ae33b1f 100644 --- a/src/audio/audio_system.c +++ b/src/audio/audio_system.c @@ -47,6 +47,10 @@ static K_SEM_DEFINE(sem_encoder_start, 0, 1); static struct k_thread encoder_thread_data; static k_tid_t encoder_thread_id; static atomic_t encoder_started; +static bool audio_system_suspended; +static bool audio_system_resume_requested; +static bool encoder_resume_requested; +K_MUTEX_DEFINE(audio_system_state_mutex); struct k_poll_signal encoder_sig; @@ -118,6 +122,10 @@ static void audio_headset_configure(void) static void encoder_thread(void *arg1, void *arg2, void *arg3) { + ARG_UNUSED(arg1); + ARG_UNUSED(arg2); + ARG_UNUSED(arg3); + int ret; uint32_t blocks_alloced_num; uint32_t blocks_locked_num; @@ -131,10 +139,11 @@ static void encoder_thread(void *arg1, void *arg2, void *arg3) static uint8_t *encoded_data; //static size_t pcm_block_size; static uint32_t test_tone_finite_pos; + static bool encode_failed; while (1) { /* Don't start encoding until the stream needing it has started */ - ret = k_poll(&encoder_evt, 1, K_FOREVER); + (void)k_poll(&encoder_evt, 1, K_FOREVER); /* Get PCM data from I2S */ /* Since one audio frame is divided into a number of @@ -182,8 +191,18 @@ static void encoder_thread(void *arg1, void *arg2, void *arg3) ret = sw_codec_encode(pcm_raw_data, FRAME_SIZE_BYTES, &encoded_data, &encoded_data_size); + if (ret) { + if (!encode_failed) { + LOG_WRN("Audio encode failed; dropping frames until recovery: %d", ret); + } + encode_failed = true; + continue; + } - ERR_CHK_MSG(ret, "Encode failed"); + if (encode_failed) { + LOG_INF("Audio encoder recovered"); + encode_failed = false; + } } /* Print block usage */ @@ -206,7 +225,7 @@ static void encoder_thread(void *arg1, void *arg2, void *arg3) } } -void audio_system_encoder_start(void) +static void audio_system_encoder_start_internal(void) { if (!sw_codec_cfg.initialized || !sw_codec_cfg.encoder.enabled || !sw_codec_is_initialized()) { LOG_WRN("Encoder start ignored because codec is not initialized"); @@ -225,7 +244,7 @@ void audio_system_encoder_start(void) }*/ } -void audio_system_encoder_stop(void) +static void audio_system_encoder_stop_internal(void) { atomic_clear(&encoder_started); k_poll_signal_reset(&encoder_sig); @@ -238,6 +257,28 @@ bool audio_system_encoder_is_started(void) sw_codec_cfg.encoder.enabled && sw_codec_is_initialized(); } +void audio_system_encoder_start(void) +{ + k_mutex_lock(&audio_system_state_mutex, K_FOREVER); + if (audio_system_suspended) { + encoder_resume_requested = true; + } else { + audio_system_encoder_start_internal(); + } + k_mutex_unlock(&audio_system_state_mutex); +} + +void audio_system_encoder_stop(void) +{ + k_mutex_lock(&audio_system_state_mutex, K_FOREVER); + if (audio_system_suspended) { + encoder_resume_requested = false; + } else { + audio_system_encoder_stop_internal(); + } + k_mutex_unlock(&audio_system_state_mutex); +} + int audio_system_encode_test_tone_set(uint32_t freq) { int ret; @@ -406,9 +447,12 @@ int audio_system_decode(void const *const encoded_data, size_t encoded_data_size return 0; } -/**@brief Initializes the FIFOs, the codec, and starts the I2S +/** + * @brief Initialize the FIFOs and codecs, then start the audio datapath. + * + * @return 0 on success, or a negative error from the failed subsystem. */ -void audio_system_start(void) +static int audio_system_start_internal(void) { int ret; @@ -418,23 +462,32 @@ void audio_system_start(void) audio_gateway_configure(); } else { LOG_ERR("Invalid CONFIG_AUDIO_DEV: %d", CONFIG_AUDIO_DEV); - ERR_CHK(-EINVAL); + return -EINVAL; } if (!fifo_tx.initialized) { ret = data_fifo_init(&fifo_tx); - ERR_CHK_MSG(ret, "Failed to set up tx FIFO"); + if (ret) { + LOG_ERR("Failed to set up tx FIFO: %d", ret); + return ret; + } } if (!fifo_rx.initialized) { ret = data_fifo_init(&fifo_rx); - ERR_CHK_MSG(ret, "Failed to set up rx FIFO"); + if (ret) { + LOG_ERR("Failed to set up rx FIFO: %d", ret); + return ret; + } } LOG_INF("Microphone channel set to %d", sw_codec_cfg.encoder.audio_ch); ret = sw_codec_init(sw_codec_cfg); - ERR_CHK_MSG(ret, "Failed to set up codec"); + if (ret) { + LOG_ERR("Failed to set up codec: %d", ret); + return ret; + } sw_codec_cfg.initialized = true; @@ -444,19 +497,37 @@ void audio_system_start(void) (k_thread_entry_t)encoder_thread, NULL, NULL, NULL, K_PRIO_PREEMPT(CONFIG_ENCODER_THREAD_PRIO), 0, K_NO_WAIT); ret = k_thread_name_set(encoder_thread_id, "ENCODER"); - ERR_CHK(ret); + if (ret) { + LOG_WRN("Failed to name encoder thread: %d", ret); + } } #if ((CONFIG_AUDIO_SOURCE_USB) && (CONFIG_AUDIO_DEV == GATEWAY)) ret = audio_usb_start(&fifo_tx, &fifo_rx); - ERR_CHK(ret); + if (ret) { + LOG_ERR("Failed to start USB audio: %d", ret); + goto cleanup_sw_codec; + } #else ret = audio_datapath_aquire(&fifo_rx); - ERR_CHK(ret); + if (ret) { + LOG_ERR("Failed to acquire audio datapath: %d", ret); + goto cleanup_sw_codec; + } ret = hw_codec_default_conf_enable(); - ERR_CHK(ret); + if (ret) { + int release_ret; + + LOG_ERR("Failed to configure hardware codec: %d", ret); + release_ret = audio_datapath_release(); + if (release_ret) { + LOG_ERR("Failed to release audio datapath after startup error: %d", + release_ret); + } + goto cleanup_sw_codec; + } /*if (IS_ENABLED(CONFIG_AUDIO_MIC_PDM)) { ret = pdm_datapath_start(&fifo_rx); @@ -464,9 +535,22 @@ void audio_system_start(void) }*/ #endif /* ((CONFIG_AUDIO_SOURCE_USB) && (CONFIG_AUDIO_DEV == GATEWAY))) */ + + return 0; + +cleanup_sw_codec: + { + int uninit_ret = sw_codec_uninit(sw_codec_cfg); + + sw_codec_cfg.initialized = false; + if (uninit_ret) { + LOG_ERR("Failed to uninitialize codec after startup error: %d", uninit_ret); + } + } + return ret; } -void audio_system_stop(void) +static void audio_system_stop_internal(void) { int ret; @@ -476,7 +560,7 @@ void audio_system_stop(void) } LOG_DBG("Stopping codec"); - audio_system_encoder_stop(); + audio_system_encoder_stop_internal(); #if ((CONFIG_AUDIO_DEV == GATEWAY) && CONFIG_AUDIO_SOURCE_USB) audio_usb_stop(); @@ -501,6 +585,81 @@ void audio_system_stop(void) data_fifo_empty(&fifo_tx); } +int audio_system_start(void) +{ + int ret = 0; + + k_mutex_lock(&audio_system_state_mutex, K_FOREVER); + if (audio_system_suspended) { + audio_system_resume_requested = true; + } else { + ret = audio_system_start_internal(); + } + k_mutex_unlock(&audio_system_state_mutex); + return ret; +} + +void audio_system_stop(void) +{ + k_mutex_lock(&audio_system_state_mutex, K_FOREVER); + if (audio_system_suspended) { + audio_system_resume_requested = false; + encoder_resume_requested = false; + } else { + audio_system_stop_internal(); + } + k_mutex_unlock(&audio_system_state_mutex); +} + +int audio_system_suspend(void) +{ + k_mutex_lock(&audio_system_state_mutex, K_FOREVER); + if (audio_system_suspended) { + k_mutex_unlock(&audio_system_state_mutex); + return -EBUSY; + } + + audio_system_resume_requested = sw_codec_cfg.initialized; + encoder_resume_requested = audio_system_encoder_is_started(); + audio_system_suspended = true; + if (audio_system_resume_requested) { + audio_system_stop_internal(); + } + LOG_INF("Audio system suspended: resume=%d encoder=%d", audio_system_resume_requested, + encoder_resume_requested); + k_mutex_unlock(&audio_system_state_mutex); + return 0; +} + +int audio_system_resume(void) +{ + int ret = 0; + bool resume_requested; + bool encoder_requested; + + k_mutex_lock(&audio_system_state_mutex, K_FOREVER); + if (!audio_system_suspended) { + k_mutex_unlock(&audio_system_state_mutex); + return -EALREADY; + } + + audio_system_suspended = false; + resume_requested = audio_system_resume_requested; + encoder_requested = encoder_resume_requested; + if (resume_requested) { + ret = audio_system_start_internal(); + if (!ret && encoder_requested) { + audio_system_encoder_start_internal(); + } + } + LOG_INF("Audio system suspension released: running=%d encoder=%d result=%d", + resume_requested && !ret, encoder_requested && !ret, ret); + audio_system_resume_requested = false; + encoder_resume_requested = false; + k_mutex_unlock(&audio_system_state_mutex); + return ret; +} + int audio_system_fifo_rx_block_drop(void) { int ret; @@ -554,10 +713,16 @@ int audio_system_init(void) static int cmd_audio_system_start(const struct shell *shell, size_t argc, const char **argv) { + int ret; + ARG_UNUSED(argc); ARG_UNUSED(argv); - audio_system_start(); + ret = audio_system_start(); + if (ret) { + shell_error(shell, "Audio system failed to start: %d", ret); + return ret; + } shell_print(shell, "Audio system started"); diff --git a/src/audio/audio_system.h b/src/audio/audio_system.h index 0c5829e9..c8dc6849 100644 --- a/src/audio/audio_system.h +++ b/src/audio/audio_system.h @@ -38,6 +38,24 @@ void audio_system_encoder_stop(void); */ bool audio_system_encoder_is_started(void); +/** + * @brief Exclusively suspend the audio system while preserving requested state. + * + * Start, stop, and encoder requests received during the suspension are deferred + * and applied by audio_system_resume(). + * + * @return 0 on success, or -EBUSY if the system is already suspended. + */ +int audio_system_suspend(void); + +/** + * @brief Release an exclusive suspension and apply the latest audio state. + * + * @return 0 on success, -EALREADY if the system is not suspended, or a + * negative startup error when the preserved audio state cannot resume. + */ +int audio_system_resume(void); + /** * @brief Toggle a test tone on and off. * @@ -87,9 +105,11 @@ int audio_system_config_set(uint32_t encoder_sample_rate_hz, uint32_t encoder_bi int audio_system_decode(void const *const encoded_data, size_t encoded_data_size, bool bad_frame); /** - * @brief Initialize and start both HW and SW audio codec. + * @brief Initialize and start both HW and SW audio codecs. + * + * @return 0 on success, or a negative error returned by the codec or datapath. */ -void audio_system_start(void); +int audio_system_start(void); /** * @brief Stop all activities related to audio. diff --git a/src/audio/decimation_filter.cpp b/src/audio/decimation_filter.cpp index 7d49f1a0..36af29fa 100644 --- a/src/audio/decimation_filter.cpp +++ b/src/audio/decimation_filter.cpp @@ -6,7 +6,6 @@ #include "decimation_filter.h" #include -#include #include LOG_MODULE_REGISTER(decimator_cpp, CONFIG_AUDIO_DATAPATH_LOG_LEVEL); @@ -27,10 +26,16 @@ static const float32_t coeff_dec3[2 * 5] = { }; // Decimator class implementation -Decimator::Decimator(uint8_t factor) - : factor_(factor), initialized_(false) { +Decimator::Decimator() + : factor_(0), initialized_(false) { + memset(state_, 0, sizeof(state_)); +} + +void Decimator::configure(uint8_t factor) { + factor_ = factor; + initialized_ = false; + memset(&biquad_, 0, sizeof(biquad_)); memset(state_, 0, sizeof(state_)); - memset(temp_f32_, 0, sizeof(temp_f32_)); } int Decimator::init() { @@ -54,8 +59,10 @@ int Decimator::init() { return 0; } -int Decimator::process(const int16_t* input, int16_t* output, uint32_t num_frames) { - if (!initialized_ || !input || !output || num_frames == 0 || num_frames > MAX_FRAMES) { +int Decimator::process(const int16_t* input, int16_t* output, uint32_t num_frames, + float32_t* processing_buffer) { + if (!initialized_ || !input || !output || !processing_buffer || num_frames == 0 || + num_frames > MAX_FRAMES) { return -EINVAL; } @@ -63,23 +70,23 @@ int Decimator::process(const int16_t* input, int16_t* output, uint32_t num_frame // Convert int16 to float32 for (uint32_t i = 0; i < num_samples; i++) { - temp_f32_[i] = static_cast(input[i]); + processing_buffer[i] = static_cast(input[i]); } - - // Apply anti-aliasing filter using stereo DF2T - static float32_t filtered[MAX_FRAMES * 2]; - arm_biquad_cascade_stereo_df2T_f32(&biquad_, temp_f32_, filtered, num_frames); - + + // CMSIS-DSP stereo DF2T supports identical source and destination buffers. + arm_biquad_cascade_stereo_df2T_f32(&biquad_, processing_buffer, processing_buffer, + num_frames); + // Clip to prevent overflow - arm_clip_f32(filtered, filtered, -32768.0f, 32767.0f, num_samples); + arm_clip_f32(processing_buffer, processing_buffer, -32768.0f, 32767.0f, num_samples); // Decimate and convert back to int16 uint32_t out_frames = num_frames / factor_; uint32_t step = factor_ * 2; for (uint32_t i = 0; i < out_frames; i++) { - output[i * 2] = static_cast(filtered[i * step]); - output[i * 2 + 1] = static_cast(filtered[i * step + 1]); + output[i * 2] = static_cast(processing_buffer[i * step]); + output[i * 2 + 1] = static_cast(processing_buffer[i * step + 1]); } return out_frames; @@ -100,106 +107,94 @@ const float32_t* Decimator::getCoefficients() const { } // CascadedDecimator class implementation -CascadedDecimator::CascadedDecimator(uint8_t total_factor) - : total_factor_(total_factor), num_stages_(0) { - memset(stages_, 0, sizeof(stages_)); - memset(temp_buffers_, 0, sizeof(temp_buffers_)); - setupStages(); +CascadedDecimator::CascadedDecimator() + : total_factor_(0), num_stages_(0), configured_(false) { + memset(intermediate_buffer_, 0, sizeof(intermediate_buffer_)); } -CascadedDecimator::~CascadedDecimator() { - cleanupStages(); -} +int CascadedDecimator::configure(uint8_t total_factor) { + uint8_t stage_factors[MAX_STAGES] = {}; -void CascadedDecimator::setupStages() { - switch (total_factor_) { + cleanup(); + switch (total_factor) { case 1: // No decimation num_stages_ = 0; break; case 2: // 2x num_stages_ = 1; - stages_[0] = new Decimator(2); + stage_factors[0] = 2; break; case 3: // 3x num_stages_ = 1; - stages_[0] = new Decimator(3); + stage_factors[0] = 3; break; case 4: // 2x -> 2x num_stages_ = 2; - stages_[0] = new Decimator(2); - stages_[1] = new Decimator(2); + stage_factors[0] = 2; + stage_factors[1] = 2; break; - case 6: // 3x -> 2x num_stages_ = 2; - stages_[0] = new Decimator(3); - stages_[1] = new Decimator(2); + stage_factors[0] = 3; + stage_factors[1] = 2; break; - case 8: // 2x -> 2x -> 2x num_stages_ = 3; - stages_[0] = new Decimator(2); - stages_[1] = new Decimator(2); - stages_[2] = new Decimator(2); + stage_factors[0] = 2; + stage_factors[1] = 2; + stage_factors[2] = 2; break; - case 12: // 3x -> 2x -> 2x num_stages_ = 3; - stages_[0] = new Decimator(3); - stages_[1] = new Decimator(2); - stages_[2] = new Decimator(2); + stage_factors[0] = 3; + stage_factors[1] = 2; + stage_factors[2] = 2; break; - case 16: // 2x -> 2x -> 2x -> 2x num_stages_ = 4; - stages_[0] = new Decimator(2); - stages_[1] = new Decimator(2); - stages_[2] = new Decimator(2); - stages_[3] = new Decimator(2); + stage_factors[0] = 2; + stage_factors[1] = 2; + stage_factors[2] = 2; + stage_factors[3] = 2; break; - case 24: // 3x -> 2x -> 2x -> 2x num_stages_ = 4; - stages_[0] = new Decimator(3); - stages_[1] = new Decimator(2); - stages_[2] = new Decimator(2); - stages_[3] = new Decimator(2); + stage_factors[0] = 3; + stage_factors[1] = 2; + stage_factors[2] = 2; + stage_factors[3] = 2; break; - default: - LOG_ERR("Unsupported total decimation factor: %d", total_factor_); - num_stages_ = 0; - return; + LOG_ERR("Unsupported total decimation factor: %d", total_factor); + return -EINVAL; } - - // Allocate temporary buffers between stages - for (uint8_t i = 0; i < num_stages_ - 1; i++) { - temp_buffers_[i] = new int16_t[MAX_FRAMES * 2]; + + for (uint8_t i = 0; i < num_stages_; i++) { + stages_[i].configure(stage_factors[i]); } - + + total_factor_ = total_factor; + configured_ = true; LOG_DBG("CascadedDecimator setup for factor %d with %d stages", total_factor_, num_stages_); + return 0; } -void CascadedDecimator::cleanupStages() { - for (uint8_t i = 0; i < num_stages_; i++) { - delete stages_[i]; - stages_[i] = nullptr; - } - - for (uint8_t i = 0; i < MAX_STAGES - 1; i++) { - delete[] temp_buffers_[i]; - temp_buffers_[i] = nullptr; +void CascadedDecimator::cleanup() { + for (uint8_t i = 0; i < MAX_STAGES; i++) { + stages_[i].configure(0); } + total_factor_ = 0; + num_stages_ = 0; + configured_ = false; } int CascadedDecimator::init() { + if (!configured_) { + return -EINVAL; + } + for (uint8_t i = 0; i < num_stages_; i++) { - if (!stages_[i]) { - LOG_ERR("Stage %d is null", i); - return -EINVAL; - } - - int ret = stages_[i]->init(); + int ret = stages_[i].init(); if (ret != 0) { LOG_ERR("Failed to initialize stage %d: %d", i, ret); return ret; @@ -211,14 +206,17 @@ int CascadedDecimator::init() { } int CascadedDecimator::process(const int16_t* input, int16_t* output, uint32_t num_frames) { + if (!configured_ || !input || !output || num_frames == 0 || num_frames > MAX_FRAMES) { + return -EINVAL; + } + if (num_stages_ == 0) { + if (output != input) { + memcpy(output, input, num_frames * 2U * sizeof(int16_t)); + } return num_frames; } - if (!input || !output || num_frames == 0) { - return -EINVAL; - } - const int16_t* stage_input = input; int16_t* stage_output = nullptr; int frames = num_frames; @@ -229,12 +227,12 @@ int CascadedDecimator::process(const int16_t* input, int16_t* output, uint32_t n // Last stage outputs to final output buffer stage_output = output; } else { - // Intermediate stage outputs to temp buffer - stage_output = temp_buffers_[i]; + // Alternate storage so adjacent stages never share input and output. + stage_output = (i % 2 == 0) ? intermediate_buffer_ : output; } // Process this stage - frames = stages_[i]->process(stage_input, stage_output, frames); + frames = stages_[i].process(stage_input, stage_output, frames, processing_buffer_); if (frames < 0) { LOG_ERR("Stage %d processing failed: %d", i, frames); return frames; @@ -249,8 +247,6 @@ int CascadedDecimator::process(const int16_t* input, int16_t* output, uint32_t n void CascadedDecimator::reset() { for (uint8_t i = 0; i < num_stages_; i++) { - if (stages_[i]) { - stages_[i]->reset(); - } + stages_[i].reset(); } -} \ No newline at end of file +} diff --git a/src/audio/decimation_filter.h b/src/audio/decimation_filter.h index 2afd709f..a3f0d5d9 100644 --- a/src/audio/decimation_filter.h +++ b/src/audio/decimation_filter.h @@ -10,24 +10,37 @@ #include #include #include "arm_math.h" +#include "audio_i2s.h" #ifdef __cplusplus +static_assert(BLOCK_SIZE_BYTES % (sizeof(int16_t) * 2U) == 0, + "The audio block must contain complete stereo int16 frames"); + /** * @brief Single stage decimation filter class */ class Decimator { public: + /** Maximum number of stereo frames supplied by one audio datapath block. */ + static constexpr uint32_t MAX_FRAMES = + BLOCK_SIZE_BYTES / (sizeof(int16_t) * 2U); + /** - * @brief Constructor - * @param factor Decimation factor (2 or 3) + * @brief Construct an unconfigured decimation stage. */ - Decimator(uint8_t factor); + Decimator(); /** * @brief Destructor */ ~Decimator() = default; + + /** + * @brief Configure this stage for a decimation factor. + * @param factor Decimation factor (2 or 3) + */ + void configure(uint8_t factor); /** * @brief Initialize the decimator @@ -40,9 +53,11 @@ class Decimator { * @param input Input buffer (interleaved stereo int16) * @param output Output buffer (interleaved stereo int16) * @param num_frames Number of input stereo frames + * @param processing_buffer In-place float processing buffer for stereo frames * @return Number of output frames, or negative on error */ - int process(const int16_t* input, int16_t* output, uint32_t num_frames); + int process(const int16_t* input, int16_t* output, uint32_t num_frames, + float32_t* processing_buffer); /** * @brief Reset filter state @@ -56,14 +71,12 @@ class Decimator { uint8_t getFactor() const { return factor_; } private: - static constexpr uint32_t MAX_FRAMES = 512; static constexpr uint32_t NUM_STAGES = 2; uint8_t factor_; bool initialized_; arm_biquad_cascade_stereo_df2T_instance_f32 biquad_; float32_t state_[4 * NUM_STAGES]; - float32_t temp_f32_[MAX_FRAMES * 2]; const float32_t* getCoefficients() const; }; @@ -74,15 +87,26 @@ class Decimator { class CascadedDecimator { public: /** - * @brief Constructor for predefined cascaded decimation factors - * @param total_factor Total decimation factor (4, 6, 8, or 12) + * @brief Construct an unconfigured cascaded decimator. */ - CascadedDecimator(uint8_t total_factor); + CascadedDecimator(); /** * @brief Destructor */ - ~CascadedDecimator(); + ~CascadedDecimator() = default; + + /** + * @brief Configure the statically allocated filter stages. + * @param total_factor Total decimation factor (1, 2, 3, 4, 6, 8, 12, 16, or 24) + * @return 0 on success, or -EINVAL for an unsupported factor. + */ + int configure(uint8_t total_factor); + + /** + * @brief Clear configuration and filter state without releasing memory. + */ + void cleanup(); /** * @brief Initialize all cascaded decimators @@ -112,15 +136,15 @@ class CascadedDecimator { private: static constexpr uint32_t MAX_STAGES = 4; - static constexpr uint32_t MAX_FRAMES = 512; + static constexpr uint32_t MAX_FRAMES = Decimator::MAX_FRAMES; uint8_t total_factor_; uint8_t num_stages_; - Decimator* stages_[MAX_STAGES]; - int16_t* temp_buffers_[MAX_STAGES - 1]; - - void setupStages(); - void cleanupStages(); + bool configured_; + Decimator stages_[MAX_STAGES]; + /* Stage output is interleaved stereo, so each frame needs two samples. */ + int16_t intermediate_buffer_[MAX_FRAMES * 2U]; + float32_t processing_buffer_[MAX_FRAMES * 2]; }; #endif #endif /* _DECIMATION_FILTER_H_ */ diff --git a/src/audio/le_audio_rx.c b/src/audio/le_audio_rx.c index 8c7c9370..9411f230 100644 --- a/src/audio/le_audio_rx.c +++ b/src/audio/le_audio_rx.c @@ -138,6 +138,10 @@ void le_audio_rx_data_handler(uint8_t const *const p_data, size_t data_size, boo */ static void audio_datapath_thread(void *dummy1, void *dummy2, void *dummy3) { + ARG_UNUSED(dummy1); + ARG_UNUSED(dummy2); + ARG_UNUSED(dummy3); + int ret; struct ble_iso_data *iso_received = NULL; size_t iso_received_size; diff --git a/src/audio/streamctrl.c b/src/audio/streamctrl.c index 8cd15d97..e3dc6df2 100644 --- a/src/audio/streamctrl.c +++ b/src/audio/streamctrl.c @@ -6,6 +6,10 @@ #include "streamctrl.h" +#include +#include +#include +#include "common/bt_str.h" #include #include @@ -28,6 +32,8 @@ #include "AutoOffManager.h" #include "BootState.h" +#include "channel_assignment.h" +#include "uicr.h" #include LOG_MODULE_REGISTER(streamctrl, CONFIG_MAIN_LOG_LEVEL); @@ -199,7 +205,11 @@ static void le_audio_msg_sub_thread(void) break; } - audio_system_start(); + ret = audio_system_start(); + if (ret) { + LOG_ERR("Failed to start audio system: %d", ret); + break; + } stream_state_set(STATE_STREAMING); if (msg.dir == BT_AUDIO_DIR_SOURCE) { audio_system_encoder_start(); @@ -545,11 +555,15 @@ static void write_sirk(uint32_t sirk) { // Callback-Funktion für gefundene Geräte static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, struct net_buf_simple *ad) { + ARG_UNUSED(rssi); + ARG_UNUSED(type); + char addr_str[BT_ADDR_LE_STR_LEN]; bt_addr_le_to_str(addr, addr_str, sizeof(addr_str)); - int ret; bool is_le_audio_device = false; + bool csis_rsi_found = false; + bool chip_id_found = false; uint8_t csis_rsi[6]; uint8_t chip_id[8]; @@ -559,14 +573,14 @@ static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, st break; // Ungültige Länge } - uint8_t type = net_buf_simple_pull_u8(ad); + uint8_t data_type = net_buf_simple_pull_u8(ad); const uint8_t *data = ad->data; ad->data += len - 1; ad->len -= len - 1; // Suchen nach 16-bit Service UUIDs (LE Audio Services) - if (type == BT_DATA_SVC_DATA16) { - for (size_t i = 0; i < len - 1; i += 2) { + if (data_type == BT_DATA_SVC_DATA16) { + for (size_t i = 0; i + 1U < (size_t)(len - 1); i += 2) { uint16_t uuid = (data[i + 1] << 8) | data[i]; if (uuid == BT_UUID_CAS_VAL) { is_le_audio_device = true; @@ -574,13 +588,24 @@ static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, st } } - if (is_le_audio_device && type == BT_DATA_MANUFACTURER_DATA) { - memset(chip_id, 0, sizeof(chip_id)); - memcpy(chip_id, data, sizeof(chip_id)); + if (is_le_audio_device && data_type == BT_DATA_MANUFACTURER_DATA) { + if (len > 1U && (size_t)(len - 1U) >= sizeof(chip_id)) { + memcpy(chip_id, data, sizeof(chip_id)); + chip_id_found = true; + } else { + LOG_DBG("Ignoring short manufacturer chip ID (%u bytes)", + (unsigned int)(len - 1)); + } } - if (type == BT_DATA_CSIS_RSI) { - memcpy(csis_rsi, data, sizeof(csis_rsi)); + /* A valid RSI contains a 3-byte hash followed by a 3-byte random value. */ + if (data_type == BT_DATA_CSIS_RSI) { + if (len > 1U && (size_t)(len - 1U) >= sizeof(csis_rsi)) { + memcpy(csis_rsi, data, sizeof(csis_rsi)); + csis_rsi_found = true; + } else { + LOG_DBG("Ignoring short CSIS RSI (%u bytes)", (unsigned int)(len - 1)); + } } // channel @@ -592,10 +617,18 @@ static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, st if (is_le_audio_device) { LOG_INF("Found LE-Audio device!"); - uint32_t hash_ref = (csis_rsi[2] << 16) | (csis_rsi[1] << 8) | csis_rsi[0]; + /* Do not derive or persist a SIRK without a complete peer identity. */ + if (!chip_id_found) { + LOG_DBG("Ignoring LE Audio device without manufacturer chip ID"); + return; + } + uint32_t hash; + uint32_t peer_device_id; - uint32_t new_sirk = *((uint32_t *) chip_id) ^ oe_boot_state.device_id; + /* Copy from the byte array without alignment or aliasing assumptions. */ + memcpy(&peer_device_id, chip_id, sizeof(peer_device_id)); + uint32_t new_sirk = peer_device_id ^ oe_boot_state.device_id; enum audio_channel channel; @@ -603,15 +636,23 @@ static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, st channel_assignment_get(&channel); if (channel == AUDIO_CH_L) { - LOG_INF("Device ID 1: %016X", oe_boot_state.device_id); - LOG_INF("Device ID 2: %016X", *((uint32_t *) chip_id)); - LOG_INF("New Sirk: %016X", new_sirk); + LOG_INF("Device ID 1: %016" PRIX64, oe_boot_state.device_id); + LOG_INF("Device ID 2: %08" PRIX32, peer_device_id); + LOG_INF("New Sirk: %08" PRIX32, new_sirk); //TODO: check if the device wants to pair (sirk == device_id) //TODO: check channel write_sirk(new_sirk); } else if (channel == AUDIO_CH_R) { + /* Only right-channel matching validates the advertised RSI. */ + if (!csis_rsi_found) { + LOG_DBG("Ignoring right-channel LE Audio device without CSIS RSI"); + return; + } + + uint32_t hash_ref = + (csis_rsi[2] << 16) | (csis_rsi[1] << 8) | csis_rsi[0]; uint8_t res[BT_CSIP_PADDED_RAND_SIZE]; uint8_t sirk[BT_CSIP_SIRK_SIZE + 1]; @@ -631,6 +672,10 @@ static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, st snprintf(sirk, BT_CSIP_SIRK_SIZE, "%08X", new_sirk); int err = bt_encrypt_le(sirk, res, res); + if (err) { + LOG_ERR("Failed to calculate CSIS hash: %d", err); + return; + } memcpy(out, res, BT_CSIP_CRYPTO_HASH_SIZE); diff --git a/src/audio/sw_codec_select.c b/src/audio/sw_codec_select.c index f1564206..8f5de1af 100644 --- a/src/audio/sw_codec_select.c +++ b/src/audio/sw_codec_select.c @@ -163,6 +163,15 @@ int sw_codec_encode(void *pcm_data, size_t pcm_size, uint8_t **encoded_data, siz break; } case SW_CODEC_STEREO: { + /* Stereo encoding consumes both channel pointers below. Reject mismatched + * configurations so the caller can drop the frame safely. + */ + if (m_config.encoder.num_ch != AUDIO_CH_NUM) { + LOG_DBG("Rejecting stereo encode with %u configured channels", + (unsigned int)m_config.encoder.num_ch); + return -EINVAL; + } + for (int i = 0; i < m_config.encoder.num_ch; ++i) { ret = sw_codec_sample_rate_convert( &encoder_converters[i], CONFIG_AUDIO_SAMPLE_RATE_HZ, @@ -238,13 +247,19 @@ int sw_codec_decode(uint8_t const *const encoded_data, size_t encoded_size, bool switch (m_config.sw_codec) { case SW_CODEC_LC3: { #if (CONFIG_SW_CODEC_LC3) - char *pcm_in_data_ptrs[m_config.decoder.channel_mode]; + /* PLC override bypasses sample-rate conversion, so its branches assign these + * fixed channel slots directly to zero-filled PCM buffers. + */ + char *pcm_in_data_ptrs[AUDIO_CH_NUM] = {0}; switch (m_config.decoder.channel_mode) { case SW_CODEC_MONO: { if (bad_frame && IS_ENABLED(CONFIG_SW_CODEC_OVERRIDE_PLC)) { memset(decoded_data_mono[AUDIO_CH_L], 0, PCM_NUM_BYTES_MONO); decoded_data_size = PCM_NUM_BYTES_MONO; + pcm_in_data_ptrs[AUDIO_CH_L] = decoded_data_mono[AUDIO_CH_L]; + pcm_size_mono = decoded_data_size; + LOG_DBG("Replacing bad mono frame with silence"); } else { ret = sw_codec_lc3_dec_run( encoded_data, encoded_size, LC3_PCM_NUM_BYTES_MONO, 0, @@ -284,6 +299,10 @@ int sw_codec_decode(uint8_t const *const encoded_data, size_t encoded_size, bool memset(decoded_data_mono[AUDIO_CH_L], 0, PCM_NUM_BYTES_MONO); memset(decoded_data_mono[AUDIO_CH_R], 0, PCM_NUM_BYTES_MONO); decoded_data_size = PCM_NUM_BYTES_MONO; + pcm_in_data_ptrs[AUDIO_CH_L] = decoded_data_mono[AUDIO_CH_L]; + pcm_in_data_ptrs[AUDIO_CH_R] = decoded_data_mono[AUDIO_CH_R]; + pcm_size_mono = decoded_data_size; + LOG_DBG("Replacing bad stereo frame with silence"); } else { /* Decode left channel */ ret = sw_codec_lc3_dec_run( @@ -304,7 +323,8 @@ int sw_codec_decode(uint8_t const *const encoded_data, size_t encoded_size, bool return ret; } - for (int i = 0; i < m_config.decoder.channel_mode; ++i) { + /* Stereo always initializes both slots consumed by pscm_combine(). */ + for (int i = 0; i < AUDIO_CH_NUM; ++i) { ret = sw_codec_sample_rate_convert( &decoder_converters[i], m_config.decoder.sample_rate_hz, diff --git a/src/bluetooth/Kconfig b/src/bluetooth/Kconfig index 2bb2d8d2..0bafa549 100644 --- a/src/bluetooth/Kconfig +++ b/src/bluetooth/Kconfig @@ -8,6 +8,23 @@ rsource "Kconfig.defaults" menu "Bluetooth" +config AUDIO_RESPONSE_MAX_SAMPLES + int "Maximum uploaded audio response samples" + range 1 48000 + default 32000 + help + Maximum number of signed 16-bit PCM samples retained by the audio + response service. Storage for this buffer is reserved at build time. + +config AUDIO_RESPONSE_MAX_POINTS + int "Maximum audio response measurement points" + range 9 60 + default 60 + help + Maximum number of frequency response points accepted in one audio + response config request. The default keeps result notifications within a + 247-byte ATT MTU because the encoded result uses 2 + 4 * points bytes. + rsource "bt_management/Kconfig" #----------------------------------------------------------------------------# diff --git a/src/bluetooth/bt_content_control/media/bt_content_ctrl_media.c b/src/bluetooth/bt_content_control/media/bt_content_ctrl_media.c index 18e6e2cd..f1b5e3e3 100644 --- a/src/bluetooth/bt_content_control/media/bt_content_ctrl_media.c +++ b/src/bluetooth/bt_content_control/media/bt_content_ctrl_media.c @@ -44,7 +44,7 @@ static struct media_ctlr mcc_peer[CONFIG_BT_MAX_CONN]; */ static int mcc_peer_index_free_get(void) { - for (int i = 0; i < ARRAY_SIZE(mcc_peer); i++) { + for (size_t i = 0; i < ARRAY_SIZE(mcc_peer); i++) { if (mcc_peer[i].conn == NULL) { return i; } @@ -162,6 +162,7 @@ static void mcc_send_command_cb(struct bt_conn *conn, int err, const struct mpl_ */ static void mcc_cmd_notification_cb(struct bt_conn *conn, int err, const struct mpl_cmd_ntf *ntf) { + ARG_UNUSED(conn); LOG_DBG("mcc_cmd_ntf_cb"); if (err) { @@ -179,6 +180,7 @@ static void mcc_cmd_notification_cb(struct bt_conn *conn, int err, const struct */ static void mcc_read_media_state_cb(struct bt_conn *conn, int err, uint8_t state) { + ARG_UNUSED(conn); LOG_DBG("mcc_read_media_cb, state: %d", state); if (err) { @@ -199,6 +201,7 @@ static void mcc_read_media_state_cb(struct bt_conn *conn, int err, uint8_t state static void mcs_command_recv_cb(struct media_player *plr, int err, const struct mpl_cmd_ntf *cmd_ntf) { + ARG_UNUSED(plr); if (err) { LOG_ERR("Command failed (%d)", err); return; @@ -223,6 +226,7 @@ static void mcs_command_recv_cb(struct media_player *plr, int err, */ static void mcs_media_state_cb(struct media_player *plr, int err, uint8_t state) { + ARG_UNUSED(plr); if (err) { LOG_ERR("Media state failed (%d)", err); return; diff --git a/src/bluetooth/bt_management/advertising/bt_mgmt_adv.c b/src/bluetooth/bt_management/advertising/bt_mgmt_adv.c index ba1e4922..d43317b4 100644 --- a/src/bluetooth/bt_management/advertising/bt_mgmt_adv.c +++ b/src/bluetooth/bt_management/advertising/bt_mgmt_adv.c @@ -9,7 +9,7 @@ #include #include #include - + #include "macros_common.h" #include "zbus_common.h" @@ -47,8 +47,10 @@ .peer = NULL, }; - static void bond_find(const struct bt_bond_info *info, void *user_data) - { +static void bond_find(const struct bt_bond_info *info, void *user_data) +{ + ARG_UNUSED(user_data); + int ret; struct bt_conn *conn; @@ -83,8 +85,10 @@ } } - static void filter_accept_list_add(const struct bt_bond_info *info, void *user_data) - { +static void filter_accept_list_add(const struct bt_bond_info *info, void *user_data) +{ + ARG_UNUSED(user_data); + int ret; ret = bt_le_filter_accept_list_add(&info->addr); @@ -227,8 +231,10 @@ return 0; } - static void advertising_process(struct k_work *work) - { +static void advertising_process(struct k_work *work) +{ + ARG_UNUSED(work); + int ret; struct bt_mgmt_msg msg; uint8_t ext_adv_index; @@ -470,8 +476,7 @@ return 0; } - void bt_mgmt_adv_init(void) - { - k_work_init(&adv_work, advertising_process); - } - \ No newline at end of file +void bt_mgmt_adv_init(void) +{ + k_work_init(&adv_work, advertising_process); +} diff --git a/src/bluetooth/bt_management/bt_mgmt.c b/src/bluetooth/bt_management/bt_mgmt.c index da38a9cb..4d0a1818 100644 --- a/src/bluetooth/bt_management/bt_mgmt.c +++ b/src/bluetooth/bt_management/bt_mgmt.c @@ -6,6 +6,7 @@ #include "bt_mgmt.h" +#include #include "channel_assignment.h" #include @@ -19,6 +20,8 @@ #include "macros_common.h" #include "zbus_common.h" #include "button_assignments.h" +#include "../../buttons/button_manager.h" +#include "uicr.h" #include "bt_mgmt_ctlr_cfg_internal.h" #include "bt_mgmt_adv_internal.h" @@ -67,12 +70,16 @@ static void conn_state_connected_check(struct bt_conn *conn, void *data) void mtu_updated(struct bt_conn *conn, uint16_t tx, uint16_t rx) { + ARG_UNUSED(conn); + LOG_INF("Updated MTU: TX: %d RX: %d bytes", tx, rx); } static void le_data_length_updated(struct bt_conn *conn, struct bt_conn_le_data_len_info *info) { + ARG_UNUSED(conn); + LOG_INF("LE data len updated: TX (len: %d time: %d)" " RX (len: %d time: %d)", info->tx_max_len, info->tx_max_time, info->rx_max_len, info->rx_max_time); @@ -83,9 +90,6 @@ static struct bt_le_conn_param *conn_param = BT_LE_CONN_PARAM(CONFIG_BLE_ACL_CON //callback static void conn_params_updated(struct bt_conn *conn, uint16_t interval, uint16_t latency, uint16_t timeout) { - struct bt_mgmt_msg msg; - int ret; - LOG_INF("Conn params updated: interval %d unit, latency %d, timeout: %d0 ms",interval, latency, timeout); bt_mgmt_ci_on_conn_param_updated(conn, interval, latency, timeout); @@ -163,7 +167,7 @@ static void connected_cb(struct bt_conn *conn, uint8_t err) err = bt_conn_le_param_update(conn, conn_param); if (err) { LOG_ERR("Cannot update conneciton parameter (err: %d)", err); - return err; + return; } LOG_INF("Connection parameters update requested: interval_min %d interval_max %d latency %d timeout %d", conn_param->interval_min, conn_param->interval_max, @@ -319,7 +323,7 @@ static int local_identity_addr_print(void) bt_id_get(addrs, &num_ids); - for (int i = 0; i < num_ids; i++) { + for (size_t i = 0; i < num_ids; i++) { (void)bt_addr_le_to_str(&(addrs[i]), addr_str, BT_ADDR_LE_STR_LEN); LOG_INF("Local identity addr: %s", addr_str); } @@ -388,6 +392,9 @@ int bt_mgmt_conn_disconnect(struct bt_conn *conn, uint8_t reason) int bonded_device_count = 0; void count_bonds(const struct bt_bond_info *info, void *user_data) { + ARG_UNUSED(info); + ARG_UNUSED(user_data); + bonded_device_count++; } @@ -408,7 +415,8 @@ int bt_mgmt_init(void) bt_gatt_cb_register(&gatt_callbacks); uint32_t sirk = uicr_sirk_get(); - snprintf(name, CONFIG_BT_DEVICE_NAME_MAX, "%s-%04X", CONFIG_BT_DEVICE_NAME, (sirk != 0xFFFFFFFFU ? sirk : oe_boot_state.device_id) & 0xFFFF); + snprintf(name, CONFIG_BT_DEVICE_NAME_MAX, "%s-%04X", CONFIG_BT_DEVICE_NAME, + (unsigned int)((sirk != 0xFFFFFFFFU ? sirk : oe_boot_state.device_id) & 0xFFFF)); ret = bt_set_name(name); if (ret) { diff --git a/src/bluetooth/bt_management/bt_mgmt_conn_interval.cpp b/src/bluetooth/bt_management/bt_mgmt_conn_interval.cpp index fa089483..e7fb9836 100644 --- a/src/bluetooth/bt_management/bt_mgmt_conn_interval.cpp +++ b/src/bluetooth/bt_management/bt_mgmt_conn_interval.cpp @@ -146,6 +146,6 @@ void BtMgmtConnIntervalStrategy::request_interval(uint16_t min_units, uint16_t m } else { LOG_INF("Requested CI [%u..%u] units (%.2f..%.2f ms)", min_units, max_units, - min_units * 1.25f, max_units * 1.25f); + (double)(min_units * 1.25f), (double)(max_units * 1.25f)); } -} \ No newline at end of file +} diff --git a/src/bluetooth/bt_management/conn_interval/conn_intvl_exp_backoff_lin_inc.cpp b/src/bluetooth/bt_management/conn_interval/conn_intvl_exp_backoff_lin_inc.cpp index 79ba1f7f..631335b2 100644 --- a/src/bluetooth/bt_management/conn_interval/conn_intvl_exp_backoff_lin_inc.cpp +++ b/src/bluetooth/bt_management/conn_interval/conn_intvl_exp_backoff_lin_inc.cpp @@ -19,10 +19,10 @@ void ConnIntvlExpBackoffLinIncr::init() { } void ConnIntvlExpBackoffLinIncr::on_audio_underrun(uint32_t count) { - uint16_t current_interval_units_ = this->current_interval_units(); + uint16_t current_interval_units = this->current_interval_units(); - LOG_DBG("Audio underrun reported: %u, current interval: %u units", count, current_interval_units_); - uint16_t new_interval_units = current_interval_units_ * backoff_factor_; + LOG_DBG("Audio underrun reported: %u, current interval: %u units", count, current_interval_units); + uint16_t new_interval_units = current_interval_units * backoff_factor_; if (new_interval_units > max_interval_units_) { new_interval_units = max_interval_units_; } @@ -31,6 +31,7 @@ void ConnIntvlExpBackoffLinIncr::on_audio_underrun(uint32_t count) { } void ConnIntvlExpBackoffLinIncr::on_timer_tick(k_timeout_t elapsed) { + ARG_UNUSED(elapsed); bool is_streaming = stream_state_get() == STATE_STREAMING; if (!is_streaming) { return; @@ -41,4 +42,4 @@ void ConnIntvlExpBackoffLinIncr::on_timer_tick(k_timeout_t elapsed) { new_interval_units = min_interval_units_; } this->request_interval(new_interval_units, new_interval_units); -} \ No newline at end of file +} diff --git a/src/bluetooth/bt_management/conn_interval/conn_intvl_linear.cpp b/src/bluetooth/bt_management/conn_interval/conn_intvl_linear.cpp index dfc792f9..f1214a78 100644 --- a/src/bluetooth/bt_management/conn_interval/conn_intvl_linear.cpp +++ b/src/bluetooth/bt_management/conn_interval/conn_intvl_linear.cpp @@ -16,10 +16,10 @@ void ConnIntvlLinear::init() { } void ConnIntvlLinear::on_audio_underrun(uint32_t count) { - uint16_t current_interval_units_ = this->current_interval_units(); + uint16_t current_interval_units = this->current_interval_units(); - LOG_DBG("Audio underrun reported: %u, current interval: %u units", count, current_interval_units_); - uint16_t new_interval_units = current_interval_units_ + inc_step_units_; + LOG_DBG("Audio underrun reported: %u, current interval: %u units", count, current_interval_units); + uint16_t new_interval_units = current_interval_units + inc_step_units_; if (new_interval_units > max_interval_units_) { new_interval_units = max_interval_units_; } @@ -28,6 +28,7 @@ void ConnIntvlLinear::on_audio_underrun(uint32_t count) { } void ConnIntvlLinear::on_timer_tick(k_timeout_t elapsed) { + ARG_UNUSED(elapsed); bool is_streaming = stream_state_get() == STATE_STREAMING; if (!is_streaming) { return; @@ -38,4 +39,4 @@ void ConnIntvlLinear::on_timer_tick(k_timeout_t elapsed) { new_interval_units = min_interval_units_; } this->request_interval(new_interval_units, new_interval_units); -} \ No newline at end of file +} diff --git a/src/bluetooth/bt_management/controller_config/bt_mgmt_ctlr_cfg.c b/src/bluetooth/bt_management/controller_config/bt_mgmt_ctlr_cfg.c index a7ff6734..8bd09f19 100644 --- a/src/bluetooth/bt_management/controller_config/bt_mgmt_ctlr_cfg.c +++ b/src/bluetooth/bt_management/controller_config/bt_mgmt_ctlr_cfg.c @@ -42,6 +42,7 @@ K_TIMER_DEFINE(ctlr_poll_timer, ctlr_poll_timer_handler, NULL); static void work_ctlr_poll_handler(struct k_work *work) { + ARG_UNUSED(work); int ret; uint16_t manufacturer = 0; @@ -54,6 +55,7 @@ static void work_ctlr_poll_handler(struct k_work *work) static void ctlr_poll_timer_handler(struct k_timer *timer_id) { + ARG_UNUSED(timer_id); int ret; ret = k_work_submit_to_queue(&ctrl_poll_work_q, &work_ctlr_poll); @@ -64,6 +66,8 @@ static void ctlr_poll_timer_handler(struct k_timer *timer_id) static void wdt_timeout_cb(int channel_id, void *user_data) { + ARG_UNUSED(channel_id); + ARG_UNUSED(user_data); ERR_CHK_MSG(-ETIMEDOUT, "No response from IPC or controller"); } @@ -139,4 +143,4 @@ int bt_mgmt_stop_watchdog(void) k_timer_stop(&ctlr_poll_timer); int ret = task_wdt_delete(wdt_ch_id); return ret; -} \ No newline at end of file +} diff --git a/src/bluetooth/bt_management/dfu/bt_mgmt_dfu.c b/src/bluetooth/bt_management/dfu/bt_mgmt_dfu.c index 8c50cd94..d1560b9a 100644 --- a/src/bluetooth/bt_management/dfu/bt_mgmt_dfu.c +++ b/src/bluetooth/bt_management/dfu/bt_mgmt_dfu.c @@ -55,11 +55,14 @@ static void smp_adv(void) /* These callbacks are to override callback registed in module le_audio_ */ static void dfu_connected_cb(struct bt_conn *conn, uint8_t err) { + ARG_UNUSED(conn); + ARG_UNUSED(err); LOG_INF("SMP connected\n"); } static void dfu_disconnected_cb(struct bt_conn *conn, uint8_t reason) { + ARG_UNUSED(conn); LOG_INF("SMP disconnected 0x%02x\n", reason); } diff --git a/src/bluetooth/bt_management/scanning/bt_mgmt_scan.c b/src/bluetooth/bt_management/scanning/bt_mgmt_scan.c index 5466c1b0..94cd2567 100644 --- a/src/bluetooth/bt_management/scanning/bt_mgmt_scan.c +++ b/src/bluetooth/bt_management/scanning/bt_mgmt_scan.c @@ -55,9 +55,13 @@ int bt_mgmt_scan_start(uint16_t scan_intvl, uint16_t scan_win, enum bt_mgmt_scan scan_window = scan_win; } - struct bt_le_scan_param *scan_param = + struct bt_le_scan_param *scan_param = NULL; + +#if defined(CONFIG_BT_CENTRAL) || defined(CONFIG_BT_BAP_BROADCAST_SINK) + scan_param = BT_LE_SCAN_PARAM(NRF5340_AUDIO_GATEWAY_SCAN_TYPE, BT_LE_SCAN_OPT_FILTER_DUPLICATE, scan_interval, scan_window); +#endif if (type == BT_MGMT_SCAN_TYPE_CONN && IS_ENABLED(CONFIG_BT_CENTRAL)) { ret = bt_mgmt_scan_for_conn_start(scan_param, srch_name); diff --git a/src/bluetooth/bt_rendering_and_capture/volume/bt_vol_rend.c b/src/bluetooth/bt_rendering_and_capture/volume/bt_vol_rend.c index 8f1f952f..578abaf6 100644 --- a/src/bluetooth/bt_rendering_and_capture/volume/bt_vol_rend.c +++ b/src/bluetooth/bt_rendering_and_capture/volume/bt_vol_rend.c @@ -24,6 +24,7 @@ LOG_MODULE_REGISTER(bt_vol_rend, CONFIG_BT_VOL_LOG_LEVEL); */ static void vcs_state_rend_cb_handler(struct bt_conn *conn, int err, uint8_t volume, uint8_t mute) { + ARG_UNUSED(conn); int ret; static uint8_t prev_mute = 0; @@ -63,6 +64,7 @@ static void vcs_state_rend_cb_handler(struct bt_conn *conn, int err, uint8_t vol */ static void vcs_flags_rend_cb_handler(struct bt_conn *conn, int err, uint8_t flags) { + ARG_UNUSED(conn); if (err) { LOG_ERR("VCS flag callback error: %d", err); } else { diff --git a/src/bluetooth/bt_stream/bt_le_audio_tx/bt_le_audio_tx.c b/src/bluetooth/bt_stream/bt_le_audio_tx/bt_le_audio_tx.c index 41608e60..81550475 100644 --- a/src/bluetooth/bt_stream/bt_le_audio_tx/bt_le_audio_tx.c +++ b/src/bluetooth/bt_stream/bt_le_audio_tx/bt_le_audio_tx.c @@ -96,10 +96,6 @@ static int iso_stream_send(uint8_t const *const data, size_t size, struct bt_cap */ if (atomic_get(&tx_info->iso_tx_pool_alloc) >= HCI_ISO_BUF_PER_CHAN) { if (!tx_info->hci_wrn_printed) { - struct bt_iso_chan *iso_chan; - - iso_chan = bt_bap_stream_iso_chan_get(&cap_stream->bap_stream); - LOG_WRN("HCI ISO TX overrun on stream %p - Single print", (void *)&cap_stream->bap_stream); tx_info->hci_wrn_printed = true; diff --git a/src/bluetooth/bt_stream/unicast/unicast_server.c b/src/bluetooth/bt_stream/unicast/unicast_server.c index c1aa6384..cd01b88c 100644 --- a/src/bluetooth/bt_stream/unicast/unicast_server.c +++ b/src/bluetooth/bt_stream/unicast/unicast_server.c @@ -6,6 +6,8 @@ #include "unicast_server.h" + #include + #include #include #include #include @@ -20,7 +22,9 @@ #include "zbus_common.h" #include "bt_mgmt.h" #include "bt_le_audio_tx.h" + #include "channel_assignment.h" #include "le_audio.h" + #include "uicr.h" #include "BootState.h" @@ -98,15 +102,18 @@ static uint8_t device_identifier[] = { } /* Callback for locking state change from server side */ - static void csip_lock_changed_cb(struct bt_conn *conn, struct bt_csip_set_member_svc_inst *csip, - bool locked) - { +static void csip_lock_changed_cb(struct bt_conn *conn, struct bt_csip_set_member_svc_inst *csip_instance, + bool locked) +{ + ARG_UNUSED(csip_instance); LOG_DBG("Client %p %s the lock", (void *)conn, locked ? "locked" : "released"); } /* Callback for SIRK read request from peer side */ - static uint8_t sirk_read_req_cb(struct bt_conn *conn, struct bt_csip_set_member_svc_inst *csip) - { +static uint8_t sirk_read_req_cb(struct bt_conn *conn, struct bt_csip_set_member_svc_inst *csip_instance) +{ + ARG_UNUSED(conn); + ARG_UNUSED(csip_instance); /* Accept the request to read the SIRK, but return encrypted SIRK instead of plaintext */ return BT_CSIP_READ_SIRK_REQ_RSP_ACCEPT_ENC; } @@ -177,11 +184,13 @@ static uint8_t device_identifier[] = { static int lc3_config_cb(struct bt_conn *conn, const struct bt_bap_ep *ep, enum bt_audio_dir dir, const struct bt_audio_codec_cfg *codec, struct bt_bap_stream **stream, struct bt_bap_qos_cfg_pref *const pref, struct bt_bap_ascs_rsp *rsp) - { +{ int ret; + ARG_UNUSED(ep); + ARG_UNUSED(rsp); LOG_DBG("LC3 config callback"); - for (int i = 0; i < ARRAY_SIZE(cap_audio_streams); i++) { + for (size_t i = 0; i < ARRAY_SIZE(cap_audio_streams); i++) { struct bt_cap_stream *cap_audio_stream = &cap_audio_streams[i]; if (!cap_audio_stream->bap_stream.conn) { @@ -233,7 +242,11 @@ static uint8_t device_identifier[] = { static int lc3_reconfig_cb(struct bt_bap_stream *stream, enum bt_audio_dir dir, const struct bt_audio_codec_cfg *codec, struct bt_bap_qos_cfg_pref *const pref, struct bt_bap_ascs_rsp *rsp) - { +{ + ARG_UNUSED(dir); + ARG_UNUSED(codec); + ARG_UNUSED(pref); + ARG_UNUSED(rsp); LOG_DBG("ASE Codec Reconfig: stream %p", (void *)stream); return 0; @@ -241,7 +254,8 @@ static uint8_t device_identifier[] = { static int lc3_qos_cb(struct bt_bap_stream *stream, const struct bt_bap_qos_cfg *qos, struct bt_bap_ascs_rsp *rsp) - { +{ + ARG_UNUSED(rsp); enum bt_audio_dir dir; dir = le_audio_stream_dir_get(stream); @@ -259,27 +273,33 @@ static uint8_t device_identifier[] = { static int lc3_enable_cb(struct bt_bap_stream *stream, const uint8_t *meta, size_t meta_len, struct bt_bap_ascs_rsp *rsp) - { +{ + ARG_UNUSED(meta); + ARG_UNUSED(rsp); LOG_DBG("Enable: stream %p meta_len %d", (void *)stream, meta_len); return 0; } static int lc3_start_cb(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp) - { +{ + ARG_UNUSED(rsp); LOG_DBG("Start stream %p", (void *)stream); return 0; } static int lc3_metadata_cb(struct bt_bap_stream *stream, const uint8_t *meta, size_t meta_len, struct bt_bap_ascs_rsp *rsp) - { +{ + ARG_UNUSED(meta); + ARG_UNUSED(rsp); LOG_DBG("Metadata: stream %p meta_len %d", (void *)stream, meta_len); return 0; } static int lc3_disable_cb(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp) - { +{ + ARG_UNUSED(rsp); enum bt_audio_dir dir; dir = le_audio_stream_dir_get(stream); @@ -296,7 +316,8 @@ static uint8_t device_identifier[] = { } static int lc3_stop_cb(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp) - { +{ + ARG_UNUSED(rsp); enum bt_audio_dir dir; dir = le_audio_stream_dir_get(stream); @@ -313,7 +334,8 @@ static uint8_t device_identifier[] = { } static int lc3_release_cb(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp) - { +{ + ARG_UNUSED(rsp); enum bt_audio_dir dir; dir = le_audio_stream_dir_get(stream); @@ -364,6 +386,7 @@ static uint8_t device_identifier[] = { #if (CONFIG_BT_AUDIO_TX) static void stream_sent_cb(struct bt_bap_stream *stream) { + ARG_UNUSED(stream); /* Unicast server/CIS headset only supports one source stream for now */ struct stream_index idx = { .lvl1 = 0, @@ -397,8 +420,9 @@ static uint8_t device_identifier[] = { } } - static void stream_disabled_cb(struct bt_bap_stream *stream) - { +static void stream_disabled_cb(struct bt_bap_stream *stream) +{ + ARG_UNUSED(stream); LOG_INF("Stream %p disabled", stream); } @@ -441,8 +465,9 @@ static uint8_t device_identifier[] = { le_audio_event_publish(LE_AUDIO_EVT_NOT_STREAMING, stream->conn, dir); } - static void stream_released_cb(struct bt_bap_stream *stream) - { +static void stream_released_cb(struct bt_bap_stream *stream) +{ + ARG_UNUSED(stream); /* NOTE: The string below is used by the Nordic CI system */ LOG_INF("Stream %p released", stream); } @@ -464,6 +489,7 @@ static uint8_t device_identifier[] = { int unicast_server_config_get(struct bt_conn *conn, enum bt_audio_dir dir, uint32_t *bitrate, uint32_t *sampling_rate_hz, uint32_t *pres_delay_us) { + ARG_UNUSED(conn); int ret; if (bitrate == NULL && sampling_rate_hz == NULL && pres_delay_us == NULL) { @@ -630,7 +656,7 @@ static uint8_t device_identifier[] = { struct le_audio_tx_info tx[CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT]; - for (int i = 0; i < ARRAY_SIZE(cap_tx_streams); i++) { + for (size_t i = 0; i < ARRAY_SIZE(cap_tx_streams); i++) { if (!le_audio_ep_state_check(cap_tx_streams[i]->bap_stream.ep, BT_BAP_EP_STATE_STREAMING)) { continue; @@ -708,7 +734,8 @@ static uint8_t device_identifier[] = { if (sirk != 0xFFFFFFFF) { snprintf(sirk_string, 16, "%08X", sirk); //"%016llX" } else { - snprintf(sirk_string, 16, "%08X", oe_boot_state.device_id); //"%016llX" + snprintf(sirk_string, 16, "%08X", + (unsigned int)oe_boot_state.device_id); //"%016llX" } // LOG_INF("SIRK as String: %s", sirk_string); @@ -719,7 +746,7 @@ static uint8_t device_identifier[] = { // memcpy(csip_param.sirk, CONFIG_BT_SET_IDENTITY_RESOLVING_KEY, BT_CSIP_SIRK_SIZE); } - for (int i = 0; i < ARRAY_SIZE(caps); i++) { + for (size_t i = 0; i < ARRAY_SIZE(caps); i++) { ret = bt_pacs_cap_register(caps_dirs[i], &caps[i]); if (ret) { LOG_ERR("Capability register failed. Err: %d", ret); @@ -780,7 +807,7 @@ static uint8_t device_identifier[] = { return ret; } - for (int i = 0; i < ARRAY_SIZE(cap_audio_streams); i++) { + for (size_t i = 0; i < ARRAY_SIZE(cap_audio_streams); i++) { bt_cap_stream_ops_register(&cap_audio_streams[i], &stream_ops); } @@ -802,4 +829,3 @@ static uint8_t device_identifier[] = { return 0; } - \ No newline at end of file diff --git a/src/bluetooth/gatt_services/CMakeLists.txt b/src/bluetooth/gatt_services/CMakeLists.txt index 141acb9e..d74ba9b3 100644 --- a/src/bluetooth/gatt_services/CMakeLists.txt +++ b/src/bluetooth/gatt_services/CMakeLists.txt @@ -6,4 +6,5 @@ target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/audio_config_service.c ${CMAKE_CURRENT_SOURCE_DIR}/power_saving_service.c ${CMAKE_CURRENT_SOURCE_DIR}/led_service.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/audio_response_service.c ) diff --git a/src/bluetooth/gatt_services/audio_config_service.c b/src/bluetooth/gatt_services/audio_config_service.c index d0a8513a..c8a53e10 100644 --- a/src/bluetooth/gatt_services/audio_config_service.c +++ b/src/bluetooth/gatt_services/audio_config_service.c @@ -12,6 +12,10 @@ LOG_MODULE_REGISTER(audio_config_service, CONFIG_BLE_LOG_LEVEL); static ssize_t write_audio_mode(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(offset); + ARG_UNUSED(flags); if (len != sizeof(uint8_t)) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } @@ -21,13 +25,21 @@ static ssize_t write_audio_mode(struct bt_conn *conn, const struct bt_gatt_attr return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); } - hw_codec_set_audio_mode((enum audio_mode)mode); + int ret = hw_codec_set_audio_mode((enum audio_mode)mode); + if (ret) { + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + return len; } static ssize_t write_mic_select(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(offset); + ARG_UNUSED(flags); if (len != sizeof(uint8_t)) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } @@ -69,7 +81,52 @@ static ssize_t read_audio_channel(struct bt_conn *conn, const struct bt_gatt_att channel_assignment_get(&channel); uint8_t channel_u8 = channel; - return bt_gatt_attr_read(conn, attr, buf, len, offset, &channel_u8, sizeof(channel)); + return bt_gatt_attr_read(conn, attr, buf, len, offset, &channel_u8, sizeof(channel_u8)); +} + +static ssize_t write_dmic_gain(struct bt_conn *conn, const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, uint16_t offset, uint8_t flags) +{ + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(offset); + ARG_UNUSED(flags); + // Mic gain is 2 bytes: [outer_reg, inner_reg]. + // Outer mic maps to DMIC_VOL0; inner mic maps to DMIC_VOL1. + // Per ADAU186x DMIC_VOL register (0x4000C045): + // 0x00 = +24 dB + // 0x01-0x3F = +23.625 to +0.375 dB (0.375 dB steps) + // 0x40 = 0 dB + // 0x41-0xFD = -0.375 to -70.875 dB (0.375 dB steps) + // 0xFE = -71.25 dB + // 0xFF = Mute + if (len != 2) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + uint8_t gain_outer = ((uint8_t*)buf)[0]; + uint8_t gain_inner = ((uint8_t*)buf)[1]; + + int ret = hw_codec_mic_gain_set(gain_outer, gain_inner); + if (ret) { + LOG_ERR("Failed to set mic gain: %d", ret); + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + LOG_INF("DMIC gain via BLE: outer=0x%02x inner=0x%02x", + gain_outer, gain_inner); + return len; +} + +static ssize_t read_dmic_gain(struct bt_conn *conn, const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) +{ + uint8_t gains[2] = { + hw_codec_mic_gain_get_outer(), + hw_codec_mic_gain_get_inner() + }; + + return bt_gatt_attr_read(conn, attr, buf, len, offset, gains, sizeof(gains)); } BT_GATT_SERVICE_DEFINE(audio_config_svc, @@ -86,6 +143,10 @@ BT_GATT_SERVICE_DEFINE(audio_config_svc, BT_GATT_CHRC_READ, BT_GATT_PERM_READ, read_audio_channel, NULL, NULL), + BT_GATT_CHARACTERISTIC(BT_UUID_DMIC_GAIN, + BT_GATT_CHRC_WRITE | BT_GATT_CHRC_READ, + BT_GATT_PERM_WRITE | BT_GATT_PERM_READ, + read_dmic_gain, write_dmic_gain, NULL), ); int init_audio_config_service(void) diff --git a/src/bluetooth/gatt_services/audio_config_service.h b/src/bluetooth/gatt_services/audio_config_service.h index 71b69261..40b696be 100644 --- a/src/bluetooth/gatt_services/audio_config_service.h +++ b/src/bluetooth/gatt_services/audio_config_service.h @@ -33,6 +33,13 @@ #define BT_UUID_AUDIO_CHANNEL \ BT_UUID_DECLARE_128(BT_UUID_AUDIO_CHANNEL_VAL) +// DMIC Gain Characteristic UUID +#define BT_UUID_DMIC_GAIN_VAL \ + BT_UUID_128_ENCODE(0x1410df99, 0x5f68, 0x4ebb, 0xa7c7, 0x5e0fb9ae7557) + +#define BT_UUID_DMIC_GAIN \ + BT_UUID_DECLARE_128(BT_UUID_DMIC_GAIN_VAL) + int init_audio_config_service(void); #endif /* _AUDIO_CONFIG_SERVICE_H_ */ diff --git a/src/bluetooth/gatt_services/audio_response_service.c b/src/bluetooth/gatt_services/audio_response_service.c new file mode 100644 index 00000000..921ae3b7 --- /dev/null +++ b/src/bluetooth/gatt_services/audio_response_service.c @@ -0,0 +1,919 @@ +#include "audio_response_service.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "audio_datapath.h" +#include "audio_system.h" +#include "hw_codec.h" + +LOG_MODULE_REGISTER(audio_response_service, CONFIG_LOG_DEFAULT_LEVEL); + +#define AUDIO_RESPONSE_CAPTURE_SAMPLES 2048 +#define AUDIO_RESPONSE_CAPTURE_SAMPLE_RATE 4000U +#define AUDIO_RESPONSE_INITIAL_DROP 128 +#define AUDIO_RESPONSE_DEFAULT_POINTS 9 +#define AUDIO_RESPONSE_TRANSFER_CREDITS 1 +#define AUDIO_RESPONSE_TRANSFER_CREDIT_DELAY K_MSEC(1) +#define AUDIO_RESPONSE_TRANSFER_TIMEOUT K_SECONDS(30) +#define AUDIO_RESPONSE_STATUS_ENCODED_SIZE 9 +#define AUDIO_RESPONSE_RESULT_ENCODED_SIZE (2 + (CONFIG_AUDIO_RESPONSE_MAX_POINTS * 4)) +#define AUDIO_RESPONSE_TWO_PI 6.28318530717958647692f + +BUILD_ASSERT(CONFIG_AUDIO_RESPONSE_MAX_POINTS >= AUDIO_RESPONSE_DEFAULT_POINTS, + "Default audio response points must fit CONFIG_AUDIO_RESPONSE_MAX_POINTS"); + +enum audio_response_transfer_status { + AUDIO_RESPONSE_TRANSFER_READY = 0, + AUDIO_RESPONSE_TRANSFER_COMMITTED = 1, + AUDIO_RESPONSE_TRANSFER_ABORTED = 2, + AUDIO_RESPONSE_TRANSFER_INVALID_STATE = 3, + AUDIO_RESPONSE_TRANSFER_INVALID_CHUNK = 4, + AUDIO_RESPONSE_TRANSFER_INSUFFICIENT_STORAGE = 5, + AUDIO_RESPONSE_TRANSFER_CHECKSUM_MISMATCH = 6, + AUDIO_RESPONSE_TRANSFER_TIMED_OUT = 7, +}; + +struct audio_response_transfer { + uint16_t id; + uint32_t total_samples; + uint32_t sampling_rate; + uint32_t expected_checksum; + uint32_t received_samples; + int16_t *samples; + bool active; + bool committed; +}; + +struct audio_response_audio_session { + bool audio_system_suspended; + bool auxiliary_audio_suspended; + bool datapath_acquired; + bool measurement_codec_enabled; + bool measurement_playback_started; +}; + +/** + * Carries the GATT write metadata and result through the generated union dispatcher. + */ +struct transfer_control_dispatch_context { + uint16_t write_len; + ssize_t result; +}; + +static const uint16_t default_frequencies[AUDIO_RESPONSE_DEFAULT_POINTS] = { + 40, 60, 90, 135, 203, 304, 456, 683, 1025, +}; + +static int16_t captured_samples[AUDIO_RESPONSE_CAPTURE_SAMPLES]; +static int16_t uploaded_samples[CONFIG_AUDIO_RESPONSE_MAX_SAMPLES]; +static uint16_t requested_frequencies[CONFIG_AUDIO_RESPONSE_MAX_POINTS]; +static uint16_t decoded_config_frequencies[UINT8_MAX]; +static uint16_t result_frequencies[CONFIG_AUDIO_RESPONSE_MAX_POINTS]; +static uint16_t result_response[CONFIG_AUDIO_RESPONSE_MAX_POINTS]; +static uint8_t result_payload[AUDIO_RESPONSE_RESULT_ENCODED_SIZE]; + +static struct audio_response_transfer transfer; +static struct audio_response_audio_session audio_session; +static audio_response_config_t pending_config; +static bool measurement_active; +static bool transfer_status_notifications_enabled; +static bool result_notifications_enabled; + +static struct k_work measurement_work; +static struct k_work measurement_complete_work; +static struct k_work_delayable transfer_ready_work; +static struct k_work_delayable transfer_timeout_work; +K_MUTEX_DEFINE(service_mutex); + +extern struct data_fifo fifo_rx; +extern const struct bt_gatt_service_static audio_response_svc; + +static int notify_transfer_status(enum audio_response_transfer_status status, uint16_t credits); +static uint32_t response_frequency_to_bin(uint16_t frequency); +static bool is_valid_response_frequency(uint16_t frequency); +static uint16_t response_magnitude_for_bin(const int16_t *samples, size_t sample_count, + uint32_t bin); +static int select_response_points(audio_response_config_t *config); +static void reset_transfer(void); +static int suspend_audio_for_measurement(void); +static void restore_audio_after_measurement(void); + +/** + * Return a readable transfer status name for logs. + */ +static const char *transfer_status_name(enum audio_response_transfer_status status) +{ + switch (status) { + case AUDIO_RESPONSE_TRANSFER_READY: + return "ready"; + case AUDIO_RESPONSE_TRANSFER_COMMITTED: + return "committed"; + case AUDIO_RESPONSE_TRANSFER_ABORTED: + return "aborted"; + case AUDIO_RESPONSE_TRANSFER_INVALID_STATE: + return "invalid_state"; + case AUDIO_RESPONSE_TRANSFER_INVALID_CHUNK: + return "invalid_chunk"; + case AUDIO_RESPONSE_TRANSFER_INSUFFICIENT_STORAGE: + return "insufficient_storage"; + case AUDIO_RESPONSE_TRANSFER_CHECKSUM_MISMATCH: + return "checksum_mismatch"; + case AUDIO_RESPONSE_TRANSFER_TIMED_OUT: + return "timed_out"; + default: + return "unknown"; + } +} + +/** + * Return a readable transfer-control command name for logs. + */ +static const char *transfer_control_type_name(audio_response_transfer_control_type_t type) +{ + switch (type) { + case AUDIO_RESPONSE_TRANSFER_CONTROL_START: + return "start"; + case AUDIO_RESPONSE_TRANSFER_CONTROL_COMMIT: + return "commit"; + case AUDIO_RESPONSE_TRANSFER_CONTROL_ABORT: + return "abort"; + default: + return "unknown"; + } +} + +/** + * Return the nearest analysis bin for a requested response frequency. + */ +static uint32_t response_frequency_to_bin(uint16_t frequency) +{ + return ((uint32_t)frequency * AUDIO_RESPONSE_CAPTURE_SAMPLES + + AUDIO_RESPONSE_CAPTURE_SAMPLE_RATE / 2) / + AUDIO_RESPONSE_CAPTURE_SAMPLE_RATE; +} + +/** + * Validate that a requested response frequency maps to a captured analysis bin. + */ +static bool is_valid_response_frequency(uint16_t frequency) +{ + return response_frequency_to_bin(frequency) < (AUDIO_RESPONSE_CAPTURE_SAMPLES / 2); +} + +/** + * Estimate a single analysis-bin magnitude using the Goertzel recurrence. + * + * The returned value is normalized into the same unsigned Q15-like range used by + * the protocol result. Non-DC bins are scaled by 2 / N so a full-scale sine near + * the target bin reports close to INT16_MAX. + */ +static uint16_t response_magnitude_for_bin(const int16_t *samples, size_t sample_count, + uint32_t bin) +{ + const float omega = AUDIO_RESPONSE_TWO_PI * (float)bin / (float)sample_count; + const float coefficient = 2.0f * cosf(omega); + float previous = 0.0f; + float previous2 = 0.0f; + + for (size_t index = 0; index < sample_count; ++index) { + const float current = (float)samples[index] + coefficient * previous - previous2; + + previous2 = previous; + previous = current; + } + + float power = previous2 * previous2 + previous * previous - + coefficient * previous * previous2; + + if (power < 0.0f) { + power = 0.0f; + } + + const float scale = (bin == 0U) ? (1.0f / (float)sample_count) : + (2.0f / (float)sample_count); + const float magnitude = sqrtf(power) * scale; + + if (magnitude >= (float)INT16_MAX) { + return INT16_MAX; + } + + return (uint16_t)(magnitude + 0.5f); +} + +/** + * Copy the requested response points into service-owned storage. + * + * A zero-point request selects the firmware default table. Nonzero requests + * use the protocol-provided frequency list after validating that every + * frequency maps to the captured response range. + */ +static int select_response_points(audio_response_config_t *config) +{ + const uint16_t *frequencies = config->frequencies; + uint8_t points = config->points; + + if (points == 0) { + points = ARRAY_SIZE(default_frequencies); + frequencies = default_frequencies; + LOG_DBG("Using default audio response points: points=%u", points); + } else if (points > CONFIG_AUDIO_RESPONSE_MAX_POINTS) { + LOG_WRN("Audio response config rejected: points=%u max_points=%u", points, + CONFIG_AUDIO_RESPONSE_MAX_POINTS); + return -EINVAL; + } + + for (size_t index = 0; index < points; ++index) { + if (!is_valid_response_frequency(frequencies[index])) { + LOG_WRN("Audio response config rejected: frequency=%u index=%u capture_rate=%u", + frequencies[index], index, AUDIO_RESPONSE_CAPTURE_SAMPLE_RATE); + return -EINVAL; + } + requested_frequencies[index] = frequencies[index]; + } + + config->points = points; + config->frequencies = requested_frequencies; + return 0; +} + +/** + * Clear all transfer metadata and make the upload buffer reusable. + */ +static void reset_transfer(void) +{ + LOG_DBG("Reset transfer state: id=%u active=%d committed=%d received=%u/%u", transfer.id, + transfer.active, transfer.committed, transfer.received_samples, transfer.total_samples); + k_work_cancel_delayable(&transfer_ready_work); + memset(&transfer, 0, sizeof(transfer)); +} + +/** + * Notify connected clients about the current upload state. + */ +static int notify_transfer_status(enum audio_response_transfer_status status, uint16_t credits) +{ + audio_response_transfer_status_t message = { + .transfer_id = transfer.id, + .status = status, + .next_sample_offset = transfer.received_samples, + .credits = credits, + }; + uint8_t payload[AUDIO_RESPONSE_STATUS_ENCODED_SIZE]; + size_t payload_size; + + if (!transfer_status_notifications_enabled) { + LOG_DBG("Skipping transfer status notification: status=%s id=%u notifications=disabled", + transfer_status_name(status), transfer.id); + return -EACCES; + } + + if (audio_response_transfer_status_encode(&message, payload, sizeof(payload), &payload_size) != + PROTOCOL_OK) { + LOG_ERR("Failed to encode transfer status: status=%s id=%u offset=%u credits=%u", + transfer_status_name(status), transfer.id, transfer.received_samples, credits); + return -EMSGSIZE; + } + + int ret = bt_gatt_notify(NULL, &audio_response_svc.attrs[6], payload, payload_size); + if (ret != 0) { + LOG_WRN("Failed to notify transfer status: status=%s id=%u err=%d", + transfer_status_name(status), transfer.id, ret); + return ret; + } + + LOG_DBG("Notified transfer status: status=%s id=%u offset=%u credits=%u bytes=%u", + transfer_status_name(status), transfer.id, transfer.received_samples, credits, + payload_size); + return 0; +} + +/** + * Reject a transfer operation and report the supplied protocol status. + */ +static ssize_t reject_transfer(enum audio_response_transfer_status status, ssize_t att_error) +{ + LOG_WRN("Rejecting transfer operation: status=%s id=%u active=%d committed=%d received=%u/%u att_error=%zd", + transfer_status_name(status), transfer.id, transfer.active, transfer.committed, + transfer.received_samples, transfer.total_samples, att_error); + (void)notify_transfer_status(status, 0); + return att_error; +} + +/** + * Start a new audio-buffer upload. + */ +static ssize_t start_transfer(const audio_response_transfer_start_t *start, uint16_t write_len) +{ + LOG_INF("Transfer start requested: id=%u samples=%u rate=%u checksum=0x%08x", + start->transfer_id, start->total_samples, start->sampling_rate, start->checksum); + + if (measurement_active || (transfer.active && !transfer.committed) || + start->total_samples == 0 || + start->sampling_rate != CONFIG_AUDIO_SAMPLE_RATE_HZ) { + LOG_WRN("Transfer start rejected: measurement_active=%d active=%d committed=%d samples=%u rate=%u expected_rate=%u", + measurement_active, transfer.active, transfer.committed, start->total_samples, + start->sampling_rate, CONFIG_AUDIO_SAMPLE_RATE_HZ); + return reject_transfer(AUDIO_RESPONSE_TRANSFER_INVALID_STATE, + BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED)); + } + + if (start->total_samples > CONFIG_AUDIO_RESPONSE_MAX_SAMPLES) { + transfer.id = start->transfer_id; + LOG_WRN("Transfer start rejected: samples=%u max_samples=%u", start->total_samples, + CONFIG_AUDIO_RESPONSE_MAX_SAMPLES); + return reject_transfer(AUDIO_RESPONSE_TRANSFER_INSUFFICIENT_STORAGE, + BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES)); + } + + reset_transfer(); + transfer.id = start->transfer_id; + transfer.total_samples = start->total_samples; + transfer.sampling_rate = start->sampling_rate; + transfer.expected_checksum = start->checksum; + transfer.samples = uploaded_samples; + transfer.active = true; + k_work_reschedule(&transfer_timeout_work, AUDIO_RESPONSE_TRANSFER_TIMEOUT); + (void)notify_transfer_status(AUDIO_RESPONSE_TRANSFER_READY, AUDIO_RESPONSE_TRANSFER_CREDITS); + + LOG_INF("Transfer started: id=%u samples=%u timeout_ms=%lld", transfer.id, + transfer.total_samples, k_ticks_to_ms_floor64(AUDIO_RESPONSE_TRANSFER_TIMEOUT.ticks)); + return write_len; +} + +/** + * Validate and retain a completely uploaded audio buffer. + */ +static ssize_t commit_transfer(const audio_response_transfer_commit_t *commit, uint16_t write_len) +{ + LOG_INF("Transfer commit requested: id=%u received=%u/%u", commit->transfer_id, + transfer.received_samples, transfer.total_samples); + + if (!transfer.active || transfer.committed || commit->transfer_id != transfer.id || + transfer.received_samples != transfer.total_samples) { + LOG_WRN("Transfer commit rejected: requested_id=%u current_id=%u active=%d committed=%d received=%u/%u", + commit->transfer_id, transfer.id, transfer.active, transfer.committed, + transfer.received_samples, transfer.total_samples); + return reject_transfer(AUDIO_RESPONSE_TRANSFER_INVALID_STATE, + BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED)); + } + + uint32_t checksum = + crc32_ieee((const uint8_t *)transfer.samples, transfer.total_samples * sizeof(int16_t)); + if (checksum != transfer.expected_checksum) { + LOG_WRN("Transfer checksum mismatch: id=%u expected=0x%08x actual=0x%08x", + transfer.id, transfer.expected_checksum, checksum); + (void)notify_transfer_status(AUDIO_RESPONSE_TRANSFER_CHECKSUM_MISMATCH, 0); + reset_transfer(); + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + transfer.committed = true; + k_work_cancel_delayable(&transfer_timeout_work); + (void)notify_transfer_status(AUDIO_RESPONSE_TRANSFER_COMMITTED, 0); + LOG_INF("Transfer committed: id=%u samples=%u checksum=0x%08x", transfer.id, + transfer.total_samples, checksum); + return write_len; +} + +/** + * Abort the active or committed upload. + */ +static ssize_t abort_transfer(const audio_response_transfer_abort_t *abort, uint16_t write_len) +{ + LOG_INF("Transfer abort requested: id=%u", abort->transfer_id); + + if ((!transfer.active && !transfer.committed) || abort->transfer_id != transfer.id || + measurement_active) { + LOG_WRN("Transfer abort rejected: requested_id=%u current_id=%u active=%d committed=%d measurement_active=%d", + abort->transfer_id, transfer.id, transfer.active, transfer.committed, + measurement_active); + return reject_transfer(AUDIO_RESPONSE_TRANSFER_INVALID_STATE, + BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED)); + } + + (void)notify_transfer_status(AUDIO_RESPONSE_TRANSFER_ABORTED, 0); + k_work_cancel_delayable(&transfer_timeout_work); + LOG_INF("Transfer aborted: id=%u received=%u/%u", transfer.id, transfer.received_samples, + transfer.total_samples); + reset_transfer(); + return write_len; +} + +/** + * Handle a typed transfer-start command dispatched by the generated protocol API. + */ +static protocol_status_t dispatch_transfer_start(void *context, + const audio_response_transfer_start_t *start) +{ + struct transfer_control_dispatch_context *dispatch_context = context; + + dispatch_context->result = start_transfer(start, dispatch_context->write_len); + return PROTOCOL_OK; +} + +/** + * Handle a typed transfer-commit command dispatched by the generated protocol API. + */ +static protocol_status_t dispatch_transfer_commit(void *context, + const audio_response_transfer_commit_t *commit) +{ + struct transfer_control_dispatch_context *dispatch_context = context; + + dispatch_context->result = commit_transfer(commit, dispatch_context->write_len); + return PROTOCOL_OK; +} + +/** + * Handle a typed transfer-abort command dispatched by the generated protocol API. + */ +static protocol_status_t dispatch_transfer_abort(void *context, + const audio_response_transfer_abort_t *abort) +{ + struct transfer_control_dispatch_context *dispatch_context = context; + + dispatch_context->result = abort_transfer(abort, dispatch_context->write_len); + return PROTOCOL_OK; +} + +/** + * Decode and execute a transfer-control command. + */ +static ssize_t write_transfer_control(struct bt_conn *conn, const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, uint16_t offset, uint8_t flags) +{ + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(flags); + static const audio_response_transfer_control_handler_t handlers = { + .start = dispatch_transfer_start, + .commit = dispatch_transfer_commit, + .abort = dispatch_transfer_abort, + }; + audio_response_transfer_control_t control; + struct transfer_control_dispatch_context dispatch_context = { + .write_len = len, + .result = BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED), + }; + size_t bytes_read = 0; + protocol_status_t status; + + if (offset != 0) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + status = audio_response_transfer_control_decode(&control, buf, len, &bytes_read); + if (status != PROTOCOL_OK || bytes_read != len) { + LOG_ERR("Failed to decode transfer control command: status=%d bytes_read=%u len=%u", + status, bytes_read, len); + return reject_transfer(AUDIO_RESPONSE_TRANSFER_INVALID_STATE, + BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN)); + } + + LOG_DBG("Decoded transfer control command: type=%s len=%u", + transfer_control_type_name(control.type), len); + + k_mutex_lock(&service_mutex, K_FOREVER); + status = audio_response_transfer_control_dispatch(&control, &handlers, &dispatch_context); + k_mutex_unlock(&service_mutex); + + if (status != PROTOCOL_OK) { + LOG_ERR("Failed to dispatch transfer control command: type=%s status=%d", + transfer_control_type_name(control.type), status); + return reject_transfer(AUDIO_RESPONSE_TRANSFER_INVALID_STATE, + BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED)); + } + LOG_DBG("Handled transfer control command: type=%s result=%zd", + transfer_control_type_name(control.type), dispatch_context.result); + return dispatch_context.result; +} + +/** + * Decode and append one contiguous chunk to the active transfer. + */ +static ssize_t write_transfer_data(struct bt_conn *conn, const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, uint16_t offset, uint8_t flags) +{ + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(flags); + protocol_reader_t reader = { .buffer = buf, .size = len, .offset = 0 }; + uint16_t transfer_id = 0; + uint32_t sample_offset = 0; + uint16_t sample_count = 0; + + if (offset != 0) { + LOG_WRN("Transfer chunk rejected: invalid offset=%u len=%u", offset, len); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + if (protocol_read_uint16(&reader, &transfer_id) != PROTOCOL_OK || + protocol_read_uint32(&reader, &sample_offset) != PROTOCOL_OK || + protocol_read_uint16(&reader, &sample_count) != PROTOCOL_OK || + reader.size - reader.offset != sample_count * sizeof(int16_t)) { + LOG_WRN("Transfer chunk decode failed: len=%u bytes_remaining=%u sample_count=%u", + len, reader.size - reader.offset, sample_count); + return reject_transfer(AUDIO_RESPONSE_TRANSFER_INVALID_CHUNK, + BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN)); + } + + LOG_DBG("Transfer chunk received: id=%u offset=%u samples=%u len=%u", transfer_id, + sample_offset, sample_count, len); + + k_mutex_lock(&service_mutex, K_FOREVER); + if (!transfer.active || transfer.committed || transfer_id != transfer.id || + sample_offset != transfer.received_samples || sample_count == 0 || + sample_count > transfer.total_samples - transfer.received_samples) { + LOG_WRN("Transfer chunk rejected: requested_id=%u current_id=%u offset=%u expected_offset=%u samples=%u remaining=%u active=%d committed=%d", + transfer_id, transfer.id, sample_offset, transfer.received_samples, + sample_count, transfer.total_samples - transfer.received_samples, transfer.active, + transfer.committed); + k_mutex_unlock(&service_mutex); + return reject_transfer(AUDIO_RESPONSE_TRANSFER_INVALID_CHUNK, + BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED)); + } + + for (size_t index = 0; index < sample_count; ++index) { + if (protocol_read_int16(&reader, &transfer.samples[transfer.received_samples + index]) != + PROTOCOL_OK) { + LOG_WRN("Transfer chunk sample decode failed: id=%u offset=%u sample_index=%u", + transfer_id, sample_offset, index); + k_mutex_unlock(&service_mutex); + return reject_transfer(AUDIO_RESPONSE_TRANSFER_INVALID_CHUNK, + BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN)); + } + } + transfer.received_samples += sample_count; + k_work_reschedule(&transfer_timeout_work, AUDIO_RESPONSE_TRANSFER_TIMEOUT); + k_work_reschedule(&transfer_ready_work, AUDIO_RESPONSE_TRANSFER_CREDIT_DELAY); + if (transfer.received_samples == transfer.total_samples) { + LOG_INF("Transfer upload complete: id=%u samples=%u", transfer.id, + transfer.total_samples); + } else { + LOG_DBG("Transfer chunk accepted: id=%u received=%u/%u", transfer.id, + transfer.received_samples, transfer.total_samples); + } + k_mutex_unlock(&service_mutex); + return len; +} + +/** + * Decode a measurement request and queue it outside the Bluetooth callback. + */ +static ssize_t write_audio_response_config(struct bt_conn *conn, const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, uint16_t offset, + uint8_t flags) +{ + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(flags); + audio_response_config_t config; + size_t bytes_read = 0; + int ret; + + if (offset != 0) { + LOG_WRN("Audio response config rejected: invalid offset=%u len=%u", offset, len); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + config.frequencies = decoded_config_frequencies; + if (audio_response_config_decode(&config, buf, len, &bytes_read) != PROTOCOL_OK || + bytes_read != len) { + LOG_WRN("Audio response config decode failed: bytes_read=%u len=%u", bytes_read, len); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + LOG_INF("Audio response config requested: id=%u transfer_id=%u volume=%.2f points=%u", + config.id, config.transfer_id, (double)config.volume, config.points); + if (!isfinite(config.volume) || config.volume < 0.0f || config.volume > 1.0f) { + LOG_WRN("Audio response config rejected: invalid volume=%.3f", + (double)config.volume); + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + ret = select_response_points(&config); + if (ret != 0) { + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + k_mutex_lock(&service_mutex, K_FOREVER); + if (!transfer.committed || config.transfer_id != transfer.id || measurement_active) { + LOG_WRN("Audio response config rejected: requested_transfer_id=%u current_id=%u committed=%d measurement_active=%d", + config.transfer_id, transfer.id, transfer.committed, measurement_active); + k_mutex_unlock(&service_mutex); + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + pending_config = config; + measurement_active = true; + k_mutex_unlock(&service_mutex); + + k_work_submit(&measurement_work); + LOG_INF("Audio response measurement queued: id=%u transfer_id=%u points=%u", + pending_config.id, pending_config.transfer_id, pending_config.points); + return len; +} + +/** + * Track whether transfer-status notifications are enabled. + */ +static void transfer_status_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value) +{ + ARG_UNUSED(attr); + transfer_status_notifications_enabled = value == BT_GATT_CCC_NOTIFY; + LOG_DBG("Audio response transfer status notifications %s", + transfer_status_notifications_enabled ? "enabled" : "disabled"); +} + +/** + * Track whether audio-response result notifications are enabled. + */ +static void result_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value) +{ + ARG_UNUSED(attr); + result_notifications_enabled = value == BT_GATT_CCC_NOTIFY; + LOG_DBG("Audio response result notifications %s", + result_notifications_enabled ? "enabled" : "disabled"); +} + +BT_GATT_SERVICE_DEFINE(audio_response_svc, + BT_GATT_PRIMARY_SERVICE(AUDIO_RESPONSE_ZEPHYR_SERVICE_UUID), + BT_GATT_CHARACTERISTIC(AUDIO_RESPONSE_ZEPHYR_TRANSFER_CONTROL_CHARACTERISTIC_UUID, + AUDIO_RESPONSE_ZEPHYR_TRANSFER_CONTROL_CHARACTERISTIC_PROPERTIES, + AUDIO_RESPONSE_ZEPHYR_TRANSFER_CONTROL_CHARACTERISTIC_PERMISSIONS, NULL, + write_transfer_control, NULL), + BT_GATT_CHARACTERISTIC(AUDIO_RESPONSE_ZEPHYR_TRANSFER_DATA_CHARACTERISTIC_UUID, + AUDIO_RESPONSE_ZEPHYR_TRANSFER_DATA_CHARACTERISTIC_PROPERTIES, + AUDIO_RESPONSE_ZEPHYR_TRANSFER_DATA_CHARACTERISTIC_PERMISSIONS, NULL, + write_transfer_data, NULL), + BT_GATT_CHARACTERISTIC(AUDIO_RESPONSE_ZEPHYR_TRANSFER_STATUS_CHARACTERISTIC_UUID, + AUDIO_RESPONSE_ZEPHYR_TRANSFER_STATUS_CHARACTERISTIC_PROPERTIES, + AUDIO_RESPONSE_ZEPHYR_TRANSFER_STATUS_CHARACTERISTIC_PERMISSIONS, NULL, + NULL, NULL), + BT_GATT_CCC(transfer_status_ccc_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), + BT_GATT_CHARACTERISTIC(AUDIO_RESPONSE_ZEPHYR_CONFIG_CHARACTERISTIC_UUID, + AUDIO_RESPONSE_ZEPHYR_CONFIG_CHARACTERISTIC_PROPERTIES, + AUDIO_RESPONSE_ZEPHYR_CONFIG_CHARACTERISTIC_PERMISSIONS, NULL, + write_audio_response_config, NULL), + BT_GATT_CHARACTERISTIC(AUDIO_RESPONSE_ZEPHYR_RESULT_CHARACTERISTIC_UUID, + AUDIO_RESPONSE_ZEPHYR_RESULT_CHARACTERISTIC_PROPERTIES, + AUDIO_RESPONSE_ZEPHYR_RESULT_CHARACTERISTIC_PERMISSIONS, NULL, NULL, + NULL), + BT_GATT_CCC(result_ccc_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), +); + +/** + * Stop shared audio activity and retain enough state to restore it after the + * response measurement owns the datapath. + */ +static int suspend_audio_for_measurement(void) +{ + int ret; + + memset(&audio_session, 0, sizeof(audio_session)); + ret = audio_system_suspend(); + if (ret != 0) { + LOG_ERR("Failed to suspend audio system: %d", ret); + return ret; + } + audio_session.audio_system_suspended = true; + + ret = audio_datapath_auxiliary_suspend(); + if (ret != 0) { + LOG_ERR("Failed to suspend auxiliary audio activity: %d", ret); + (void)audio_system_resume(); + memset(&audio_session, 0, sizeof(audio_session)); + return ret; + } + LOG_DBG("Auxiliary audio activity suspended for measurement"); + + audio_session.auxiliary_audio_suspended = true; + return 0; +} + +/** + * Stop measurement-specific activity and restore the audio state captured by + * suspend_audio_for_measurement(). Safe to call after partial setup. + */ +static void restore_audio_after_measurement(void) +{ + int ret; + + record_to_buffer_stop(); + if (audio_session.measurement_playback_started) { + audio_datapath_buffer_stop(); + } + if (audio_session.measurement_codec_enabled) { + ret = hw_codec_stop_audio(); + if (ret != 0) { + LOG_ERR("Failed to stop measurement codec: %d", ret); + } + } + if (audio_session.datapath_acquired) { + ret = audio_datapath_release(); + if (ret != 0) { + LOG_ERR("Failed to release measurement datapath: %d", ret); + } + } + if (audio_session.auxiliary_audio_suspended) { + ret = audio_datapath_auxiliary_resume(); + if (ret != 0) { + LOG_ERR("Failed to restore auxiliary audio activity: %d", ret); + } + } + if (audio_session.audio_system_suspended) { + ret = audio_system_resume(); + if (ret != 0) { + LOG_ERR("Failed to resume audio system: %d", ret); + } + } + memset(&audio_session, 0, sizeof(audio_session)); +} + +/** + * Run the audio playback and capture setup from the system work queue. + */ +static void measurement_work_handler(struct k_work *work) +{ + ARG_UNUSED(work); + int ret; + + LOG_INF("Starting audio response measurement: id=%u transfer_id=%u samples=%u volume=%.2f points=%u", + pending_config.id, pending_config.transfer_id, transfer.total_samples, + (double)pending_config.volume, pending_config.points); + + ret = suspend_audio_for_measurement(); + if (ret != 0) { + goto fail; + } + LOG_DBG("Audio system suspended for audio response measurement: id=%u", pending_config.id); + + if (!fifo_rx.initialized) { + LOG_DBG("Initializing RX FIFO for audio response measurement"); + ret = data_fifo_init(&fifo_rx); + if (ret != 0) { + LOG_ERR("Failed to initialize RX FIFO: %d", ret); + goto fail; + } + } + LOG_DBG("RX FIFO ready for audio response measurement: id=%u", pending_config.id); + + ret = audio_datapath_decimator_init(CONFIG_AUDIO_SAMPLE_RATE_HZ / + AUDIO_RESPONSE_CAPTURE_SAMPLE_RATE); + if (ret != 0) { + LOG_ERR("Failed to initialize audio response decimator: %d", ret); + goto fail; + } + LOG_DBG("Audio response decimator initialized: input_rate=%u output_rate=%u", + CONFIG_AUDIO_SAMPLE_RATE_HZ, AUDIO_RESPONSE_CAPTURE_SAMPLE_RATE); + ret = audio_datapath_aquire(&fifo_rx); + if (ret != 0) { + LOG_ERR("Failed to acquire audio datapath: %d", ret); + goto fail; + } + LOG_DBG("Audio datapath acquired: id=%u", pending_config.id); + audio_session.datapath_acquired = true; + ret = hw_codec_default_conf_enable(); + if (ret != 0) { + LOG_ERR("Failed to enable codec for audio response measurement: %d", ret); + goto fail; + } + audio_session.measurement_codec_enabled = true; + LOG_DBG("Codec enabled for audio response measurement: id=%u", pending_config.id); + ret = hw_codec_volume_unmute(); + if (ret != 0) { + LOG_ERR("Failed to unmute codec for audio response measurement: %d", ret); + goto fail; + } + ret = audio_datapath_buffer_play(transfer.samples, transfer.total_samples, false, + pending_config.volume, NULL); + if (ret != 0) { + LOG_ERR("Failed to start audio response playback: %d", ret); + goto fail; + } + audio_session.measurement_playback_started = true; + + LOG_INF("Audio response playback started: id=%u capture_samples=%u capture_rate=%u initial_drop=%u", + pending_config.id, AUDIO_RESPONSE_CAPTURE_SAMPLES, + AUDIO_RESPONSE_CAPTURE_SAMPLE_RATE, AUDIO_RESPONSE_INITIAL_DROP); + record_to_buffer(captured_samples, AUDIO_RESPONSE_CAPTURE_SAMPLES, + AUDIO_RESPONSE_INITIAL_DROP, false, true, audio_response_capture_complete); + return; + +fail: + LOG_ERR("Failed to start audio response measurement: %d", ret); + restore_audio_after_measurement(); + k_mutex_lock(&service_mutex, K_FOREVER); + measurement_active = false; + k_mutex_unlock(&service_mutex); +} + +/** + * Encode and notify the measured response at the configured target frequencies. + */ +static void notify_result(void) +{ + audio_response_result_t result = { + .id = pending_config.id, + .points = pending_config.points, + .frequencies = result_frequencies, + .response = result_response, + }; + size_t payload_size; + + if (!result_notifications_enabled) { + LOG_WRN("Audio response result notifications are not enabled: id=%u", + pending_config.id); + return; + } + if (audio_response_result_encode(&result, result_payload, sizeof(result_payload), &payload_size) != + PROTOCOL_OK) { + LOG_ERR("Failed to encode audio response result: id=%u points=%u", result.id, + result.points); + return; + } + + int ret = bt_gatt_notify(NULL, &audio_response_svc.attrs[11], result_payload, payload_size); + if (ret != 0) { + LOG_ERR("Failed to notify audio response result: id=%u err=%d", result.id, ret); + return; + } + LOG_INF("Audio response result notified: id=%u points=%u bytes=%u", result.id, + result.points, payload_size); +} + +/** + * Compute the captured signal's response at the configured target frequencies. + */ +static void measurement_complete_work_handler(struct k_work *work) +{ + ARG_UNUSED(work); + LOG_INF("Audio response capture complete: id=%u", pending_config.id); + restore_audio_after_measurement(); + + for (size_t index = 0; index < pending_config.points; ++index) { + uint32_t bin = response_frequency_to_bin(pending_config.frequencies[index]); + + result_frequencies[index] = pending_config.frequencies[index]; + result_response[index] = response_magnitude_for_bin( + captured_samples, ARRAY_SIZE(captured_samples), bin); + } + LOG_DBG("Audio response analysis complete: id=%u bins=%u result_points=%u", + pending_config.id, + AUDIO_RESPONSE_CAPTURE_SAMPLES / 2, pending_config.points); + notify_result(); + + k_mutex_lock(&service_mutex, K_FOREVER); + measurement_active = false; + k_mutex_unlock(&service_mutex); + LOG_DBG("Audio response measurement complete: id=%u", pending_config.id); +} + +/** + * Grant the next upload credit after the current GATT write has released its + * incoming ACL buffer. + */ +static void transfer_ready_work_handler(struct k_work *work) +{ + ARG_UNUSED(work); + k_mutex_lock(&service_mutex, K_FOREVER); + if (transfer.active && !transfer.committed) { + (void)notify_transfer_status(AUDIO_RESPONSE_TRANSFER_READY, + AUDIO_RESPONSE_TRANSFER_CREDITS); + } + k_mutex_unlock(&service_mutex); +} + +/** + * Release an incomplete transfer after the protocol timeout. + */ +static void transfer_timeout_work_handler(struct k_work *work) +{ + ARG_UNUSED(work); + k_mutex_lock(&service_mutex, K_FOREVER); + if (transfer.active && !transfer.committed) { + LOG_WRN("Transfer timed out: id=%u received=%u/%u", transfer.id, + transfer.received_samples, transfer.total_samples); + (void)notify_transfer_status(AUDIO_RESPONSE_TRANSFER_TIMED_OUT, 0); + reset_transfer(); + } + k_mutex_unlock(&service_mutex); +} + +void audio_response_capture_complete(void) +{ + LOG_DBG("Audio response capture completion callback"); + k_work_submit(&measurement_complete_work); +} + +int init_audio_response_service(void) +{ + LOG_INF("Initializing audio response service"); + k_work_init(&measurement_work, measurement_work_handler); + k_work_init(&measurement_complete_work, measurement_complete_work_handler); + k_work_init_delayable(&transfer_ready_work, transfer_ready_work_handler); + k_work_init_delayable(&transfer_timeout_work, transfer_timeout_work_handler); + return 0; +} diff --git a/src/bluetooth/gatt_services/audio_response_service.h b/src/bluetooth/gatt_services/audio_response_service.h new file mode 100644 index 00000000..948f6da7 --- /dev/null +++ b/src/bluetooth/gatt_services/audio_response_service.h @@ -0,0 +1,24 @@ +#ifndef _AUDIO_RESPONSE_SERVICE_H_ +#define _AUDIO_RESPONSE_SERVICE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Initialize the audio response service's asynchronous work items. + * + * @return 0 on success. + */ +int init_audio_response_service(void); + +/** + * Signal that the microphone capture for an audio response measurement completed. + */ +void audio_response_capture_complete(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _AUDIO_RESPONSE_SERVICE_H_ */ diff --git a/src/bluetooth/gatt_services/battery_service.cpp b/src/bluetooth/gatt_services/battery_service.cpp index 71e0aa5a..cdad8beb 100644 --- a/src/bluetooth/gatt_services/battery_service.cpp +++ b/src/bluetooth/gatt_services/battery_service.cpp @@ -75,6 +75,7 @@ struct battery_health_status health_status; static void battery_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { + ARG_UNUSED(attr); notify_enabled = (value == BT_GATT_CCC_NOTIFY); #ifdef CONFIG_LOG if (notify_enabled) LOG_INF("subscribe to battery level"); @@ -158,6 +159,7 @@ BT_GATT_CHARACTERISTIC(BT_UUID_BAS_BATTERY_HEALTH_STATUS, int bt_send_battery_level(struct battery_data * data) { + ARG_UNUSED(data); if (!notify_enabled) { LOG_WRN("battery level not subscribed"); return -EACCES; @@ -166,4 +168,4 @@ int bt_send_battery_level(struct battery_data * data) LOG_INF("notify battery level change"); return bt_gatt_notify(NULL, &battery_service.attrs[2], &msg.battery_level, sizeof(msg.battery_level)); -} \ No newline at end of file +} diff --git a/src/bluetooth/gatt_services/button_service.c b/src/bluetooth/gatt_services/button_service.c index 6399cf4f..8addf53b 100644 --- a/src/bluetooth/gatt_services/button_service.c +++ b/src/bluetooth/gatt_services/button_service.c @@ -23,6 +23,7 @@ static K_THREAD_STACK_DEFINE(thread_stack, CONFIG_BUTTON_MSG_SUB_STACK_SIZE); static void button_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { + ARG_UNUSED(attr); notify_enabled = (value == BT_GATT_CCC_NOTIFY); } @@ -103,4 +104,4 @@ int init_button_service() { } return 0; -} \ No newline at end of file +} diff --git a/src/bluetooth/gatt_services/device_info.c b/src/bluetooth/gatt_services/device_info.c index 3cbb548a..13c4438b 100644 --- a/src/bluetooth/gatt_services/device_info.c +++ b/src/bluetooth/gatt_services/device_info.c @@ -1,4 +1,6 @@ #include "device_info.h" +#include +#include #include #include @@ -17,7 +19,8 @@ static ssize_t read_device_identifier(struct bt_conn *conn, uint16_t len, uint16_t offset) { - snprintf(device_identifier, sizeof(device_identifier), "0x%08X", oe_boot_state.device_id); + snprintf(device_identifier, sizeof(device_identifier), "0x%08X", + (unsigned int)oe_boot_state.device_id); return bt_gatt_attr_read(conn, attr, buf, len, offset, device_identifier, sizeof(device_identifier)); @@ -59,4 +62,4 @@ BT_GATT_CHARACTERISTIC(BT_UUID_FIRMWARE, BT_GATT_CHRC_READ, BT_GATT_PERM_READ, read_firmware, NULL, firmware), -); \ No newline at end of file +); diff --git a/src/bluetooth/gatt_services/led_service.cpp b/src/bluetooth/gatt_services/led_service.cpp index 26de1418..f12a82f1 100644 --- a/src/bluetooth/gatt_services/led_service.cpp +++ b/src/bluetooth/gatt_services/led_service.cpp @@ -10,6 +10,9 @@ static ssize_t write_led(struct bt_conn *conn, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(flags); if (len != 3U) { LOG_INF("Write led: Incorrect data length"); return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); @@ -30,9 +33,9 @@ static ssize_t write_state(struct bt_conn *conn, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { - //printk("Attribute write, handle: %u, conn: %p", attr->handle, - //(void *)conn); - + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(flags); if (len != 1U) { LOG_INF("Write led: Incorrect data length"); return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); diff --git a/src/bluetooth/gatt_services/sensor_service.c b/src/bluetooth/gatt_services/sensor_service.c index 3d821d14..c358ceff 100644 --- a/src/bluetooth/gatt_services/sensor_service.c +++ b/src/bluetooth/gatt_services/sensor_service.c @@ -170,12 +170,17 @@ static void connect_evt_handler(const struct zbus_channel *chan) case BT_MGMT_DISCONNECTED: reset_sensor_notification_state(); break; + + default: + /* Other events do not affect sensor notification state. */ + break; } } static void sensor_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { + ARG_UNUSED(attr); k_spinlock_key_t key = k_spin_lock(¬ify_state_lock); notify_enabled = (value == BT_GATT_CCC_NOTIFY); @@ -189,6 +194,7 @@ static void sensor_ccc_cfg_changed(const struct bt_gatt_attr *attr, static void sensor_config_status_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { + ARG_UNUSED(attr); k_spinlock_key_t key = k_spin_lock(¬ify_state_lock); sensor_config_status_ntfy_enabled = (value == BT_GATT_CCC_NOTIFY); k_spin_unlock(¬ify_state_lock, key); @@ -199,6 +205,7 @@ static ssize_t write_config(struct bt_conn *conn, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { + ARG_UNUSED(flags); LOG_DBG("Attribute write, handle: %u, conn: %p", attr->handle, (void *)conn); if (len != sizeof(struct sensor_config)) { @@ -211,12 +218,12 @@ static ssize_t write_config(struct bt_conn *conn, return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); } - struct sensor_config * config = (struct sensor_config *)buf; +struct sensor_config * sensor_configuration = (struct sensor_config *)buf; - if (config->storageOptions == 0) { - LOG_INF("Setup sensor ID %i (turned off)", config->sensorId); + if (sensor_configuration->storageOptions == 0) { + LOG_INF("Setup sensor ID %i (turned off)", sensor_configuration->sensorId); } else { - LOG_INF("Setup sensor ID %i with samplerateIndex %i", config->sensorId, config->sampleRateIndex); + LOG_INF("Setup sensor ID %i with samplerateIndex %i", sensor_configuration->sensorId, sensor_configuration->sampleRateIndex); } //stop_sensor_manager(); @@ -246,6 +253,8 @@ static ssize_t write_sensor_rec_name(struct bt_conn *conn, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { + ARG_UNUSED(offset); + ARG_UNUSED(flags); LOG_DBG("Attribute write, len: %u, handle: %u, conn: %p", len, attr->handle, (void *)conn); if (len > MAX_SENSOR_REC_NAME_LENGTH - 1) { LOG_WRN("Write sensor recording name: Data length exceeds maximum allowed length of %i", MAX_SENSOR_REC_NAME_LENGTH - 1); @@ -405,6 +414,7 @@ static void notification_task(void) { } void sensor_queue_listener_cb(const struct zbus_channel *chan) { + ARG_UNUSED(chan); int ret; const struct sensor_msg * msg; @@ -447,19 +457,19 @@ int init_sensor_config_status() { return 0; } -int set_sensor_config_status(struct sensor_config config) { - LOG_DBG("Setting sensor config status for sensorId: %i", config.sensorId); +int set_sensor_config_status(struct sensor_config sensor_configuration) { + LOG_DBG("Setting sensor config status for sensorId: %i", sensor_configuration.sensorId); ssize_t sensor_config_index = -1; for (size_t i = 0; i < active_sensor_configs_size; i++) { - if (active_sensor_configs[i].sensorId == config.sensorId) { + if (active_sensor_configs[i].sensorId == sensor_configuration.sensorId) { sensor_config_index = i; break; } } if (sensor_config_index >= 0) { - active_sensor_configs[sensor_config_index] = config; + active_sensor_configs[sensor_config_index] = sensor_configuration; LOG_DBG("Found sensor config"); } else { LOG_DBG("Sensor config not found, adding new sensor config"); @@ -471,7 +481,7 @@ int set_sensor_config_status(struct sensor_config config) { return -1; } active_sensor_configs = new_active_sensor_configs; - active_sensor_configs[active_sensor_configs_size - 1] = config; + active_sensor_configs[active_sensor_configs_size - 1] = sensor_configuration; } if (sensor_config_status_ntfy_enabled) { diff --git a/src/buttons/Button.cpp b/src/buttons/Button.cpp index 6e990342..51744664 100644 --- a/src/buttons/Button.cpp +++ b/src/buttons/Button.cpp @@ -15,13 +15,14 @@ struct gpio_callback Button::button_cb_data; void Button::button_isr(const struct device *dev, struct gpio_callback *cb, uint32_t pins) { - Button * button; - - if (pins & BIT(BUTTON_EARABLE)) { - //earable_btn._read_state(); - button = &earable_btn; + ARG_UNUSED(dev); + ARG_UNUSED(cb); + if (!(pins & BIT(BUTTON_EARABLE))) { + return; } + Button *button = &earable_btn; + /*if (pins & BIT(BUTTON_VOLUME_UP)) { volume_up_btn._read_state(); } @@ -114,4 +115,4 @@ button_action Button::getState() const { Button earable_btn(GPIO_DT_SPEC_GET(DT_ALIAS(sw0), gpios)); // Button volume_up_btn(GPIO_DT_SPEC_GET_OR(DT_ALIAS(sw1), gpios, {0})); // Button volume_down_btn(GPIO_DT_SPEC_GET_OR(DT_ALIAS(sw2), gpios, {0})); -// Button four_btn(GPIO_DT_SPEC_GET_OR(DT_ALIAS(sw3), gpios, {0})); \ No newline at end of file +// Button four_btn(GPIO_DT_SPEC_GET_OR(DT_ALIAS(sw3), gpios, {0})); diff --git a/src/drivers/ADAU1860.cpp b/src/drivers/ADAU1860.cpp index b75fa521..4b662914 100644 --- a/src/drivers/ADAU1860.cpp +++ b/src/drivers/ADAU1860.cpp @@ -2,6 +2,7 @@ #include "zbus_common.h" #include "openearable_common.h" #include +#include #include #include @@ -18,6 +19,7 @@ static struct k_work_delayable ascr_lock_work; void ADAU1860::check_ascr_lock(struct k_work *work) { + ARG_UNUSED(work); uint8_t status2; dac.readReg(registers::STATUS2, &status2, sizeof(status2)); @@ -208,8 +210,9 @@ int ADAU1860::begin() { uint8_t spt0_route1 = 33; // ASCRO 1 writeReg(registers::SPT0_ROUTE1, &spt0_route1, sizeof(spt0_route1)); - // DMIC_VOL0 uint8_t dmic_vol = 0x20; // 12dB + // DMIC channel 0 - outer mic + // DMIC channel 1 - inner mic writeReg(registers::DMIC_VOL0, &dmic_vol, sizeof(dmic_vol)); writeReg(registers::DMIC_VOL1, &dmic_vol, sizeof(dmic_vol)); @@ -343,10 +346,10 @@ int ADAU1860::setup_FDSP() { uint8_t fdsp_ctrl4 = 2; // framrate source DMIC01 writeReg(registers::FDSP_CTRL4, &fdsp_ctrl4, sizeof(fdsp_ctrl4)); - /*uint8_t fdsp_ctrl4 = 15; // fixed frame rate + /* uint8_t fdsp_ctrl4 = 15; // fixed frame rate writeReg(registers::FDSP_CTRL4, &fdsp_ctrl4, sizeof(fdsp_ctrl4)); - /*uint8_t fdsp_ctrl5 = 0xFF; // fixed frame rate + uint8_t fdsp_ctrl5 = 0xFF; // fixed frame rate writeReg(registers::FDSP_CTRL5, &fdsp_ctrl5, sizeof(fdsp_ctrl5)); uint8_t fdsp_ctrl6 = 0x01; // fixed frame rate @@ -404,17 +407,17 @@ int ADAU1860::mute(bool active) { #endif } -int ADAU1860::fdsp_safe_load(sl_address address, safe_load_params params, bool update_inactive) { +int ADAU1860::fdsp_safe_load(sl_address safe_load_address, safe_load_params params, bool update_inactive) { // TODO: not working if (update_inactive) { // write to non active banks for (int i = 0; i < FDSP_NUM_BANKS; i++) { if (i == _active_bank) continue; - writeReg(FDSP_BANK(i, address), (uint8_t *) params, sizeof(safe_load_params)); + writeReg(FDSP_BANK(i, safe_load_address), (uint8_t *) params, sizeof(safe_load_params)); } } - uint8_t _address = address; + uint8_t _address = safe_load_address; writeReg(registers::FDSP_SL_ADDR, &_address, sizeof(_address)); writeReg(registers::FDSP_SL_P0_0, (uint8_t *) params, sizeof(safe_load_params)); @@ -424,16 +427,16 @@ int ADAU1860::fdsp_safe_load(sl_address address, safe_load_params params, bool u return 0; } -int ADAU1860::fdsp_safe_load(sl_address address, int n, uint32_t param, bool update_inactive) { +int ADAU1860::fdsp_safe_load(sl_address safe_load_address, int n, uint32_t param, bool update_inactive) { uint32_t params[FDSP_NUM_PARAMS]; for (int i = 0; i < FDSP_NUM_PARAMS; i++) { - params[i] = fdsp_param_bank_a[i][address]; + params[i] = fdsp_param_bank_a[i][safe_load_address]; } params[n] = param; - fdsp_safe_load(address, params, update_inactive); + fdsp_safe_load(safe_load_address, params, update_inactive); return 0; } @@ -495,6 +498,18 @@ uint8_t ADAU1860::fdsp_get_volume() { return 0xFF-dac_vol; } +int ADAU1860::mic_gain_write(uint8_t channel, uint8_t gain) { + uint32_t reg = (channel == 0) ? registers::DMIC_VOL0 : registers::DMIC_VOL1; + return writeReg(reg, &gain, sizeof(gain)); +} + +uint8_t ADAU1860::mic_gain_read(uint8_t channel) { + uint8_t val = 0; + uint32_t reg = (channel == 0) ? registers::DMIC_VOL0 : registers::DMIC_VOL1; + readReg(reg, &val, sizeof(val)); + return val; +} + int ADAU1860::soft_reset(bool full_reset) { int ret = 0; @@ -537,7 +552,7 @@ bool ADAU1860::readReg(uint32_t reg, uint8_t * buffer, uint16_t len) { } -void ADAU1860::writeReg(uint32_t reg, uint8_t *buffer, uint16_t len) { +int ADAU1860::writeReg(uint32_t reg, uint8_t *buffer, uint16_t len) { int ret; struct i2c_msg msg[2]; @@ -564,6 +579,8 @@ void ADAU1860::writeReg(uint32_t reg, uint8_t *buffer, uint16_t len) { } _i2c->release(); + + return ret; } #ifdef NOISE_GATE_ACTIVE @@ -575,11 +592,12 @@ int cmd_dsp_noise_gate(const struct shell *shell, size_t argc, char **argv) { safe_load_params params; - params[0] = strtoul(argv[1], NULL, 16) | 0xC80; - params[1] = strtoul(argv[2], NULL, 16) | 0xD00; - params[2] = strtoul(argv[3], NULL, 16); - params[3] = strtoul(argv[4], NULL, 16); - params[4] = strtoul(argv[5], NULL, 16) | 0x80000000; + params[0] = static_cast(strtoul(argv[1], nullptr, 16)) | 0xC80U; + params[1] = static_cast(strtoul(argv[2], nullptr, 16)) | 0xD00U; + params[2] = static_cast(strtoul(argv[3], nullptr, 16)); + params[3] = static_cast(strtoul(argv[4], nullptr, 16)); + params[4] = + static_cast(strtoul(argv[5], nullptr, 16)) | 0x80000000U; shell_print(shell, "Params:"); for (int i = 0; i < FDSP_NUM_PARAMS; i++) { @@ -598,4 +616,4 @@ SHELL_STATIC_SUBCMD_SET_CREATE(dsp_cmd, SHELL_SUBCMD_SET_END); SHELL_CMD_REGISTER(dsp, &dsp_cmd, "Set DSP parameters", NULL); -#endif \ No newline at end of file +#endif diff --git a/src/drivers/ADAU1860.h b/src/drivers/ADAU1860.h index 63e3fd98..9614e704 100644 --- a/src/drivers/ADAU1860.h +++ b/src/drivers/ADAU1860.h @@ -412,12 +412,16 @@ class ADAU1860 { uint8_t get_volume(); + // DMIC gain control + int mic_gain_write(uint8_t channel, uint8_t gain); + uint8_t mic_gain_read(uint8_t channel); + #if CONFIG_FDSP int fdsp_bank_select(uint8_t bank); #endif private: bool readReg(uint32_t reg, uint8_t * buffer, uint16_t len); - void writeReg(uint32_t reg, uint8_t * buffer, uint16_t len); + int writeReg(uint32_t reg, uint8_t * buffer, uint16_t len); int setup_EQ(); int setup_FDSP(); @@ -453,4 +457,4 @@ class ADAU1860 { extern ADAU1860 dac; -#endif \ No newline at end of file +#endif diff --git a/src/drivers/CMakeLists.txt b/src/drivers/CMakeLists.txt index d1f11d41..37d0f0dc 100644 --- a/src/drivers/CMakeLists.txt +++ b/src/drivers/CMakeLists.txt @@ -4,7 +4,7 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # -target_sources_ifdef(CONFIG_NRF5340_AUDIO_CS47L63_DRIVER +target_sources_ifdef(CONFIG_NRF5340_AUDIO_ADAU1860_DRIVER app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/ADAU1860.cpp ) @@ -12,4 +12,4 @@ add_subdirectory(LED_Controller) zephyr_library_include_directories( LED_Controller -) \ No newline at end of file +) diff --git a/src/drivers/Kconfig b/src/drivers/Kconfig index 97d6acf4..327b92d6 100644 --- a/src/drivers/Kconfig +++ b/src/drivers/Kconfig @@ -8,6 +8,7 @@ menu "Drivers" menuconfig NRF5340_AUDIO_CS47L63_DRIVER bool "CS47L63 HW codec driver" + depends on BOARD_NRF5340_AUDIO_DK_NRF5340_CPUAPP select HW_CODEC_CIRRUS_LOGIC help Include the driver for the Cirrus Logic CS47L63 hardware codec chip @@ -24,6 +25,31 @@ config CS47L63_STACK_SIZE int "Stack size for CS47L63" default 700 +module = CS47L63 +module-str = cs47l63 +source "subsys/logging/Kconfig.template.log_config" + +endif # NRF5340_AUDIO_CS47L63_DRIVER + +menuconfig NRF5340_AUDIO_ADAU1860_DRIVER + bool "ADAU1860 HW codec driver" + depends on BOARD_OPENEARABLE_V2_NRF5340_CPUAPP || BOARD_OPENEARABLE_V2_NRF5340_CPUAPP_NS + default y if BOARD_OPENEARABLE_V2_NRF5340_CPUAPP || BOARD_OPENEARABLE_V2_NRF5340_CPUAPP_NS + help + Include the driver for the Analog Devices ADAU1860 hardware codec chip + +if NRF5340_AUDIO_ADAU1860_DRIVER + +config ADAU1860_THREAD_PRIO + int "Priority for ADAU1860 thread" + default 5 + help + This is a preemptible thread + +config ADAU1860_STACK_SIZE + int "Stack size for ADAU1860" + default 700 + choice EQUALIZER prompt "Choose EQ on DSP or MCU ISO transport" default EQAULIZER_DSP @@ -41,9 +67,9 @@ config FDSP default y bool "Use FDSP the fast DSP core. This is required for Transparancy mode and ANC." -module = CS47L63 -module-str = cs47l63 +module = ADAU1860 +module-str = adau1860 source "subsys/logging/Kconfig.template.log_config" -endif # NRF5340_AUDIO_CS47L63_DRIVER +endif # NRF5340_AUDIO_ADAU1860_DRIVER endmenu # Drivers diff --git a/src/drivers/LED_Controller/KTD2026.cpp b/src/drivers/LED_Controller/KTD2026.cpp index feb3ead9..15631f7d 100644 --- a/src/drivers/LED_Controller/KTD2026.cpp +++ b/src/drivers/LED_Controller/KTD2026.cpp @@ -95,10 +95,17 @@ void KTD2026::begin() { if (_active) return; - _active = true; + _active = true; ret = pm_device_runtime_get(ls_1_8); + if (ret) { + LOG_ERR("Failed to enable 1.8 V LED supply: %d", ret); + } + ret = pm_device_runtime_get(ls_3_3); + if (ret) { + LOG_ERR("Failed to enable 3.3 V LED supply: %d", ret); + } _i2c->begin(); @@ -121,7 +128,14 @@ void KTD2026::power_off() { (void)writeReg(registers::CTRL, &val, sizeof(val)); int ret = pm_device_runtime_put(ls_1_8); + if (ret) { + LOG_ERR("Failed to disable 1.8 V LED supply: %d", ret); + } + ret = pm_device_runtime_put(ls_3_3); + if (ret) { + LOG_ERR("Failed to disable 3.3 V LED supply: %d", ret); + } clearCachedColor(); diff --git a/src/modules/CMakeLists.txt b/src/modules/CMakeLists.txt index 6780217b..45cf0745 100644 --- a/src/modules/CMakeLists.txt +++ b/src/modules/CMakeLists.txt @@ -9,6 +9,8 @@ target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/audio_i2s.c) target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/audio_sync_timer.c) target_sources_ifdef(CONFIG_NRF5340_AUDIO_CS47L63_DRIVER app PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/hw_codec.c) +target_sources_ifdef(CONFIG_NRF5340_AUDIO_ADAU1860_DRIVER app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/hw_codec_adau1860.cpp) target_sources_ifdef(CONFIG_NRF5340_AUDIO_POWER_MEASUREMENT app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/power_meas.c) diff --git a/src/modules/hw_codec.h b/src/modules/hw_codec.h index c0347432..f25755e8 100644 --- a/src/modules/hw_codec.h +++ b/src/modules/hw_codec.h @@ -119,6 +119,22 @@ int hw_codec_set_audio_mode(enum audio_mode mode); enum audio_mode hw_codec_get_audio_mode(); +/* Microphone (DMIC) gain control. + * Board mapping: + * ADAU186x DMIC_VOL0 / DMIC channel 0 controls the outer microphone. + * ADAU186x DMIC_VOL1 / DMIC channel 1 controls the inner microphone. + * Register mapping (per ADAU186x datasheet DMIC_VOL0 @ 0x4000C045): + * 0x00 = +24 dB + * 0x01-0x3F = +23.625 to +0.375 dB (decrement by 0.375 dB per step) + * 0x40 = 0 dB (reset value) + * 0x41-0xFD = -0.375 to -70.875 dB (decrement by 0.375 dB per step) + * 0xFE = -71.25 dB + * 0xFF = Mute + */ +int hw_codec_mic_gain_set(uint8_t gain_outer_reg, uint8_t gain_inner_reg); +uint8_t hw_codec_mic_gain_get_outer(void); +uint8_t hw_codec_mic_gain_get_inner(void); + #ifdef __cplusplus } #endif diff --git a/src/modules/hw_codec_adau1860.cpp b/src/modules/hw_codec_adau1860.cpp index fd06b2d7..f69668d6 100644 --- a/src/modules/hw_codec_adau1860.cpp +++ b/src/modules/hw_codec_adau1860.cpp @@ -26,6 +26,10 @@ LOG_MODULE_REGISTER(hw_codec, CONFIG_MODULE_HW_CODEC_LOG_LEVEL); #define VOLUME_ADJUST_STEP_DB 3 +/* Allow the DSP mute ramp and I2S clock domain to settle at source changes. */ +#define CODEC_MUTE_SETTLE_MS 100 +#define CODEC_I2S_PREROLL_MS 100 + ZBUS_SUBSCRIBER_DEFINE(volume_evt_sub, CONFIG_VOLUME_MSG_SUB_QUEUE_SIZE); static uint32_t prev_volume_reg_val = OUT_VOLUME_DEFAULT; @@ -38,6 +42,11 @@ K_THREAD_STACK_DEFINE(volume_msg_sub_thread_stack, CONFIG_VOLUME_MSG_SUB_STACK_S static enum audio_mode audio_mode; +enum hw_codec_mic { + HW_CODEC_MIC_OUTER = 0, + HW_CODEC_MIC_INNER = 1, +}; + static int settings_set_cb(const char *name, size_t len, settings_read_cb read_cb, void *cb_arg) { if (strcmp(name, "mode") == 0 && len == sizeof(audio_mode)) { @@ -54,22 +63,46 @@ static int settings_set_cb(const char *name, size_t len, settings_read_cb read_c SETTINGS_STATIC_HANDLER_DEFINE(audio, "audio", NULL, settings_set_cb, NULL, NULL); int hw_codec_set_audio_mode(enum audio_mode mode) { - int ret; - - audio_mode = mode; - - settings_save_one("audio/mode", &mode, sizeof(mode)); + int first_error = 0; + int ret; ret = dac.fdsp_bank_select((uint8_t) mode); + if (ret) { + LOG_ERR("Failed to select DSP bank, ret: %d", ret); + first_error = ret; + } + // TODO: make writing to bank work k_msleep(200); ret = hw_codec_volume_adjust(0); + if (ret) { + LOG_ERR("Failed to adjust codec volume, ret: %d", ret); + if (!first_error) { + first_error = ret; + } + } + ret = dac.mute(muted); if (ret) { LOG_ERR("Failed to set audio mode, ret: %d", ret); + if (!first_error) { + first_error = ret; + } + } + + if (first_error) { + /* Keep the persisted mode unchanged when the codec could not fully apply it. */ + return first_error; + } + + audio_mode = mode; + ret = settings_save_one("audio/mode", &mode, sizeof(mode)); + if (ret) { + LOG_ERR("Failed to persist audio mode, ret: %d", ret); return ret; } - return ret; + + return 0; } enum audio_mode hw_codec_get_audio_mode() { @@ -285,6 +318,11 @@ int hw_codec_default_conf_enable(void) return ret; } + /* audio_datapath_aquire() starts I2S before enabling the codec. Keep the + * output muted while the new clock stream becomes stable. + */ + k_msleep(CODEC_I2S_PREROLL_MS); + //ret = dac.setup(); if (!muted) { ret = dac.mute(false); @@ -305,9 +343,53 @@ int hw_codec_stop_audio(void) return ret; } + /* Do not remove the I2S clocks while the DSP is still applying mute. */ + k_msleep(CODEC_MUTE_SETTLE_MS); + + return 0; +} + +/* Microphone (DMIC) gain control using ADAU186x DMIC_VOL0/1 registers. + * Per ADAU186x datasheet register DMIC_VOL0 (addr 0x4000C045): + * 0x00 = +24 dB + * 0x01-0x3F = +23.625 to +0.375 dB (decrement by 0.375 dB per step) + * 0x40 = 0 dB (reset value) + * 0x41-0xFD = -0.375 to -70.875 dB (decrement by 0.375 dB per step) + * 0xFE = -71.25 dB + * 0xFF = Mute + */ +int hw_codec_mic_gain_set(uint8_t gain_outer_reg, uint8_t gain_inner_reg) +{ + int ret; + + ret = dac.mic_gain_write(HW_CODEC_MIC_OUTER, gain_outer_reg); + if (ret) { + LOG_ERR("Failed to set outer mic gain: %d", ret); + return ret; + } + + ret = dac.mic_gain_write(HW_CODEC_MIC_INNER, gain_inner_reg); + if (ret) { + LOG_ERR("Failed to set inner mic gain: %d", ret); + return ret; + } + + LOG_INF("DMIC gain set: outer=0x%02x, inner=0x%02x", + gain_outer_reg, gain_inner_reg); return 0; } +uint8_t hw_codec_mic_gain_get_outer(void) +{ + return dac.mic_gain_read(HW_CODEC_MIC_OUTER); +} + +uint8_t hw_codec_mic_gain_get_inner(void) +{ + return dac.mic_gain_read(HW_CODEC_MIC_INNER); +} + + int hw_codec_soft_reset(void) { int ret; @@ -345,4 +427,4 @@ int hw_codec_init(void) ERR_CHK(ret); return 0; -} \ No newline at end of file +} diff --git a/src/modules/sd_card.c b/src/modules/sd_card.c index dee80d7e..a147e9a4 100644 --- a/src/modules/sd_card.c +++ b/src/modules/sd_card.c @@ -259,7 +259,7 @@ int sd_card_list_files(char const *const path, char *buf, size_t *buf_size, bool entry.name); } - if (len >= remaining_buf_size) { + if (len < 0 || (size_t)len >= remaining_buf_size) { LOG_ERR("Failed to append to buffer, error: %d", len); return -EINVAL; } diff --git a/src/time_sync/time_sync.c b/src/time_sync/time_sync.c index 4d8a0c16..1b26991f 100644 --- a/src/time_sync/time_sync.c +++ b/src/time_sync/time_sync.c @@ -73,6 +73,7 @@ static ssize_t write_rtt_request( uint16_t offset, uint8_t flags ) { + ARG_UNUSED(flags); uint64_t rx_time = get_current_time_us(); if (offset != 0) { @@ -128,6 +129,9 @@ static ssize_t write_time_offset( uint16_t offset, uint8_t flags ) { + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(flags); if (offset != 0) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); } @@ -162,15 +166,19 @@ bool time_sync_is_synced(void) { uint64_t get_current_time_us(void) { uint64_t base_u = get_time_since_boot_us(); int64_t base_s = (base_u > (uint64_t)INT64_MAX) ? INT64_MAX : (int64_t)base_u; - int64_t now_s = base_s + time_offset_us; + if (time_offset_us > 0 && base_s > INT64_MAX - time_offset_us) { + LOG_WRN("Current time overflow, returning UINT64_MAX"); + return UINT64_MAX; + } + if (time_offset_us < 0 && base_s < INT64_MIN - time_offset_us) { + LOG_WRN("Current time underflow, returning 0"); + return 0; + } + int64_t now_s = base_s + time_offset_us; if (now_s < 0) { LOG_WRN("Current time underflow, returning 0"); return 0; } - if (now_s != base_u + time_offset_us) { - LOG_WRN("Current time overflow, returning UINT64_MAX"); - return UINT64_MAX; - } return (uint64_t)now_s; } @@ -180,6 +188,7 @@ uint64_t get_time_since_boot_us(void) { void rtt_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { + ARG_UNUSED(attr); LOG_DBG("RTT characteristic CCCD changed: %u", value); notify_rtt_enabled = (value == BT_GATT_CCC_NOTIFY); } diff --git a/src/utils/StateIndicator.cpp b/src/utils/StateIndicator.cpp index 2600637d..89ca40f8 100644 --- a/src/utils/StateIndicator.cpp +++ b/src/utils/StateIndicator.cpp @@ -29,6 +29,10 @@ static void connect_evt_handler(const struct zbus_channel *chan) case BT_MGMT_DISCONNECTED: state_indicator.set_pairing_state(PAIRED); break; + + default: + /* Other events do not affect the pairing state indication. */ + break; } } @@ -63,9 +67,9 @@ void StateIndicator::init(struct earable_state state) { set_state(state); } -void StateIndicator::set_custom_color(const RGBColor &color) { - memcpy(&this->color, color, sizeof(RGBColor)); - if (_state.led_mode == CUSTOM) led_controller.setColor(color); +void StateIndicator::set_custom_color(const RGBColor &custom_color) { + memcpy(&this->color, custom_color, sizeof(RGBColor)); + if (_state.led_mode == CUSTOM) led_controller.setColor(custom_color); } void StateIndicator::set_dfu_active(bool active) { diff --git a/src/utils/error_handler.c b/src/utils/error_handler.c index 7e5ae97e..024bf4ab 100644 --- a/src/utils/error_handler.c +++ b/src/utils/error_handler.c @@ -23,6 +23,7 @@ static const struct gpio_dt_spec center_led_b = GPIO_DT_SPEC_GET(DT_NODELABEL(rg void error_handler(unsigned int reason, const struct arch_esf *esf) { + ARG_UNUSED(esf); #if (CONFIG_DEBUG) LOG_ERR("Caught system error -- reason %d. Entering infinite loop", reason); LOG_PANIC(); diff --git a/src/utils/macros/macros_custom.h b/src/utils/macros/macros_custom.h index 5e102938..fb900645 100644 --- a/src/utils/macros/macros_custom.h +++ b/src/utils/macros/macros_custom.h @@ -15,7 +15,7 @@ ZBUS_SUBSCRIBER_DEFINE(name ## _sub, CONFIG_BUTTON_MSG_SUB_QUEUE_SIZE); \ ZBUS_CHAN_DECLARE(name ## _chan); \ static K_THREAD_STACK_DEFINE(thread_stack, CONFIG_BUTTON_MSG_SUB_STACK_SIZE); \ \ -static void write_ ## name ## _gatt(void)\ +static void write_ ## name ## _gatt(void)\ {\ int ret;\ const struct zbus_channel *chan;\ @@ -60,4 +60,4 @@ int init_ ## name ## _service() {\ } #endif -#endif \ No newline at end of file +#endif diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..f0fde713 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,109 @@ +# Tests + +Unit tests live in `tests/unit`. Zephyr's +[Twister test runner](https://docs.nordicsemi.com/bundle/ncs-3.0.1/page/zephyr/develop/test/twister.html) +builds each +[Unity suite](https://github.com/nrfconnect/sdk-nrf/blob/v3.0.1/doc/nrf/test_and_optimize/test_framework/testing_unity_cmock.rst) +as a Linux executable using +[`native_sim/native/64`](https://docs.nordicsemi.com/bundle/ncs-3.0.1/page/zephyr/boards/native/native_sim/doc/index.html), +so the tests run on a development computer or GitHub Actions runner and do not +require OpenEarable hardware. +These links target the nRF Connect SDK version pinned in +[`west.yml`](../west.yml). + +## GitHub Actions + +The [`Unit Tests`](../.github/workflows/unit_tests.yaml) workflow is the primary +way to run the test suite. It runs for every pull request, for pushes to `main`, +and when started manually from GitHub Actions. + +For pull requests, the workflow creates or updates a comment with the result. +Twister reports and logs are also available as the `unit-test-results` artifact +for 14 days. + +Twister recursively discovers every `testcase.yaml` below `tests/unit`, so a +valid new suite in that directory is included automatically; the workflow does +not need to be edited. The suite must allow `native_sim/native/64` to run in +this workflow. + +## Run the tests locally (optional) + +Run Twister from the west workspace root: the directory containing `zephyr`, +`nrf`, and this repository. The commands below assume the repository directory +is named `open-earable-v2`; adjust the path if it is named differently. + +Unity's test-runner generation requires Ruby. On Debian or Ubuntu: + +```sh +sudo apt install ruby +``` + +Run all unit-test scenarios: + +```sh +python3 zephyr/scripts/twister \ + -T open-earable-v2/tests/unit \ + -p native_sim/native/64 \ + --inline-logs +``` + +Run a single scenario by its name from `testcase.yaml`: + +```sh +python3 zephyr/scripts/twister \ + -T open-earable-v2/tests/unit \ + -p native_sim/native/64 \ + --scenario openearable.unit.sensor_component \ + --inline-logs +``` + +On Windows, run the tests in WSL or a Linux container because `native_sim` +produces a Linux executable. + +## Add a test suite + +The existing suites demonstrate two common patterns: + +- [`sensor_component`](unit/sensor_component) tests C++ production sources and + binary serialization; +- [`ring_buffer`](unit/ring_buffer) tests a header-only C++ template. + +A suite under `tests/unit//` consists of: + +- `testcase.yaml`, defining a unique scenario name and allowing + `native_sim/native/64`; +- `prj.conf`, enabling `CONFIG_UNITY` and any configuration required by the + production code; +- `CMakeLists.txt`, passing the test source to `test_runner_generate(...)` and + adding the production sources and include directories to the `app` target; +- test source files whose test functions start with `test_` and use Unity's + `TEST_ASSERT_*` macros. + +Production sources are not pulled in automatically. List each source and its +include directories in the suite's `CMakeLists.txt`. + +The host build cannot use nRF hardware. Keep hardware-independent behavior +separate from drivers, or provide test doubles for hardware and Zephyr APIs. +The CI workflow discovers new suites automatically when they are placed below +`tests/unit`. + +For C++ tests, enable `CONFIG_CPP`. Add `CONFIG_REQUIRES_FULL_LIBCPP` only when +the tested code requires the full C++ standard library. Keep the +`test_suiteTearDown` linkage adapter shown in the C++ examples so the generated +Unity runner can call the nRF Connect SDK's C teardown function. + +## Generate coverage + +Install `gcovr` in the active Python environment and run Twister with +[coverage enabled](https://docs.nordicsemi.com/bundle/ncs-3.0.1/page/zephyr/develop/test/coverage.html): + +```sh +python3 -m pip install gcovr +python3 zephyr/scripts/twister \ + -T open-earable-v2/tests/unit \ + -p native_sim/native/64 \ + --coverage \ + --coverage-basedir open-earable-v2 +``` + +Open `twister-out/coverage/index.html` after the run. diff --git a/tests/unit/ring_buffer/CMakeLists.txt b/tests/unit/ring_buffer/CMakeLists.txt new file mode 100644 index 00000000..930e4928 --- /dev/null +++ b/tests/unit/ring_buffer/CMakeLists.txt @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(ring_buffer_tests) + +test_runner_generate(src/main.cpp) + +target_include_directories(app PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../../../src/Wire +) + +target_sources(app PRIVATE + src/main.cpp +) diff --git a/tests/unit/ring_buffer/prj.conf b/tests/unit/ring_buffer/prj.conf new file mode 100644 index 00000000..a9136815 --- /dev/null +++ b/tests/unit/ring_buffer/prj.conf @@ -0,0 +1,2 @@ +CONFIG_UNITY=y +CONFIG_CPP=y diff --git a/tests/unit/ring_buffer/src/main.cpp b/tests/unit/ring_buffer/src/main.cpp new file mode 100644 index 00000000..e55ffabe --- /dev/null +++ b/tests/unit/ring_buffer/src/main.cpp @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: MIT + +#include + +#include "RingBuffer.h" + +using arduino::RingBufferN; + +void setUp(void) +{ +} + +void test_new_buffer_is_empty(void) +{ + RingBufferN<4> buffer; + + TEST_ASSERT_EQUAL_INT(0, buffer.available()); + TEST_ASSERT_EQUAL_INT(4, buffer.availableForStore()); + TEST_ASSERT_EQUAL_INT(-1, buffer.peek()); + TEST_ASSERT_EQUAL_INT(-1, buffer.read_char()); + TEST_ASSERT_FALSE(buffer.isFull()); +} + +void test_reads_values_in_fifo_order_across_wraparound(void) +{ + RingBufferN<4> buffer; + + buffer.store_char(1); + buffer.store_char(2); + buffer.store_char(3); + TEST_ASSERT_EQUAL_INT(1, buffer.read_char()); + TEST_ASSERT_EQUAL_INT(2, buffer.read_char()); + + buffer.store_char(4); + buffer.store_char(5); + + TEST_ASSERT_EQUAL_INT(3, buffer.read_char()); + TEST_ASSERT_EQUAL_INT(4, buffer.read_char()); + TEST_ASSERT_EQUAL_INT(5, buffer.read_char()); + TEST_ASSERT_EQUAL_INT(-1, buffer.read_char()); +} + +void test_full_buffer_rejects_new_values(void) +{ + RingBufferN<4> buffer; + + buffer.store_char(1); + buffer.store_char(2); + buffer.store_char(3); + buffer.store_char(4); + + TEST_ASSERT_TRUE(buffer.isFull()); + TEST_ASSERT_EQUAL_INT(0, buffer.availableForStore()); + + buffer.store_char(5); + + TEST_ASSERT_EQUAL_INT(4, buffer.available()); + TEST_ASSERT_EQUAL_INT(1, buffer.read_char()); + TEST_ASSERT_EQUAL_INT(2, buffer.read_char()); + TEST_ASSERT_EQUAL_INT(3, buffer.read_char()); + TEST_ASSERT_EQUAL_INT(4, buffer.read_char()); +} + +void test_peek_does_not_remove_value(void) +{ + RingBufferN<4> buffer; + + buffer.store_char(42); + + TEST_ASSERT_EQUAL_INT(42, buffer.peek()); + TEST_ASSERT_EQUAL_INT(1, buffer.available()); + TEST_ASSERT_EQUAL_INT(42, buffer.read_char()); +} + +void test_clear_discards_values_and_restores_capacity(void) +{ + RingBufferN<4> buffer; + + buffer.store_char(1); + buffer.store_char(2); + buffer.clear(); + + TEST_ASSERT_EQUAL_INT(0, buffer.available()); + TEST_ASSERT_EQUAL_INT(4, buffer.availableForStore()); + TEST_ASSERT_EQUAL_INT(-1, buffer.read_char()); +} + +extern "C" int unity_c_suite_teardown(int failures) asm("test_suiteTearDown"); + +int test_suiteTearDown(int failures) +{ + return unity_c_suite_teardown(failures); +} + +extern int unity_main(void); + +int main(void) +{ + (void)unity_main(); + return 0; +} diff --git a/tests/unit/ring_buffer/testcase.yaml b/tests/unit/ring_buffer/testcase.yaml new file mode 100644 index 00000000..82d31a07 --- /dev/null +++ b/tests/unit/ring_buffer/testcase.yaml @@ -0,0 +1,9 @@ +tests: + openearable.unit.ring_buffer: + tags: + - unit + - wire + integration_platforms: + - native_sim/native/64 + platform_allow: + - native_sim/native/64 diff --git a/tests/unit/sensor_component/CMakeLists.txt b/tests/unit/sensor_component/CMakeLists.txt new file mode 100644 index 00000000..001cd348 --- /dev/null +++ b/tests/unit/sensor_component/CMakeLists.txt @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(sensor_component_tests) + +test_runner_generate(src/main.cpp) + +target_include_directories(app PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../../../src/ParseInfo +) + +target_sources(app PRIVATE + src/main.cpp + ../../../src/ParseInfo/SensorComponent.cpp +) diff --git a/tests/unit/sensor_component/prj.conf b/tests/unit/sensor_component/prj.conf new file mode 100644 index 00000000..b8c4533b --- /dev/null +++ b/tests/unit/sensor_component/prj.conf @@ -0,0 +1,3 @@ +CONFIG_UNITY=y +CONFIG_CPP=y +CONFIG_REQUIRES_FULL_LIBCPP=y diff --git a/tests/unit/sensor_component/src/main.cpp b/tests/unit/sensor_component/src/main.cpp new file mode 100644 index 00000000..54106e7f --- /dev/null +++ b/tests/unit/sensor_component/src/main.cpp @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: MIT + +#include + +#include +#include + +#include "SensorComponent.h" + +static SensorComponent components[] = { + {.name = "x", .unit = "m/s2", .parseType = PARSE_TYPE_FLOAT}, + {.name = "quality", .unit = "%", .parseType = PARSE_TYPE_UINT8}, +}; + +static SensorComponentGroup group = { + .name = "acceleration", + .componentCount = sizeof(components) / sizeof(components[0]), + .components = components, +}; + +void setUp(void) +{ +} + +extern "C" int unity_c_suite_teardown(int failures) asm("test_suiteTearDown"); + +int test_suiteTearDown(int failures) +{ + return unity_c_suite_teardown(failures); +} + +void test_size_matches_serialized_size(void) +{ + char buffer[64] = {0}; + + const size_t expected_size = getSensorComponentGroupSize(&group); + const ssize_t bytes_written = serializeSensorComponentGroup(&group, buffer, sizeof(buffer)); + + TEST_ASSERT_EQUAL_INT64(static_cast(expected_size), bytes_written); +} + +void test_serializes_all_component_fields(void) +{ + char buffer[64] = {0}; + const char *cursor = buffer; + + const ssize_t bytes_written = serializeSensorComponentGroup(&group, buffer, sizeof(buffer)); + + TEST_ASSERT_GREATER_THAN_INT64(0, bytes_written); + + for (size_t i = 0; i < group.componentCount; ++i) { + const SensorComponent *component = &components[i]; + uint8_t length; + + TEST_ASSERT_EQUAL_UINT8(component->parseType, static_cast(*cursor++)); + + length = static_cast(*cursor++); + TEST_ASSERT_EQUAL_UINT8(strlen(group.name), length); + TEST_ASSERT_EQUAL_MEMORY(group.name, cursor, length); + cursor += length; + + length = static_cast(*cursor++); + TEST_ASSERT_EQUAL_UINT8(strlen(component->name), length); + TEST_ASSERT_EQUAL_MEMORY(component->name, cursor, length); + cursor += length; + + length = static_cast(*cursor++); + TEST_ASSERT_EQUAL_UINT8(strlen(component->unit), length); + TEST_ASSERT_EQUAL_MEMORY(component->unit, cursor, length); + cursor += length; + } + + TEST_ASSERT_EQUAL_INT64(bytes_written, cursor - buffer); +} + +void test_accepts_exactly_sized_buffer(void) +{ + char buffer[64] = {0}; + const size_t required_size = getSensorComponentGroupSize(&group); + + const ssize_t bytes_written = serializeSensorComponentGroup(&group, buffer, required_size); + + TEST_ASSERT_EQUAL_INT64(static_cast(required_size), bytes_written); +} + +void test_rejects_buffer_one_byte_too_small(void) +{ + char buffer[64]; + const size_t required_size = getSensorComponentGroupSize(&group); + + memset(buffer, 0x5a, sizeof(buffer)); + const ssize_t result = serializeSensorComponentGroup(&group, buffer, required_size - 1); + + TEST_ASSERT_EQUAL_INT64(-1, result); + for (const char byte : buffer) { + TEST_ASSERT_EQUAL_HEX8(0x5a, byte); + } +} + +void test_empty_group_has_empty_serialization(void) +{ + SensorComponentGroup empty_group = { + .name = "empty", + .componentCount = 0, + .components = nullptr, + }; + char buffer = 0x5a; + + TEST_ASSERT_EQUAL_UINT64(0, getSensorComponentGroupSize(&empty_group)); + TEST_ASSERT_EQUAL_INT64(0, serializeSensorComponentGroup(&empty_group, &buffer, 0)); + TEST_ASSERT_EQUAL_HEX8(0x5a, buffer); +} + +/* Zephyr reserves nonzero returns from main, so ignore Unity's return value. */ +extern int unity_main(void); + +int main(void) +{ + (void)unity_main(); + return 0; +} diff --git a/tests/unit/sensor_component/testcase.yaml b/tests/unit/sensor_component/testcase.yaml new file mode 100644 index 00000000..9f64fc42 --- /dev/null +++ b/tests/unit/sensor_component/testcase.yaml @@ -0,0 +1,9 @@ +tests: + openearable.unit.sensor_component: + tags: + - unit + - parse_info + integration_platforms: + - native_sim/native/64 + platform_allow: + - native_sim/native/64 diff --git a/tools/.DS_Store b/tools/.DS_Store deleted file mode 100644 index 72588dae..00000000 Binary files a/tools/.DS_Store and /dev/null differ diff --git a/tools/ci/extended-warning-compiler-launcher.sh b/tools/ci/extended-warning-compiler-launcher.sh new file mode 100755 index 00000000..07a9bd98 --- /dev/null +++ b/tools/ci/extended-warning-compiler-launcher.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env sh +# Apply CI-only diagnostics to this application's sources, not Zephyr/NCS code. +compiler=$1 +shift + +for arg in "$@"; do + case "$arg" in + */open-earable-v2/src/*) exec "$compiler" -Wextra -Wshadow "$@" ;; + esac +done + +exec "$compiler" "$@" diff --git a/unicast_server/main.cpp b/unicast_server/main.cpp index 706cab4e..4da7011f 100644 --- a/unicast_server/main.cpp +++ b/unicast_server/main.cpp @@ -28,6 +28,7 @@ #include "button_service.h" #include "sensor_service.h" #include "led_service.h" +#include "audio_response_service.h" #include "SensorScheme.h" #include "DefaultSensors.h" @@ -110,6 +111,9 @@ int main(void) { ret = init_sensor_service(); ERR_CHK(ret); + ret = init_audio_response_service(); + ERR_CHK(ret); + bt_mgmt_conn_interval_init(new ConnIntvlLinear( 4, // linear increase step (8ms units) CONFIG_BLE_ACL_CONN_INTERVAL,