Skip to content

Commit

Permalink
[HOTFIX] Add errors to bus operations and updated setup documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
EvanLF6768 committed Jun 13, 2024
1 parent eacc345 commit 65ffcc4
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 24 deletions.
60 changes: 54 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,60 @@
# balloon-obc-401
A flexible on-board-computer for high altitude balloons based on the dual core STM32H7 series running FreeRTOS.

## Setup / Build Instructions
1. Windows -> ARM cross toolchain available [here](https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads).
2. Run `git submodule update --init --remote --recursive`
3. `cmake -DCMAKE_C_COMPILER=... -DCMAKE_CXX_COMPILER=... -Bbuild .`
4. `cmake --build build --config Debug --target all`
5. Profit
## Getting Started
### Dependencies
Before starting you require the following:
- [ARM Toolchain](https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads)
- [CMake](https://cmake.org/download/)
- [Clang](https://releases.llvm.org/download.html)
- [STLINK](https://github.com/stlink-org/stlink)
- [STM32CubeMX](https://www.st.com/content/st_com/en/stm32cubemx.html)

```sh
# Arch Installation
sudo pacman -Syu gcc-arm-none-eabi gcc-arm-none-eabi-newlib clang cmake stlink
paru -S stm32cubemx
```

This repository uses Git submodules for software libraries. The submodules can be downloaded with:
```sh
git submodule update --init --remote --recursive
```

### Building
All commands should be run from the root directory of the project. To build everything run:
```sh
cmake -DCMAKE_C_COMPILER=[C_COMPILER] -DCMAKE_CXX_COMPILER=[CPP_COMPILER] -Bbuild .
cmake --build build --config Debug --target all
```
`[C_COMPILER]` and `[CPP_COMPILER]` must be replaced with with the compiler which corresponds to the hardware being targetted. For local testing this is `gcc` and `g++`, for deployment to STM32 this is `gcc-arm-none-gcc` and `gcc-arm-none-g++`.

### Local Testing
Units tests are run with GTest. To run all tests use CTest (a part of CMake).
```sh
cd build
ctest
```

To debug a test which is failing with GDB the following procedure can be used.
```sh
# Find the command used to run the test to debug, replace [NAME_OF_TEST] with the name or a regex with matches it
ctest -R $[NAME_OF_TEST] -V -N
# Run GDB with the test command eg. gdb obc_m7 "-v" "test0"
gdb [TEST_COMMAND]
```

### Device Testing
Debugging is done with GDB.
```sh
# In a secondary terminal
st-util

# In the primary terminal
arm-none-eabi-gdb build/obc_m7.elf
target extended localhost:4242
load
```

## Multithreading
Given the low level nature of embedded development, we do not have the luxury of symmetric multiprocessing.
Expand Down
57 changes: 39 additions & 18 deletions common/Inc/obc/bus.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@
#include <cassert>
#include <concepts>
#include <cstddef>
#include <expected>
#include <optional>
#include <span>

#include "obc/ipc/callback.hpp"
#include "obc/utils/error.hpp"
#include "obc/utils/handle.hpp"

/**
Expand Down Expand Up @@ -157,9 +159,14 @@ template<typename T, typename M = BasicMessage>
concept ListenBus =
requires(T& bus, ipc::Callback<void, const M&>::DummyProvider&& cb) {
typename T::ListenHandle;
typename T::ListenError;

{ bus.Listen(cb.Func) } -> std::same_as<typename T::ListenHandle>;
} && std::semiregular<typename T::ListenHandle> && Message<M>;
{
bus.Listen(cb.Func)
} -> std::same_as<
std::expected<typename T::ListenHandle, typename T::ListenError>>;
} && std::semiregular<typename T::ListenHandle> &&
utils::MaybeError<typename T::ListenError> && Message<M>;

/**
* @brief A helper class for implementing listening functionality.
Expand All @@ -177,6 +184,7 @@ class ListenBusMixin {

public:
using ListenHandle = utils::Handle<ListenCallback>;
using ListenError = utils::Never;

/**
* @brief Adds a listener to be notified upon receiving a message.
Expand All @@ -185,7 +193,7 @@ class ListenBusMixin {
* @return An opaque handle that must be retained for the listener to remain
* active.
*/
ListenHandle Listen(ListenCallback&& cb) {
std::expected<ListenHandle, ListenError> Listen(ListenCallback&& cb) {
return ListenHandle(m_listeners, std::move(cb));
}

Expand Down Expand Up @@ -226,12 +234,15 @@ concept RequestBus =
ipc::Callback<void, const Res&>::DummyProvider cb, Buf& buf
) {
typename T::RequestHandle;
typename T::RequestError;

{
bus.Request(req, cb.Func, buf)
} -> std::same_as<typename T::RequestHandle>;
} -> std::same_as<
std::expected<typename T::RequestHandle, typename T::RequestError>>;
} &&
std::semiregular<typename T::RequestHandle> && Message<Req> &&
std::semiregular<typename T::RequestHandle> &&
utils::MaybeError<typename T::RequestError> && Message<Req> &&
Message<Res> && Buffer<Buf, BufUnit>;

/**
Expand Down Expand Up @@ -261,7 +272,7 @@ template<
typename T, typename Req = BasicMessage, typename Res = BasicMessage,
typename Flt = ipc::Callback<bool, const Res&>>
concept RequestBusMixinRequirements = requires(T& bus, const Req& req) {
{ bus.IssueRequest(req) } -> std::same_as<Flt>;
{ bus.IssueRequest(req) } -> utils::ExpectedReturn<Flt>;
} && Message<Req> && Message<Res> && MessageFilter<Flt>;

/**
Expand Down Expand Up @@ -290,6 +301,9 @@ class RequestBusMixin {

public:
using RequestHandle = utils::Handle<RequestHandleData>;
using RequestError = decltype(Derived().IssueRequest(
std::declval<const Req&>(), std::declval<const Res&>()
))::error_type;

/**
* @brief Sends a request on the bus.
Expand All @@ -299,14 +313,16 @@ class RequestBusMixin {
* @return An opaque handle that must be retained until the request is
* fulfilled or the response is no longer desired.
*/
RequestHandle Request(const Req& req, RequestCallback&& cb, Buf buf) {
return RequestHandle(
m_request_handlers,
{
.filter = Derived().IssueRequest(req, buf),
.callback = std::move(cb),
}
);
std::expected<RequestHandle, RequestError> Request(
const Req& req, RequestCallback&& cb, Buf buf
) {
if (auto filter = Derived().IssueRequest(req, buf)) {
return RequestHandle(
m_request_handlers, {.filter = filter.value(), .callback = cb}
);
} else {
return std::unexpected(filter.error());
}
}

protected:
Expand Down Expand Up @@ -351,10 +367,14 @@ template<typename T, typename Req = BasicMessage, typename Res = BasicMessage>
concept ProcessBus =
requires(T& bus, ipc::Callback<std::optional<Res>, Req>::DummyProvider cb) {
typename T::ProcessHandle;
typename T::ProcessError;

{ bus.Process(cb.Func) } -> std::same_as<typename T::ProcessHandle>;
} && std::semiregular<typename T::ProcessHandle> && Message<Req> &&
Message<Res>;
{
bus.Process(cb.Func)
} -> std::same_as<
std::expected<typename T::ProcessHandle, typename T::ProcessError>>;
} && std::semiregular<typename T::ProcessHandle> &&
utils::MaybeError<typename T::ProcessError> && Message<Req> && Message<Res>;

/**
* @brief Specifies the required methods for the class \ref ProcessBusMixin is
Expand Down Expand Up @@ -385,6 +405,7 @@ class ProcessBusMixin {

public:
using ProcessHandle = utils::Handle<ProcessCallback>;
using ProcessError = utils::Never;

/**
* @brief Registers a processor callback.
Expand All @@ -394,7 +415,7 @@ class ProcessBusMixin {
* @return An opaque handle that must be retained for the processor to
* remain active.
*/
ProcessHandle Process(ProcessCallback&& cb) {
std::expected<ProcessHandle, ProcessError> Process(ProcessCallback&& cb) {
return ProcessHandle(m_processors, std::move(cb));
}

Expand Down
8 changes: 8 additions & 0 deletions common/Inc/obc/port.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

#include <iterator>

#include "utils/error.hpp"

/**
* @brief Defines a common interface for IO ports.
*
Expand All @@ -33,6 +35,12 @@
* used to signal intent, however there may be additional constraints placed on
* them in future.
*
* Ports should be able to source or sink an unlimited amount of data. Reaching
* the end of the iterator signals some sort of error state. The error can be
* queried from the iterator.
*
* @todo Timeouts and error states
*
* @warning Data is only pulled/pushed to the port upon calling the increment
* operator, this operation may block for an unspecified duration.
*
Expand Down
37 changes: 37 additions & 0 deletions common/Inc/obc/utils/error.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#include <concepts>
#include <expected>

#include "utils/meta.hpp"

namespace obc::utils {
/**
* @brief A class which implements the error interface or
* is the special Never type (ie. not an error).
*
* @todo Implment the error interface, currently only `Never`
* is valid.
*
* @warning MaybeError is not a valid return type for a function
* which may fail or return nothing. You probably want to use
* `std::optional<Error>` instead.
*/
template<typename T>
concept MaybeError = std::same_as<T, Never>;

template<typename T, typename R>
concept ExpectedReturn =
Specializes<T, std::expected> && MaybeError<typename T::error_type> &&
std::convertible_to<T, std::expected<R, typename T::error_type>>;

#define UNWRAP_ASSIGN_IMPL(lhs, rexpr, temp) \
auto temp = rexpr; \
static_assert(obc::utils::ExpectedReturn<std::remove_cvref<temp>>); \
if constexpr (!std::same_as< \
std::remove_cvref<temp>::error_type, \
obc::utils::Never>) { \
if (!temp) return std::unexpected(std::move(temp).error()); \
} \
lhs = std::move(temp.value());

#define UNWRAP_ASSIGN(lhs, rexpr) UNWRAP_ASSIGN_IMPL(lhs, rexpr, TEMP_NAME)
} // namespace obc::utils
35 changes: 35 additions & 0 deletions common/Inc/obc/utils/meta.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,26 @@ concept OptionLike = requires(T t) {
{ *t } -> std::convertible_to<U>;
};

/**
* @brief An appoximation of a bottom type.
*
* Bottom types are used to represent values which cannot exist in contexts
* where a type must be specified. In other words, a type for which there
* are no valid values.
*
* Unlike a true bottom type (like those in Rust), this cannot be cast into
* any other type. This was an intentional design choice to prevent the
* accidental creation and
*
* This type can be privately subclassed by other marker types to prevent
* construction and use.
*/
struct Never {
Never() = delete;
Never(const Never&) = delete;
Never& operator=(const Never&) = delete;
};

/**
* @brief Check if a predicate is true for all elements of the zip of the
* element types of tuples.
Expand Down Expand Up @@ -68,4 +88,19 @@ concept AllPairsConvertable = MultiwayConjunctionV<std::is_convertible, F, T>;
*/
template<typename... Ts>
class TypeAmalgam : public Ts... {};

template<typename T, template<typename...> class Z>
struct is_specialization_of : std::false_type {};

template<typename... Args, template<typename...> class Z>
struct is_specialization_of<Z<Args...>, Z> : std::true_type {};

template<typename T, template<typename...> class Z>
inline constexpr bool is_specialization_of_v =
is_specialization_of<T, Z>::value;

template<typename T, template<typename...> class Z>
concept Specializes = is_specialization_of_v<T, Z>;

#define TEMP_NAME temp_##__COUNTER__
} // namespace obc::utils

0 comments on commit 65ffcc4

Please sign in to comment.