Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
32 changes: 32 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Build check only: does the firmware still compile.
# Runs the real embedded build (ARM cross-compiler + the project's own
# CMake preset), not just a host-side syntax check, so this catches
# anything that would break the actual firmware build.

name: Build

on:
push:
branches: [main]
pull_request:

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4

- name: Install Ninja
run: sudo apt-get update && sudo apt-get install -y ninja-build

- name: Install ARM GCC toolchain
uses: carlosperate/arm-none-eabi-gcc-action@v1
with:
release: '13.2.Rel1'

- name: Configure (CMake Debug preset)
run: cmake --preset Debug

- name: Build
run: cmake --build --preset Debug
47 changes: 47 additions & 0 deletions Docs/state-machine-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,12 +179,59 @@ case is now about as thoroughly tested as a single state can be.
Grouped `main`'s calls under a `//IDLE state tests` comment — will need a
matching comment per state as Ready/Running/Charging/Fault tests get added.

### CI added

`.github/workflows/build.yml`: runs on every push to `main` and every PR.
Installs the real `arm-none-eabi-gcc` toolchain + `ninja`, then runs
`cmake --preset Debug` / `cmake --build --preset Debug` — the project's own
existing preset, not a workaround. This is the real embedded build, not the
host-side `cc` checks used locally — catches anything that breaks the actual
firmware, for anyone's code, not just mine.

### Next: Ready, Running, Charging, Fault, and the retry timer

Still need: Ready's other two branches (→ Charging, → Fault), Running/Charging
→ Fault, Fault → Idle on reset, and the retry-timer countdown (does it
actually decrement, and reset back to 10 after hitting 0).

### Ready fully covered

Five tests: → Charging, → Running, and three Fault variants (both readings
bad, voltage bad alone, temp bad alone) — same rigor as Idle's sensor checks,
applied to Ready's `voltagesGood && tempsGood` condition. Also scaffolded empty
`//RUNNING`, `//CHARGING`, `//FAULT` section headers in `main` ahead of writing
those tests. 18 tests total, all passing.

State-machine branch got merged into `main` mid-session (PR #3), then a second
PR (#5) added the CI workflow and merged `hardware_test`'s FreeRTOS changes
back in — real embedded build passed on GitHub Actions, not just the host-side
`cc` checks used locally.

### Running and Charging fully covered

Six tests, same shape as each other since both states check the same
`voltagesGood && tempsGood` condition with no other branching: stays in state
when good, Fault when either reading is bad. 24 tests total, all passing.

### Fault fully covered — all five states now tested

Two tests: → Idle on `resetRequested`, and stays Fault when not requested. The
second one matters most — it's the test that actually proves the latching
rule (rules require the shutdown circuit stay open until a manual reset;
nothing else may clear it).

**26 tests total, all passing. Every state's transitions are now covered.**

Not yet committed/pushed — sitting locally on `state-machine`.

### Next

- Commit and push the finished test suite.
- Wire `Control_Step` into the real `ControlTask` in `main.c`, on a 10ms
period, with placeholder inputs (real sensor data still depends on someone
building the Data Acquisition Thread's driver).
- Raise the open questions above with the team.

## C syntax notes

A running glossary of C concepts, added as I actually use them — not a general C
Expand Down
209 changes: 209 additions & 0 deletions tests/test_control.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include <stdio.h>
#include "include/control.h"

//IDLE

static void test_idle_stays_idle_when_nothing_connected(void) {
BmsContext ctx = { .state = BMS_STATE_IDLE, .idleRetryTicks = 10 };
BmsInputs inputs = { 0 };
Expand Down Expand Up @@ -219,6 +221,192 @@ static void test_idle_retry_timer_wraps_around_to_ten_when_no_can(void) {

}

//READY

static void test_ready_moves_to_charging_when_good_and_charging_requested(void){
BmsContext ctx = { .state = BMS_STATE_READY};
BmsInputs inputs = {
.voltagesGood = true,
.tempsGood = true,
.chargingRequested = true,
};
BmsOutputs outputs;

Control_Step(&ctx, &inputs, &outputs);

assert(ctx.state == BMS_STATE_CHARGING);

}

static void test_ready_moves_to_running_when_good_and_charging_not_requested(void){
BmsContext ctx = { .state = BMS_STATE_READY};
BmsInputs inputs = {
.voltagesGood = true,
.tempsGood = true,
.chargingRequested = false,
};
BmsOutputs outputs;

Control_Step(&ctx, &inputs, &outputs);

assert(ctx.state == BMS_STATE_RUNNING);
}

static void test_ready_moves_to_fault_when_readings_not_good(void){
BmsContext ctx = { .state = BMS_STATE_READY};
BmsInputs inputs = {
.voltagesGood = false,
.tempsGood = false,
.chargingRequested = true,
};
BmsOutputs outputs;

Control_Step(&ctx, &inputs, &outputs);

assert(ctx.state == BMS_STATE_FAULT);
}

static void test_ready_moves_to_fault_when_voltage_readings_not_good(void){
BmsContext ctx = { .state = BMS_STATE_READY};
BmsInputs inputs = {
.voltagesGood = false,
.tempsGood = true,
.chargingRequested = true,
};
BmsOutputs outputs;

Control_Step(&ctx, &inputs, &outputs);

assert(ctx.state == BMS_STATE_FAULT);
}

static void test_ready_moves_to_fault_when_temp_readings_not_good(void){
BmsContext ctx = { .state = BMS_STATE_READY};
BmsInputs inputs = {
.voltagesGood = true,
.tempsGood = false,
.chargingRequested = false,
};
BmsOutputs outputs;

Control_Step(&ctx, &inputs, &outputs);

assert(ctx.state == BMS_STATE_FAULT);
}


//RUNNING

static void test_running_stays_running_when_good(void){
BmsContext ctx = { .state = BMS_STATE_RUNNING};
BmsInputs inputs = {
.voltagesGood = true,
.tempsGood = true,
};
BmsOutputs outputs;

Control_Step(&ctx, &inputs, &outputs);

assert(ctx.state == BMS_STATE_RUNNING);
}

static void test_running_moves_to_fault_when_voltages_not_good(void){
BmsContext ctx = { .state = BMS_STATE_RUNNING};
BmsInputs inputs = {
.voltagesGood = false,
.tempsGood = true,
};
BmsOutputs outputs;

Control_Step(&ctx, &inputs, &outputs);

assert(ctx.state == BMS_STATE_FAULT);

}

static void test_running_moves_to_fault_when_temps_not_good(void){
BmsContext ctx = { .state = BMS_STATE_RUNNING};
BmsInputs inputs = {
.voltagesGood = true,
.tempsGood = false,
};
BmsOutputs outputs;

Control_Step(&ctx, &inputs, &outputs);

assert(ctx.state == BMS_STATE_FAULT);
}

//CHARGING

static void test_charging_stays_charging_when_good(void){
BmsContext ctx = { .state = BMS_STATE_CHARGING};
BmsInputs inputs = {
.voltagesGood = true,
.tempsGood = true,
};
BmsOutputs outputs;

Control_Step(&ctx, &inputs, &outputs);

assert(ctx.state == BMS_STATE_CHARGING);
}

static void test_charging_moves_to_fault_when_voltages_not_good(void){
BmsContext ctx = { .state = BMS_STATE_CHARGING};
BmsInputs inputs = {
.voltagesGood = false,
.tempsGood = true,
};
BmsOutputs outputs;

Control_Step(&ctx, &inputs, &outputs);

assert(ctx.state == BMS_STATE_FAULT);

}

static void test_charging_moves_to_fault_when_temos_not_good(void){
BmsContext ctx = { .state = BMS_STATE_CHARGING};
BmsInputs inputs = {
.voltagesGood = true,
.tempsGood = false,
};
BmsOutputs outputs;

Control_Step(&ctx, &inputs, &outputs);

assert(ctx.state == BMS_STATE_FAULT);
}

//FAULT

static void test_fault_moves_to_idle_when_reset_requested(void){
BmsContext ctx = { .state = BMS_STATE_FAULT};
BmsInputs inputs = {
.resetRequested = true,
};
BmsOutputs outputs;

Control_Step(&ctx, &inputs, &outputs);

assert(ctx.state == BMS_STATE_IDLE);
}

static void test_fault_stays_fault_when_reset_not_requested(void){
BmsContext ctx = { .state = BMS_STATE_FAULT};
BmsInputs inputs = {
.resetRequested = false,
};
BmsOutputs outputs;

Control_Step(&ctx, &inputs, &outputs);

assert(ctx.state == BMS_STATE_FAULT);

}


int main(void) {

//IDLE state tests
Expand All @@ -236,6 +424,27 @@ int main(void) {
test_idle_retry_timer_wraps_around_to_ten_when_no_isoSPI();
test_idle_retry_timer_wraps_around_to_ten_when_no_can();

//READY state tests
test_ready_moves_to_charging_when_good_and_charging_requested();
test_ready_moves_to_running_when_good_and_charging_not_requested();
test_ready_moves_to_fault_when_readings_not_good();
test_ready_moves_to_fault_when_voltage_readings_not_good();
test_ready_moves_to_fault_when_temp_readings_not_good();

//RUNNING state tests
test_running_stays_running_when_good();
test_running_moves_to_fault_when_voltages_not_good();
test_running_moves_to_fault_when_temps_not_good();

//CHARGING state tests
test_charging_stays_charging_when_good();
test_charging_moves_to_fault_when_voltages_not_good();
test_charging_moves_to_fault_when_temos_not_good();

//FAULT state tests
test_fault_moves_to_idle_when_reset_requested();
test_fault_stays_fault_when_reset_not_requested();

printf("All tests passed.\n");
return 0;

Expand Down
Loading