Aller au contenu

Project Specification

The project specification is described in the following sections.

Constraints

  • The code delivered to github.com compiles without error and can be flashed to the target device.
  • The program must run without error on the target device.
  • Any major deviation from these rules may lead to the project not being considered for the lecture grade.

Phase 1: Project infrastructure

  • The project code is based on the Getting Started codelab. It thus only implements the basic blinky behavior.
  • The project infrastructure (e.g. tools including precommit) is fully developed and deployed to github, as described in the Improving Software Quality codelab.
  • The test programs developed in the Testing MbedOS codelab are added to github and the CI/CD pipeline is properly configured. Pipeline jobs run successfully.

Expected Deliverables

  • Deliverable: Your GitHub repository exists and has been shared. It follows all rules and directives defined under project organization.
  • Deliverable: The Github workflow is configured correctly and runs without error. It includes the build of the “ptr-test” program that you have developed. The “ptr-test” program includes the tests described in the unique_ptr and raw pointers exercises.
  • Deliverable: pre-commit has been configured properly and all pre-commit hooks run successfully. All configuration files have been added to your Github repository.
  • Important: Code developed for the “BikeComputer part 1 & 2” codelab is NOT part yet of Phase 1. It should be committed and pushed to a separate branch of your Github repository. It is expected that the “main” branch contains only the Phase 1 deliverables.

Phase 2: BikeComputer: static cyclic and event-driven implementation

  • Phase 2 is based on Phase 1. Every specification below is in addition to Phase 1 specifications. If issues have been added to your github repository, these must be solved and closed as part of Phase 2. Issues will be added at latest 2 weeks prior to the deadline.
  • The BikeComputer program must be delivered in the following versions:

    • static cyclic scheduling: in the folder “static_scheduling”, under the namespace static_scheduling, the BikeSystem class implements static cyclic scheduling in the BikeSystem::start() method. This is documented in the BikeComputer part 1 codelab. In this implementation, busy waits are replaced by ThisThread::sleep_for() calls as documented in BikeComputer part 2 codelab
    • static cyclic scheduling with EventQueue: in the folder “static_scheduling”, under the namespace static_scheduling, the BikeSystem class implements static cyclic scheduling using the Mbed OS EventQueue API (in the BikeSystem::startWithEventQueue() method). This is documented in the BikeComputer part 2 codelab.
    • static cyclic scheduling with event handling: in the folder “static_scheduling_with_event”, under the namespace static_scheduling_with_event, the BikeSystem class implements static cyclic scheduling using the Mbed OS EventQueue API for periodic tasks and implements event handling using interrupts for the button and the joystick. The behavior is implemented in the BikeSystem::start() method and is documented in the BikeComputer part 2 codelab.

Expected Deliverables

  • Deliverable: Answers to the 4 questions asked in the BikeComputer part II codelab are given and documented in the README file of your project.

  • Deliverable: CPU logging must be disabled in test mode using the #if !MBED_TEST_MODE pragma.

  • Deliverable: the code for the three scenarios described above has been pushed to the “main” branch of your Github repository. Any code that implements mechanisms that are not part of phase 1 and 2 must be pushed to a different branch.

  • Deliverable: the code passes all test cases of the “TESTS/bike-computer/bike-system” test program successfully. The different test cases are described in the related codelabs. For the sake of clarity, the entire “bike-system” test program is given below. The “sensor-device” and “speedometer” test programs are given in the related codelabs. After pulling your repository, the command mbed test -m DISCO_H747I -t GCC_ARM -n tests-bike-computer-bike-system,tests-bike-computer-sensor-device,tests-bike-computer-speedometer --compile --run --clean must deliver the expected results (all tests pass) without any further change.

  • Deliverable: the CPU usage is optimized for every scenario:

    • When polling for button and joystick states, busy wait calls are replaced by sleep calls and CPU usage drops from around 99% to around 75%.
    • When implementing periodic tasks by using the Mbed OS EventQueue API, CPU usage remains around 75%.
    • When handling button and joystick with ISR, CPU usage drops from around 75% to 1%.
  • Deliverable: the “DISCO_H747I” and “advembsof” libraries are updated to the latest versions.

BikeComputer test program
TESTS/bike-computer/bike-system/main.cpp
// Copyright 2022 Haute école d'ingénierie et d'architecture de Fribourg
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/****************************************************************************
 * @file main.cpp
 * @author Serge Ayer <serge.ayer@hefr.ch>
 *
 * @brief Bike computer test suite: scheduling
 *
 * @date 2023-08-26
 * @version 0.1.0
 ***************************************************************************/

#include <chrono>

#include "greentea-client/test_env.h"
#include "mbed.h"
#include "static_scheduling/bike_system.hpp"
#include "static_scheduling_with_event/bike_system.hpp"
#include "task_logger.hpp"
#include "unity/unity.h"
#include "utest/utest.h"

using namespace utest::v1;

// test_bike_system handler function
static void test_bike_system() {
    // create the BikeSystem instance
    static_scheduling::BikeSystem bikeSystem;

    // run the bike system in a separate thread
    Thread thread;
    thread.start(callback(&bikeSystem, &static_scheduling::BikeSystem::start));

    // let the bike system run for 20 secs
    ThisThread::sleep_for(20s);

    // stop the bike system
    bikeSystem.stop();

    // check whether scheduling was correct
    // Order is kGearTaskIndex, kSpeedTaskIndex, kTemperatureTaskIndex,
    //          kResetTaskIndex, kDisplayTask1Index, kDisplayTask2Index
    constexpr std::chrono::microseconds taskComputationTimes[] = {
        100000us, 200000us, 100000us, 100000us, 200000us, 100000us};
    constexpr std::chrono::microseconds taskPeriods[] = {
        800000us, 400000us, 1600000us, 800000us, 1600000us, 1600000us};

    // allow for 2 msecs offset
    uint64_t deltaUs = 2000;
    for (uint8_t taskIndex = 0; taskIndex < advembsof::TaskLogger::kNbrOfTasks;
         taskIndex++) {
        TEST_ASSERT_UINT64_WITHIN(
            deltaUs,
            taskPeriods[taskIndex].count(),
            bikeSystem.getTaskLogger().getPeriod(taskIndex).count());
        TEST_ASSERT_UINT64_WITHIN(
            deltaUs,
            taskComputationTimes[taskIndex].count(),
            bikeSystem.getTaskLogger().getComputationTime(taskIndex).count());
    }
}

// test_bike_system_event_queue handler function
static void test_bike_system_event_queue() {
    // create the BikeSystem instance
    static_scheduling::BikeSystem bikeSystem;

    // run the bike system in a separate thread
    Thread thread;
    thread.start(
        callback(&bikeSystem, &static_scheduling::BikeSystem::startWithEventQueue));

    // let the bike system run for 20 secs
    ThisThread::sleep_for(20s);

    // stop the bike system
    bikeSystem.stop();

    // check whether scheduling was correct
    // Order is kGearTaskIndex, kSpeedTaskIndex, kTemperatureTaskIndex,
    //          kResetTaskIndex, kDisplayTask1Index, kDisplayTask2Index
    // When we use the event queue, we do not check the computation time
    constexpr std::chrono::microseconds taskPeriods[] = {
        800000us, 400000us, 1600000us, 800000us, 1600000us, 1600000us};

    // allow for 2 msecs offset (with EventQueue)
    uint64_t deltaUs = 2000;
    for (uint8_t taskIndex = 0; taskIndex < advembsof::TaskLogger::kNbrOfTasks;
         taskIndex++) {
        TEST_ASSERT_UINT64_WITHIN(
            deltaUs,
            taskPeriods[taskIndex].count(),
            bikeSystem.getTaskLogger().getPeriod(taskIndex).count());
    }
}

// test_bike_system_with_event handler function
static void test_bike_system_with_event() {
    // create the BikeSystem instance
    static_scheduling_with_event::BikeSystem bikeSystem;

    // run the bike system in a separate thread
    Thread thread;
    thread.start(callback(&bikeSystem, &static_scheduling_with_event::BikeSystem::start));

    // let the bike system run for 20 secs
    ThisThread::sleep_for(20s);

    // stop the bike system
    bikeSystem.stop();

    // check whether scheduling was correct
    // Order is kGearTaskIndex, kSpeedTaskIndex, kTemperatureTaskIndex,
    //          kResetTaskIndex, kDisplayTask1Index, kDisplayTask2Index
    // When we use event handling, we do not check the computation time
    constexpr std::chrono::microseconds taskPeriods[] = {
        800000us, 400000us, 1600000us, 800000us, 1600000us, 1600000us};

    // allow for 2 msecs offset (with EventQueue)
    uint64_t deltaUs = 2000;
    for (uint8_t taskIndex = 0; taskIndex < advembsof::TaskLogger::kNbrOfTasks;
         taskIndex++) {
        TEST_ASSERT_UINT64_WITHIN(
            deltaUs,
            taskPeriods[taskIndex].count(),
            bikeSystem.getTaskLogger().getPeriod(taskIndex).count());
    }
}

static utest::v1::status_t greentea_setup(const size_t number_of_cases) {
    // Here, we specify the timeout (60s) and the host test (a built-in host test or the
    // name of our Python file)
    GREENTEA_SETUP(180, "default_auto");

    return greentea_test_setup_handler(number_of_cases);
}

// List of test cases in this file
static Case cases[] = {
    Case("test bike system", test_bike_system),
    Case("test bike system with event queue", test_bike_system_event_queue),
    Case("test bike system with event", test_bike_system_with_event)};

static Specification specification(greentea_setup, cases);

int main() { return !Harness::run(specification); }

Bug Fixes from Phase 1

After you delivered the Phase 1 of the project, a few issues related to your project may have been created. These will need to be fixed within Phase 2.

In order to fix issues orderly, the proper way to do so is to create a dedicated branch per issue and only commit the fix(es) related to that issue on that branch. Once the fix is made available and approved, the branch created for that purpose is deleted. Below you find a description how you shall go about it.

Under the github project you created, under Issues, chose the issue you intend to address and :

  1. Define who the issue is assigned to with the field Assignees
  2. Declare what type of issue it is Labels (note: choose the correct type)
  3. Potentially define at what milestone the issue should be fixed. In your case add Phase 2 as milestone
  4. Create a dedicated branch for fixing the particular issue Afterwards you can pick the newly created branch to fix the issues with the following commands
      git fetch origin
      git checkout 2-testing-guthub-functions-mispelled
    
  5. Go about fixing the issue and commit to the newly created branch
      git add README.md
      git commit -m "fix: corrected github mispell"
    
    Or (as the example was fixed directly in github)
  6. In order to protect the quality of the main branch, usually it is protected. Protected in this context means that no changes are accepted without application of the four eye principle (see at the bottom of the page for a definition).

    In order to activate the protection, you need to :

    • go to your repository Settings and click on Branches
    • there you need to choose Branch protection rules and Add rule
    • once the Add rule page opens up, fill in the information as per the following picture:
  7. When are finished, you should be able to see the following:

  8. Once the fix is available, you shall create a pull request

  9. When you are creating the pull request, you shall define at least one reviewer. Normally, at least the issue issuer shall be invited to review the correction.
  10. After the reviewer has approved the change, for as long as there are no conflicts, the merge can occur into main. It can even do automatically if one chooses so (Enable auto-merge).
  11. When the merge has been done, you should delete the specific branch created for fixing the issue (see Delete branch in picture below)

Note

Make sure you replace 2-testing-guthub-functions-mispelled in the example above with the correct branch when you run above commands.

Warning

Once the branch protection is installed, it would make sense to use dedicated branches also for feature development. Not only is this good practice, but it also works for everyone - contrary to bypassing branch protection that only works for administrators.

Quote - Four eyes principle

The Four eyes principle is a requirement that two individuals approve some action before it can be taken. The Four eyes principle is sometimes called the two-man rule or the two-person rule.

Source: https://cros-legacy.ec.europa.eu/content/four-eyes-principle_en#:~:text=The%20Four%20eyes%20principle%20is,or%20the%20two%2Dperson%20rule

Phase 3: BikeComputer: multi-tasking and bootloader integration

In Phase 3, you must deliver a version of the BikeComputer program with the following requirements:

  • The version of the BikeComputer program delivered in Phase 2 is still functional.
  • The multi-tasking version of the BikeComputer described in the Bike computer part 3 codelab is made available and functional.
  • When the BikeComputer program starts, a runtime memory analysis is performed, as described in the memory analysis codelab. The analysis includes both heap and stack analysis and the statistics is printed for all threads created by the program. A memory analysis is then launched at a regular time interval and the statistics show that the heap and the stack sizes do not grow any more after the warmup phase.
  • The BikeComputer program implements a bootloader as documented in the bootloader codelab. The update process is fully functional based on the following requirements:
    • Candidate application updates can be downloaded to the target device while the BikeComputer program is running.
    • The maximum number of candidate applications can be stored on the target device, depending on the candidate application size and the available Flash memory. A new firmware is downloaded to a slot that is not containing any candidate application or to the slot that contains the oldest candidate application.
    • At boot time, if a candidate application is present, it is copied to the active application slot, before the BikeComputer program starts. After installation of the new application, the system can be resetted and the updated BikeComputer is ultimately launched.
    • At boot time, if several candidate applications are present, the newest application must be chosen.
    • A candidate application might be installed (copied to the active application slot) only if it is newer than the active application.
    • The candidate or active applications must be validated before they are installed or launched.

Expected Deliverables

  • Deliverable: All requirements described above are met.

  • Deliverable: The deliverables described in the bike computer part 3 codelab are parts of the project deliverables.

  • Deliverable: An answer to the question asked in the bike computer part 3 codelab is given and documented in the README file of your project.

  • Deliverable: The bootloader application is delivered in a sub-folder named “bootloader” in your github repository. You must make sure of the following:

    • Your BikeComputer program “.mbedignore” file is configured for ignoring this subfolder.
    • Your BikeComputer program must compile properly when the bootloader is located as a subfolder in the root directory of the application. It must also be configured for integrating the bootloader binary properly.
    • Your bootloader application must compile properly if you compile it using the “mbed compile -m DISCO_H747I -t ARMC6” command in a Mbed Studio terminal window.
  • Deliverable: the “DISCO_H747I” and “advembsof” libraries are updated to the latest versions.

Bug Fixes from Phase 2

After you delivered the Phase 2 of the project, a few issues related to your project may have been created. These will need to be fixed within Phase 3. The procedure for fixing bugs is the same as the one documented for Phase 2.