diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..d5e666e --- /dev/null +++ b/.github/workflows/build.yml @@ -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 diff --git a/Docs/state-machine-notes.md b/Docs/state-machine-notes.md index 43aa5fd..2d055b6 100644 --- a/Docs/state-machine-notes.md +++ b/Docs/state-machine-notes.md @@ -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 diff --git a/tests/test_control.c b/tests/test_control.c index c93bba0..f3000b3 100644 --- a/tests/test_control.c +++ b/tests/test_control.c @@ -14,6 +14,8 @@ #include #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 }; @@ -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 @@ -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;