Skip to content
Merged

2.2.8 #262

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
4babf40
update version
TobiasRoeddiger Jul 31, 2026
a2998d6
feat(unit_tests): Add unit test framework
Jul 20, 2026
e254231
feat(unit_test_pipeline): Add unit test pipeline
Jul 20, 2026
2d98897
fix(unit-test-comment): Fix broken artifact link
Jul 20, 2026
ccf090e
refactor(ci): move unit test comment script
Jul 27, 2026
b6e7cfe
test: add ring buffer unit test example
Jul 27, 2026
a3f4725
Add BT_UUID_DMIC_GAIN characteristic UUID
JakeSW Feb 17, 2026
ea84560
Add DMIC gain control methods to ADAU1860 driver
JakeSW Feb 17, 2026
0c9baa8
Implement DMIC gain control in hw_codec layer
JakeSW Feb 17, 2026
8496305
Add DMIC gain BLE characteristic to audio config service
JakeSW Feb 17, 2026
2e0a1c0
Add DMIC gain control reference documentation
JakeSW Feb 17, 2026
9e531f8
Microphone gain fixed and added to app
habibialireza Jun 14, 2026
ec12066
fix: mic gain getters were reading wrong channels
o-bagge Jul 27, 2026
ae4d647
fix(audio): rename DMIC gain channels to internal and external mics
o-bagge Jul 27, 2026
8e23702
fix(audio): map DMIC gain channels and handle write failures
o-bagge Jul 28, 2026
2df6aea
refactor(audio): rename codec config to ADAU1860
o-bagge Jul 28, 2026
3d78cc3
chore: stop tracking macOS metadata
o-bagge Jul 28, 2026
e74775d
rename: change external/internal to outer/inner to be consistent with…
o-bagge Jul 29, 2026
63efde2
implemented seal-check
DennisMoschina Jan 31, 2026
def9c15
fix unmuting problem for seal check
Apr 1, 2026
d7200ac
feat: integrate openearable-protocol and update CMakeLists for protoc…
DennisMoschina Jun 10, 2026
7be1d7a
feat: add openearable-protocol as a submodule and update CMakeLists f…
DennisMoschina Jun 10, 2026
cbae2fc
feat(audio-response): Replace Seal Check Service with Audio Response …
DennisMoschina Jun 16, 2026
18af308
fix(build_firmware): set submodule path for Zephyr workspace initiali…
DennisMoschina Jun 23, 2026
4889d6e
chore: update submodule commit for protocol
DennisMoschina Jun 23, 2026
a0132be
feat(audio-response): add configurable audio response measurement points
DennisMoschina Jun 29, 2026
3d5aa31
fix(multitone): removed unused multitone
DennisMoschina Jul 10, 2026
368e5cd
refactor(audio-response): replace FFT with analysis bin calculations …
DennisMoschina Jul 10, 2026
cba30e9
fix(audio-response): eliminate dynamic decimator allocation
DennisMoschina Jul 17, 2026
f8791b5
fix(audio-response): suspend active audio during measurements
DennisMoschina Jul 17, 2026
7d62173
fix(audio): propagate playback startup failures
DennisMoschina Jul 17, 2026
d78b48a
fix(audio): smooth buffer playback transitions
DennisMoschina Jul 17, 2026
d798e13
fix(audio-response): prevent decimator buffer overruns
TobiasRoeddiger Jul 27, 2026
2866691
fix(audio-response): make repeated seal checks reliable
TobiasRoeddiger Jul 28, 2026
683e1cd
docs: document protocol submodule setup
TobiasRoeddiger Jul 31, 2026
e9f43a6
fix(audio): read audio channel characteristic with correct size
o-bagge Jul 31, 2026
96e1e61
fix(sca): Add missing API declarations
Ruben-Lohberg Jul 27, 2026
52c284a
fix(sca): Make narrowing conversions explicit
Ruben-Lohberg Jul 27, 2026
2eeadb9
fix(power): Use floating-point absolute value
Ruben-Lohberg Jul 27, 2026
61b8e67
fix(bluetooth): Return correctly from connection callback
Ruben-Lohberg Jul 27, 2026
a2172eb
fix(sca): Initialize state before use
Ruben-Lohberg Jul 27, 2026
2bb850b
fix(bmp388): Guard FIFO processing after length read
Ruben-Lohberg Jul 27, 2026
5e44e19
fix(button): Ignore unrelated GPIO interrupts
Ruben-Lohberg Jul 27, 2026
8086818
fix(sca): Remove unused local values
Ruben-Lohberg Jul 27, 2026
47fc2a9
fix(sca): Log discarded errors
Ruben-Lohberg Jul 27, 2026
193ae76
fix(sca): Discard unused battery return values
Ruben-Lohberg Aug 3, 2026
12f1bca
fix(sca): Discard unused subsystem return values
Ruben-Lohberg Aug 3, 2026
9d7915b
fix(sca): Avoid overwritten sensor results
Ruben-Lohberg Aug 3, 2026
64ca770
fix(sca): Guard conditional scan parameters
Ruben-Lohberg Aug 3, 2026
59be7c4
fix(audio): Avoid null TX buffer dereference
Ruben-Lohberg Aug 3, 2026
33bd543
fix(bluetooth): Ignore missing CSIS RSI data
Ruben-Lohberg Aug 3, 2026
bb254b8
fix(bluetooth): Ignore missing peer chip IDs
Ruben-Lohberg Aug 3, 2026
404867a
fix(audio): Reject incomplete stereo encoder setup
Ruben-Lohberg Aug 3, 2026
9c78c22
fix(audio): Initialize silence-frame decode inputs
Ruben-Lohberg Aug 3, 2026
5bb0488
fix(bluetooth): clarify guarded scan data
Ruben-Lohberg Aug 3, 2026
808d106
fix(audio): persist codec mode after apply
Ruben-Lohberg Aug 3, 2026
37bd93f
fix(audio): fail on persistent audio encode errors
o-bagge Sep 1, 2026
24ba3ab
fix(audio): warn when reusing released TX buffer
o-bagge Sep 1, 2026
e92cecc
fix(audio): avoid odd-length BLE service-data read
o-bagge Sep 1, 2026
86c394b
fix(bluetooth): report audio mode switch failures
o-bagge Sep 1, 2026
f6b7eb4
fix(audio): treat unavailable codec errors as fatal
o-bagge Sep 3, 2026
6b338ef
Revert "fix(audio): fail on persistent audio encode errors"
Ruben-Lohberg Sep 7, 2026
2fdd8bf
feat(SCA): Add CodeChecker pipeline
Ruben-Lohberg Jul 20, 2026
3f9668d
feat(SCA): Add extensive reporting
Ruben-Lohberg Jul 20, 2026
3f78b96
ci(codechecker): shorten clean analysis reports
Ruben-Lohberg Aug 3, 2026
8f49062
fix(ci): require CodeChecker report output
Ruben-Lohberg Sep 7, 2026
34e73b1
fix: mark intentionally unused callback arguments
Ruben-Lohberg Sep 7, 2026
3267710
fix: initialize structures and remove dead locals
Ruben-Lohberg Sep 7, 2026
f22ad37
fix: eliminate shadowed declarations
Ruben-Lohberg Sep 7, 2026
3ad72d1
fix: mark remaining intentionally unused arguments
Ruben-Lohberg Sep 7, 2026
5396639
fix: correct compiler-reported type and format issues
Ruben-Lohberg Sep 7, 2026
c95c385
fix: handle Bluetooth management events
Ruben-Lohberg Sep 7, 2026
ab517e9
fix: resolve signedness compiler warnings
Ruben-Lohberg Sep 7, 2026
14864cc
fix: resolve floating-point promotion warnings
Ruben-Lohberg Sep 7, 2026
1ea6a27
fix(bma580): clear X acceleration when FIFO frame omits axis
o-bagge Sep 8, 2026
8efb8b2
ci(warnings): report application compiler warnings on pull requests
Sep 8, 2026
cffd79b
ci: add versions to release artifact names
Ruben-Lohberg Sep 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .codechecker.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"analyze": [
"--analyzers",
"clangsa",
"cppcheck",
"--disable",
"severity:STYLE",
"--file",
"*/open-earable-v2/*",
"--capture-analysis-output",
"--report-hash",
"context-free-v2"
]
}
134 changes: 134 additions & 0 deletions .github/scripts/render_unit_test_comment.py
Original file line number Diff line number Diff line change
@@ -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 = "<!-- openearable-unit-test-results -->"
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(
[
"",
"<details open>",
"<summary>Failure details</summary>",
"",
f"<pre>{html.escape(details)}</pre>",
"</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()
1 change: 1 addition & 0 deletions .github/workflows/build_firmware.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
189 changes: 189 additions & 0 deletions .github/workflows/codechecker.yaml
Original file line number Diff line number Diff line change
@@ -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 = '<!-- codechecker-advisory-report -->';
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 });
}
Loading
Loading