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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
16 changes: 16 additions & 0 deletions .github/workflows/build_and_test_common_libs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Build and Test common libraries
on: push
jobs:
setup_and_build_check:
runs-on: ubuntu-22.04
steps:
- name: checkout repo with submoudles
uses: actions/checkout@v4
with:
submodules: true # clone submodules recursively
fetch-depth: 0 # ensures full history (optional but good practice)
- name: build + test
run: |
cd common_libraries/
make test

11 changes: 6 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -380,13 +380,14 @@ MigrationBackup/
FodyWeavers.xsd

# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
**/.vscode/*
# !.vscode/settings.json
# !.vscode/tasks.json
# !.vscode/launch.json
# !.vscode/extensions.json
*.code-workspace


# Local History for Visual Studio Code
.history/

Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "external_libs/Catch2"]
path = external_libs/Catch2
url = https://github.com/catchorg/Catch2.git
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,37 @@
# FirmFlake
Embedded Systems Repository for UBC Rover

This contains all our firmware and embedded code. From both elec and software.

Some build code is committed, which will likely change soon. But we don't have a setup script for zephyr, and use teensy tools to flash our old arm, so by having build code, we don't need to install zephyr on all our computers.

### CONTRIBUTING
Currently, we protect main branch by PR approval and an automated test of our common libraries.
More automated tests will follow.


### ZephyrRTOS
We have some zephyr rtos projects under zephyr_projects. To build/develop them you will need to install zephyr: https://docs.zephyrproject.org/latest/develop/getting_started/index.html

### Arduino
We have some arduino projects, under arduino_firmware.

### common libraries
Under common_libs/ we have our common libraries. These should be hardware agnostic, modular and testable.
We have an example here, that shows the general workflow for creating a common library module, including Catch2 testing.

### Testing
We use Catch2 for testing cpp and c code. It's an easy to use, and easy to integrate tool for cpp unit tests.
Ideally, all our code has test coverage. Test driven development (TDD) is a powerful process where if done perfectly, you never push a bug that would impact system operations, because your tests would cover every needed operation of the system.

Embedded systems development's biggest difference to regular software is that hardware is always in the loop, and this makes debugging harder. "is it the code or the hardware? is this my problem or elec's problem?" With really good test code, we can blame more things on elec!!

Catch2 docs are in it's readme: https://github.com/catchorg/Catch2?tab=readme-ov-file

A good book on TDD is Test Driven Development for Embedded Systems: https://pragprog.com/titles/jgade/test-driven-development-for-embedded-c/

### ROS2 Tests
We have some ros2 code in here, specifically for testing firmware. Might be moved to RoverFlake2 in the future.

### End Notes
The filestructure is currently based around development enviroments, we may change this to project based structure in the future.
44 changes: 44 additions & 0 deletions common_libraries/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# CMake file for exporting common libraries
cmake_minimum_required(VERSION 3.20)

project(common_lib_tests)
# This is not a project, but the tests are a project.

set(CMAKE_CXX_STANDARD 17) # cpp 2017 standard
set(CMAKE_CXX_STANDARD_REQUIRED True)

add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../external_libs/Catch2 ${CMAKE_CURRENT_BINARY_DIR}/external_libs/Catch2) # Include Catch2 for unit tests

set(BUILD_DIR ${CMAKE_CURRENT_LIST_DIR}/build)

set(COMMON_LIB_SOURCES
${CMAKE_CURRENT_LIST_DIR}/example/some_lib.cpp

# Autogenerated files:
# ${BUILD_DIR}/autogen/*.cpp
# ${BUILD_DIR}/autogen/*.c
)

add_library(common_lib STATIC ${COMMON_LIB_SOURCES})

target_include_directories(common_lib PUBLIC
${CMAKE_CURRENT_LIST_DIR}/example/
)

add_executable(common_lib_tests
${CMAKE_CURRENT_LIST_DIR}/example/test/test_some_lib.cpp)


target_link_libraries(common_lib_tests PRIVATE
common_lib
Catch2::Catch2WithMain)

target_compile_options(common_lib_tests PRIVATE
-Wall
-Wextra
# -Werror # Uncomment to be verry strict
)

include(CTest)
include(Catch)
catch_discover_tests(common_lib_tests)
21 changes: 21 additions & 0 deletions common_libraries/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Makefile for common libraries

BUILD_DIR:=build

all: clean configure build test

configure:
@mkdir -p $(BUILD_DIR)
@cd $(BUILD_DIR) && cmake ..

build: configure
@$(MAKE) -C $(BUILD_DIR)

clean:
@rm -rf $(BUILD_DIR)

test: configure build
@${BUILD_DIR}/common_lib_tests


.PHONY: all configure build clean test
8 changes: 8 additions & 0 deletions common_libraries/crc/inc/crc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#pragma once
#include <stdint.h>


static inline uint8_t crc8(uint8_t crc, const uint8_t *data, uint32_t length)
{
//TODO if we need it
}
44 changes: 44 additions & 0 deletions common_libraries/example/some_lib.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include "some_lib.h" // If path is direct/relative to this file
// #include <some_lib.h> // If the path is linked only through cmake

// Don't worry about why anyone would use this library. its just gibberish to serve as an example


bool SomeLib::double_int32(const uint32_t& input, uint32_t& output_dst)
{
bool ret = false; // ret is a common variable name for the return value

if(input & 0b1000'0000'0000'0000 != 0x0000)
{
// Input is too big to fit in an uint32
ret = false;
this->prv_update_status(ret);
return ret; // Early return
} else
{
ret = true;
this->prv_update_status(ret);
}

output_dst = input << 1;
return ret;
}

const internal_status_t& SomeLib::get_status() const
{
return this->internal_status;
}

void SomeLib::prv_update_status(bool operation_was_successfull)
{
this->internal_status.num_operations++;

if (operation_was_successfull)
{
this->internal_status.num_successes++;
} else
{
this->external_object = EXTERNAL_OBJECT_FAILURE_VALUE;
this->internal_status.num_failures++;
}
}
31 changes: 31 additions & 0 deletions common_libraries/example/some_lib.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#pragma once // ensures this file is not included multiple times (similar to #IFNDEF SOME_LIB_H)
#include <stdint.h> // Whatever includes this library needs

#define EXTERNAL_OBJECT_FAILURE_VALUE 69


typedef struct internal_status {
uint32_t num_successes = 0;
uint32_t num_failures = 0;
uint32_t num_operations = 0;
} internal_status_t;

class SomeLib {
public:
SomeLib(uint32_t& external_object) : external_object(external_object){}; // Constructor, with external refrence input
~SomeLib() = default; // Deconstructor


bool double_int32(const uint32_t& input, uint32_t& output_dst);

const internal_status_t& get_status() const; // Read only getter that does not copy AND does not change internal variables, while still needing this->.


private:

internal_status_t internal_status = {}; // Private instantiation of internal status

uint32_t &external_object;

void prv_update_status(bool operation_was_successfull); // Private helper function to update internal status. Cannot be static as it needs `this` pointer
};
51 changes: 51 additions & 0 deletions common_libraries/example/test/test_some_lib.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#define CATCH_CONFIG_MAIN // Let catch2 handle the main function and boiler plate code.
#include <catch2/catch_all.hpp>

// Code under test
#include <some_lib.h>

#define EXTERNAL_OBJECT_INIT_DEFAULT 420
#define VALID_INT32_TO_DOUBLE 100

TEST_CASE("test_some_lib")
{
uint32_t some_external_object = EXTERNAL_OBJECT_INIT_DEFAULT;
SomeLib lib_instance(some_external_object);

SECTION("test_valid_double") // "Happy path" test -> Make sure the code works with valid inputs
{
// Setup objects
uint32_t my_int = VALID_INT32_TO_DOUBLE;
uint32_t my_doubled_int = 0;

// "inject" data / call functions
bool ret = lib_instance.double_int32(my_int, my_doubled_int);
internal_status_t status = lib_instance.get_status();

// Assert outputs
REQUIRE(ret == true);
REQUIRE(my_int == VALID_INT32_TO_DOUBLE); // my_int should not of changed
REQUIRE(status.num_failures == 0);
REQUIRE(status.num_operations == 1);
REQUIRE(status.num_successes == 1);
REQUIRE(some_external_object == EXTERNAL_OBJECT_INIT_DEFAULT);
REQUIRE(my_doubled_int == 2*my_int);
}

SECTION("test_invalid_double") // "unhappy path" test -> Make sure the code raises errors with invalid inputs / Make sure the code fails successfully
{
uint32_t my_int = -1; // Max out uint32
uint32_t my_doubled_int = 99;

bool ret = lib_instance.double_int32(my_int, my_doubled_int);
internal_status_t status = lib_instance.get_status();

REQUIRE(ret == false);
REQUIRE(status.num_failures == 1);
REQUIRE(status.num_operations == 1);
REQUIRE(status.num_successes == 0);
REQUIRE(some_external_object == EXTERNAL_OBJECT_FAILURE_VALUE); // on error, external object should be changed. Idk why
REQUIRE(my_doubled_int == 99); // dst object should not change
}

}
1 change: 1 addition & 0 deletions external_libs/Catch2
Submodule Catch2 added at 33e6fd
Binary file removed samples-and-testing/echo_bot/build/.ninja_deps
Binary file not shown.
Loading