Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
1727bba
Add clang-format config + CI format check, reformat sources
Jul 8, 2026
1eacca4
Preserve keypad KEY_MAP as a 4x4 grid via clang-format off
Jul 8, 2026
2392e3f
Add ./ci entrypoint (lint fix / check)
Jul 8, 2026
12f0ccc
Add shellcheck to ./ci lint; unify CI on ./ci --action=check
Jul 8, 2026
62cab50
Actually add ci.yml + shellcheck integration (fix prior partial commit)
Jul 8, 2026
9542062
Fix signed-overflow UB in base32_encode accumulator
Jul 8, 2026
2d11fdf
Add host test skeleton (test/): ASan/UBSan/valgrind/coverage harnesses
Jul 8, 2026
16dfbf0
Add whole-codebase host coverage (lcov/genhtml) + valgrind + blind-sp…
Jul 8, 2026
c18957f
Wire --check=test into ./ci (asan+valgrind+coverage)
Jul 8, 2026
d2de8fc
ci: add host test job (asan+valgrind+coverage)
Jul 8, 2026
189324e
test: drop 'range' from lcov --ignore-errors for CI lcov 2.0 compat
Jul 8, 2026
d6ef84e
test: separate genhtml --ignore-errors list (drop 'gcov') for CI lcov
Jul 8, 2026
106ba65
ci: publish coverage report to GitHub Pages
Jul 8, 2026
f27cc47
test: derive coverage first-party filter from repo root, not literal …
Jul 8, 2026
19d5cb7
test: report coverage on every first-party file, drop blind-spot list
Jul 8, 2026
fc8c4f3
test: clang-format the new cyw43/lwip host stubs
Jul 8, 2026
cbe63ec
test: drop coverage-summary.sh; rely on genhtml's own report
Jul 8, 2026
1fcedcc
test: remove coverage-summary.sh call from Makefile (fix prior partia…
Jul 8, 2026
59b56e8
test: slim coverage to harness-exercised modules only
Jul 8, 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
16 changes: 16 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Config tuned to the existing hand-written style:
# 4-space indent, spaces only, K&R braces, 100-col.
# The Align* + SortIncludes:false options preserve the author's manual
# column alignment (aligned #defines, struct members, tables) as much as
# clang-format allows. Residual churn is alignment inside call-args and
# brace-init tables, which clang-format cannot preserve.
BasedOnStyle: LLVM
IndentWidth: 4
TabWidth: 4
UseTab: Never
ColumnLimit: 100
SortIncludes: false
AlignConsecutiveMacros: AcrossComments
AlignConsecutiveDeclarations: true
AlignConsecutiveAssignments: true
AllowShortFunctionsOnASingleLine: None
27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: ci

on:
push:
pull_request:

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Pin clang-format to match the version used locally. shellcheck is
# preinstalled on GitHub-hosted ubuntu runners.
- run: pipx install clang-format==19.1.7
- run: ./ci --action=check

test:
runs-on: ubuntu-latest
steps:
# Submodules (qrcodegen/littlefs) are needed for the coverage build.
- uses: actions/checkout@v4
with:
submodules: recursive
# gcc/make are preinstalled; valgrind + lcov are not.
- run: sudo apt-get update && sudo apt-get install -y valgrind lcov
# Hard gate: ASan+UBSan + Valgrind. Coverage is report-only.
- run: ./ci --check=test --action=check
49 changes: 49 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: pages

# Publishes the host-test coverage HTML report (test/coverage/html/) to GitHub
# Pages. Kept separate from ci.yml so the coverage gate and the publish step do
# not entangle. The report is regenerated on every push to the branches below.
on:
push:
branches: [chore/clang-format, chore/host-tests, master]

# Least privilege for the Pages deployment via the job's OIDC token.
permissions:
contents: read
pages: write
id-token: write

# Serialize deployments; do not cancel an in-flight publish.
concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
# Submodules (qrcodegen/littlefs) are needed for the coverage build.
- uses: actions/checkout@v4
with:
submodules: recursive
# gcc/make are preinstalled; lcov (and valgrind) are not.
- run: sudo apt-get update && sudo apt-get install -y valgrind lcov
# Default action=fix: run harnesses + (re)generate test/coverage/html/.
- run: ./ci --check=test
# Enable Pages via the job token (no admin CLI scope needed).
- uses: actions/configure-pages@v5
with:
enablement: true
- uses: actions/upload-pages-artifact@v3
with:
path: test/coverage/html

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v4
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,9 @@ build

.vscode

*.uf2
*.uf2

# host test artifacts
test/coverage/
*.gcno
*.gcda
2 changes: 1 addition & 1 deletion build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ fi
mkdir -p build
cd build
cmake -DPICO_BOARD=pico_w ..
make -j$(nproc)
make -j"$(nproc)"
cd ..

cp build/hslock.uf2 hslock.uf2
Expand Down
32 changes: 32 additions & 0 deletions ci
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
# Project CI entrypoint. Default: fix formatting locally.
# Use --action=check (dry-run, non-zero on diff) in a git hook / CI.
set -euo pipefail
cd "$(dirname "$0")"

check=lint action=fix
for a in "$@"; do case $a in
--check=*) check=${a#*=} ;;
--action=*) action=${a#*=} ;;
*) echo "usage: ./ci [--check=lint] [--action=fix|check]" >&2; exit 2 ;;
esac; done

srcs() { git ls-files '*.c' '*.h' ':!:libs/**' ':!:version.h'; }
shells() { git ls-files ':!:libs/**' | while read -r f; do
if head -1 -- "$f" | grep -q '^#!.*sh'; then printf '%s\n' "$f"; fi
done; }

case $check.$action in
lint.fix) srcs | xargs -r clang-format -i
shells | xargs -r shellcheck ;; # no autofix; reports only
lint.check) srcs | xargs -r clang-format --dry-run --Werror
shells | xargs -r shellcheck ;;
# Host tests. The hard gate is ASan+UBSan + Valgrind (correctness); coverage
# is report-only (whole-codebase denominator keeps line% intentionally low),
# so it never fails CI.
test.check) make -C test asan
make -C test valgrind
make -C test coverage ;;
test.fix) make -C test coverage ;; # run + (re)gen report
*) echo "unknown --check=$check --action=$action" >&2; exit 2 ;;
esac
4 changes: 2 additions & 2 deletions hardware/buzzer.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

#define BUZZER_PIN 17

#define BUZZER_SHORT_BEEP_DELAY 100
#define BUZZER_SHORT_BEEP_DELAY 100
#define BUZZER_MEDIUM_BEEP_DELAY 500
#define BUZZER_LONG_BEEP_DELAY 2000
#define BUZZER_LONG_BEEP_DELAY 2000

void buzzer_init();
void buzzer_on();
Expand Down
7 changes: 4 additions & 3 deletions hardware/clock.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

uint32_t clock_get_unix_time(void) {
datetime_t dt;
if (!rtc_get_datetime(&dt)) return 0;
if (!rtc_get_datetime(&dt))
return 0;

struct tm t = {
.tm_year = dt.year - 1900,
Expand All @@ -17,12 +18,12 @@ uint32_t clock_get_unix_time(void) {
}

void clock_set_from_unix_time(uint32_t unix_time) {
time_t t = (time_t)unix_time;
time_t t = (time_t)unix_time;
struct tm *utc = gmtime(&t);

datetime_t dt = {
.year = utc->tm_year + 1900,
.month = utc->tm_mon + 1,
.month = utc->tm_mon + 1,
.day = utc->tm_mday,
.dotw = utc->tm_wday,
.hour = utc->tm_hour,
Expand Down
13 changes: 8 additions & 5 deletions hardware/keypad.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
#include "pico/stdlib.h"
#include "pico/time.h"

// clang-format off
// Kept as a 4x4 grid mirroring the physical keypad layout.
static const char KEY_MAP[KEYPAD_ROWS][KEYPAD_COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
// clang-format on

void keypad_init(void) {
// Rows: outputs, default HIGH
Expand Down Expand Up @@ -48,16 +51,16 @@ static char scan_raw(void) {
}

char keypad_get_key(void) {
static char last_raw = 0;
static char last_stable = 0;
static absolute_time_t stable_at = {0};
static char last_raw = 0;
static char last_stable = 0;
static absolute_time_t stable_at = {0};

char raw = scan_raw();

if (raw != last_raw) {
// Reading changed - restart debounce timer
last_raw = raw;
stable_at = make_timeout_time_ms(KEYPAD_DEBOUNCE_MS);
last_raw = raw;
stable_at = make_timeout_time_ms(KEYPAD_DEBOUNCE_MS);
return 0;
}

Expand Down
1 change: 0 additions & 1 deletion hardware/light.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

#include <stdio.h>


void light_init() {
gpio_init(LIGHT_PIN);
gpio_set_dir(LIGHT_PIN, GPIO_OUT);
Expand Down
4 changes: 2 additions & 2 deletions libs/base32/base32.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
static const char B32_CHARS[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

void base32_encode(const uint8_t *in, size_t in_len, char *out) {
int buffer = 0;
int bits_left = 0;
uint32_t buffer = 0;
int bits_left = 0;
size_t out_len = 0;

for (size_t i = 0; i < in_len; i++) {
Expand Down
Loading