Prepare files for public release

This commit is contained in:
2026-01-24 10:45:51 -05:00
parent 9898e6c975
commit 048e39016a
41 changed files with 2641 additions and 0 deletions

6
.gitignore vendored
View File

@@ -39,3 +39,9 @@
# debug information files
*.dwo
/build
.DS_Store
/cmake-build-debug
/.cache
**/.DS_Store

83
CMakeLists.txt Normal file
View File

@@ -0,0 +1,83 @@
cmake_minimum_required(VERSION 3.15)
project(control LANGUAGES C CXX)
option(BUILD_SHARED_LIBS "Build shared libraries" ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_COMPILE_WARNING_AS_ERROR ON)
find_package(Eigen3 REQUIRED)
find_package(librpc REQUIRED)
find_package(flatbuffers REQUIRED)
find_package(spdlog REQUIRED)
find_package(sentry REQUIRED)
find_package(crashpad REQUIRED)
file(GLOB_RECURSE COMMON_SOURCES
src/flatbuffers/*.cpp
src/actuators/*.cpp
src/Module.cpp
src/Hub.cpp
src/ModuleFactory.cpp
src/libcontrol.cpp
)
# C API Library
if(WIN32)
add_library(c_control SHARED
src/lib_c_control.cpp
${COMMON_SOURCES}
control.def
)
target_compile_definitions(c_control PRIVATE CONTROL_EXPORTS)
else()
add_library(c_control SHARED
src/lib_c_control.cpp
${COMMON_SOURCES}
)
endif()
target_include_directories(c_control PUBLIC include)
target_link_libraries(c_control
PRIVATE
librpc::librpc
Eigen3::Eigen
spdlog::spdlog
sentry-native::sentry-native
sentry-crashpad::sentry-crashpad
)
set_property(TARGET c_control PROPERTY CXX_STANDARD 23)
install(TARGETS c_control
EXPORT controlTargets
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include
)
# C++ API Library
add_library(control SHARED
${COMMON_SOURCES}
)
target_include_directories(control PUBLIC include)
target_link_libraries(control
PRIVATE
librpc::librpc
Eigen3::Eigen
spdlog::spdlog
sentry-native::sentry-native
sentry-crashpad::sentry-crashpad
)
set_property(TARGET control PROPERTY CXX_STANDARD 23)
install(TARGETS control
EXPORT controlTargets
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include
)
install(DIRECTORY include/ DESTINATION include)

9
CMakeUserPresets.json Normal file
View File

@@ -0,0 +1,9 @@
{
"version": 4,
"vendor": {
"conan": {}
},
"include": [
"build/RelWithDebInfo/generators/CMakePresets.json"
]
}

104
README.md Normal file
View File

@@ -0,0 +1,104 @@
# Control Library
The Control library provides a high level interface to control BotChain devices.
This builds separate C and C++ libraries, see `include/lib_c_control.h` and `include/libcontrol.h`.
The library is managed by the [conan](https://conan.io) package manager, and is consumed by BotChain's user interface.
The library uses the [RPC library](https://git.uwaterloo.ca/capstone-group2/rpc) to communicate with BotChain's modules.
The latest releases of the Control library can be found in our [artifactory](http://jslightham.com:8082).
## Platform Support
- MacOS (Apple silicon)
- MacOS (x86)
- Ubuntu (x86)
- Windows (x86)
## Setup
### MacOS
Install xcode command line tools (if you do not already have them)
```
xcode-select --install
```
Install conan and dependencies
```
brew install conan cmake ninja clang-format@21
```
Generate a conan profile
```
conan profile detect --force
```
Follow the required artifactory setup.
### Ubuntu
On newer versions of Ubuntu, the package manager is responsible for managing python packages. We use `pipx` to create a virtual environment.
Install `pipx` and dependencies
```
sudo apt install pipx cmake ninja-build
```
Install clang-format version 21.
Install conan
```
pipx install conan
```
Generate a conan profile
```
conan profile detect --force
```
Follow the required artifactory setup.
### Required Artifactory Setup
These instructions should only be followed after you have completed all setup steps for your platform.
Add the artifactory
```
conan remote add artifactory http://jslightham.com:8081/artifactory/api/conan/botchain
```
### Additional Artifactory Setup (optional)
These instructions should only be followed after you have completed all setup steps for your platform.
This is an optional section that is only required if you plan on uploading releases to the artifactory.
Add credentials to connect to the remote artifactory
```
conan remote login artifactory <username> -p <password>
```
Contact Johnathon to get login credentials for the artifactory.
## Development
```
# Macos and Linux users can run,
./build_control_library.sh
# Manual steps
build_type=Release # set to your desired build type (Release, RelWithDebInfo, Debug, etc.)
conan install . --build=missing --output-folder=. -s build_type="${build_type}"
cmake -S . -B "./build/${build_type}" -DCMAKE_TOOLCHAIN_FILE="./build/${build_type}/generators/conan_toolchain.cmake" -DCMAKE_BUILD_TYPE="${build_type}"
cmake --build "./build/${build_type}" --config "${build_type}"
```
## Building For Release
Create the package
```
conan create .
```
Upload to the artifactory
```
conan upload libcontrol -r=artifactory
```
## Crashpad
To be able to use sentry, we need a crashpad executable.
The executable can be taken out of the conan cache, ie,
```
find ~/.conan2 -type f -name "crashpad_handler" -perm /u=x,g=x,o=x 2>/dev/null | head -n 1
```

61
build_control_library.sh Executable file
View File

@@ -0,0 +1,61 @@
#!/bin/bash
set -e
function usage() {
echo "Usage:"
echo "${SCRIPT_NAME} [-b <build type>] [-h]"
echo " -b | --build-type - The build type (ie. Release, Debug, RelWithDebInfo)"
echo " -h | --help - Print usage"
echo "Example:"
echo "${SCRIPT_NAME} -b Release"
exit 1
}
function parse_args() {
while [ -n "${1}" ]; do
case "${1}" in
-h | --help)
usage
;;
-b | --build-type)
[ -n "${2}" ] || usage || echo "ERROR: Not enough parameters"
build_type="${2}"
shift 2
;;
-d | --disable-format)
disable_format=true
shift 1
;;
*)
echo "ERROR: Invalid parameter. Exiting..."
usage
exit 1
;;
esac
done
}
function check_pre_req() {
if [ "${build_type}" != "Debug" ] && [ "${build_type}" != "Release" ] && [ "${build_type}" != "RelWithDebInfo" ]; then
usage
echo "ERROR: Build type must be one of: Release, Debug, RelWithDebInfo"
fi
}
SCRIPT_NAME="$(basename "${0}")"
ROOT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
build_type=""
disable_format=false
parse_args "${@}"
check_pre_req
if [ "$disable_format" != "true" ]; then
echo "Formatting with clang-format..."
find "${ROOT_DIR}" -iname '*.h' -o -iname '*.cpp' | xargs clang-format -i -style=file
fi
echo "Building..."
conan install "${ROOT_DIR}" --build=missing --output-folder="${ROOT_DIR}" -s build_type="${build_type}"
cmake -S "${ROOT_DIR}" -B "${ROOT_DIR}/build/${build_type}" -DCMAKE_TOOLCHAIN_FILE="${ROOT_DIR}/build/${build_type}/generators/conan_toolchain.cmake" -DCMAKE_BUILD_TYPE="${build_type}"
cmake --build "${ROOT_DIR}/build/${build_type}" --config "${build_type}"

1
compile_commands.json Symbolic link
View File

@@ -0,0 +1 @@
build/RelWithDebInfo/compile_commands.json

50
conanfile.py Normal file
View File

@@ -0,0 +1,50 @@
import os
from conan import ConanFile
from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain, cmake_layout
from conan.tools.files import copy
class MyLibraryConan(ConanFile):
name = "libcontrol"
version = "1.0.0"
settings = "os", "compiler", "build_type", "arch"
options = {"shared": [True, False], "fPIC": [True, False]}
default_options = {"shared": True, "fPIC": True}
exports_sources = "CMakeLists.txt", "src/*", "include/*"
def layout(self):
cmake_layout(self)
def generate(self):
deps = CMakeDeps(self)
deps.generate()
tc = CMakeToolchain(self)
tc.variables["BUILD_SHARED_LIBS"] = self.options.shared
tc.generate()
def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()
def package(self):
cmake = CMake(self)
cmake.install()
def package_info(self):
self.cpp_info.libs = ["control"]
def requirements(self):
self.requires("flatbuffers/24.12.23")
self.requires("librpc/1.1.6")
self.requires("eigen/3.4.1")
self.requires("spdlog/1.16.0")
self.requires("sentry-native/0.12.2")
self.requires("sentry-crashpad/0.6.5")
def configure(self):
if self.settings.os == "Linux":
self.options.fPIC = True

8
control.def Normal file
View File

@@ -0,0 +1,8 @@
EXPORTS
init
cleanup
send_angle_control
get_configuration
control_sentry_init
control_sentry_set_app_info
control_sentry_shutdown

29
include/Hub.h Normal file
View File

@@ -0,0 +1,29 @@
//
// Created by Johnathon Slightham on 2026-01-13.
//
#ifndef CONTROL_HUB_H
#define CONTROL_HUB_H
#include "Module.h"
#include "flatbuffers/SensorMessageBuilder.h"
class Hub final : public Module {
public:
explicit Hub(uint8_t device_id) : Module(device_id) {};
Hub(uint8_t device_id, ModuleType module_type) : Module(device_id, module_type) {};
Hub(uint8_t device_id, ModuleType module_type, Messaging::ConnectionType connection_type,
uint8_t leader)
: Module(device_id, module_type, connection_type, leader) {};
double get_position() override;
void actuate(double position) override;
void actuate(double x, double y) override;
std::vector<uint8_t> get_actuation_message() override;
void update_sensor_data(const Flatbuffers::sensor_value &value) override;
};
#endif // CONTROL_HUB_H

66
include/Module.h Normal file
View File

@@ -0,0 +1,66 @@
//
// Created by Johnathon Slightham on 2025-11-14.
//
#ifndef CONTROL_MODULE_H
#define CONTROL_MODULE_H
#include <chrono>
#include <cstdint>
#include "flatbuffers/SensorMessageBuilder.h"
#include "flatbuffers_generated/RobotModule_generated.h"
#include "flatbuffers_generated/TopologyMessage_generated.h"
#include "librpc.h"
struct neighbour {
uint8_t device_id;
Orientation orientation;
};
class Module {
public:
explicit Module(uint8_t device_id) : m_device_id(device_id) {};
Module(uint8_t device_id, ModuleType module_type)
: m_device_id(device_id), m_module_type(module_type) {};
Module(uint8_t device_id, ModuleType module_type, Messaging::ConnectionType connection_type,
uint8_t leader)
: m_device_id(device_id), m_module_type(module_type), m_connection_type(connection_type),
m_leader(leader) {};
std::vector<neighbour> get_neighbours();
uint8_t get_device_id();
ModuleType get_type();
Messaging::ConnectionType get_connection_type();
uint8_t get_leader();
std::chrono::time_point<std::chrono::system_clock> get_last_updated_time();
virtual double get_position() = 0;
virtual void actuate(double x) = 0;
// There are no modules with 2D actuation support, this is an example of how to implement it.
virtual void actuate(double x, double y) = 0;
void update_module_metadata(const Messaging::TopologyMessage &message);
virtual std::vector<uint8_t> get_actuation_message() = 0;
virtual void update_sensor_data(const Flatbuffers::sensor_value &value) = 0;
private:
uint8_t m_device_id;
ModuleType m_module_type;
Messaging::ConnectionType m_connection_type;
uint8_t m_leader;
std::chrono::time_point<std::chrono::system_clock> m_last_updated;
std::vector<neighbour> m_neighbours;
std::shared_ptr<MessagingInterface> m_messaging_interface;
};
#endif // CONTROL_MODULE_H

14
include/ModuleFactory.h Normal file
View File

@@ -0,0 +1,14 @@
#ifndef CONTROL_MODULEFACTORY_H
#define CONTROL_MODULEFACTORY_H
#include "Module.h"
#include "flatbuffers_generated/RobotModule_generated.h"
class ModuleFactory {
public:
static std::shared_ptr<Module>
createModule(uint8_t device_id, ModuleType type,
std::shared_ptr<MessagingInterface> &messaging_interface);
};
#endif // CONTROL_MODULEFACTORY_H

View File

@@ -0,0 +1,28 @@
#ifndef CONTROL_ACTUATOR_H
#define CONTROL_ACTUATOR_H
#include <cstdint>
#include "Module.h"
#include "flatbuffers_generated/RobotModule_generated.h"
#include "flatbuffers_generated/TopologyMessage_generated.h"
class Actuator : public Module {
public:
explicit Actuator(uint8_t device_id) : Module(device_id) {};
Actuator(uint8_t device_id, ModuleType module_type) : Module(device_id, module_type) {};
Actuator(uint8_t device_id, ModuleType module_type, Messaging::ConnectionType connection_type,
uint8_t leader)
: Module(device_id, module_type, connection_type, leader) {};
protected:
virtual std::vector<uint8_t> get_actuation_message() = 0;
private:
[[noreturn]] void actuator_tx_loop();
};
#endif // CONTROL_MODULE_H

View File

@@ -0,0 +1,37 @@
//
// Created by Johnathon Slightham on 2025-11-13.
//
#ifndef CONTROL_BOUNDED1DPOSITIONALACTUATOR_H
#define CONTROL_BOUNDED1DPOSITIONALACTUATOR_H
#include "actuators/Actuator.h"
#include "flatbuffers/AngleControlMessageBuilder.h"
#include "flatbuffers/SensorMessageBuilder.h"
class BoundedPositionalActuator1D : public Actuator {
public:
BoundedPositionalActuator1D(uint8_t device_id, ModuleType type, double max_value,
double min_value, double initial_position)
: Actuator(device_id, type), m_target_position(initial_position), m_max_value(max_value),
m_min_value(min_value),
acm_builder(std::make_unique<Flatbuffers::AngleControlMessageBuilder>()) {
}
double get_position() override;
void actuate(double position) override;
void actuate(double x, double y) override; // no-op
std::vector<uint8_t> get_actuation_message() override;
void update_sensor_data(const Flatbuffers::sensor_value &value) override;
private:
double m_current_position = 0;
double m_target_position;
double m_max_value;
double m_min_value;
std::unique_ptr<Flatbuffers::AngleControlMessageBuilder> acm_builder;
};
#endif // CONTROL_1DPOSITIONALACTUATOR_H

View File

@@ -0,0 +1,34 @@
//
// Created by Johnathon Slightham on 2025-11-13.
//
#ifndef CONTROL_1DPOSITIONALACTUATOR_H
#define CONTROL_1DPOSITIONALACTUATOR_H
#include "actuators/Actuator.h"
#include "flatbuffers/AngleControlMessageBuilder.h"
#include "flatbuffers/SensorMessageBuilder.h"
class PositionalActuator1D final : public Actuator {
public:
PositionalActuator1D(uint8_t device_id, ModuleType type)
: Actuator(device_id, type),
m_acm_builder(std::make_unique<Flatbuffers::AngleControlMessageBuilder>()) {};
double get_position() override;
void actuate(double position) override;
void actuate(double x, double y) override; // no-op
std::vector<uint8_t> get_actuation_message() override;
void update_sensor_data(const Flatbuffers::sensor_value &value) override;
private:
void update_loop();
double m_target_position = 0;
double m_current_position = 0;
double m_board_target_position = 0;
std::unique_ptr<Flatbuffers::AngleControlMessageBuilder> m_acm_builder;
};
#endif // CONTROL_1DPOSITIONALACTUATOR_H

View File

@@ -0,0 +1,29 @@
//
// Created by Johnathon Slightham on 2025-06-30.
//
#ifndef ANGLECONTROLMESSAGEBUILDER_H_
#define ANGLECONTROLMESSAGEBUILDER_H_
#include <string>
#include <vector>
#include "SerializedMessage.h"
#include "flatbuffers/flatbuffers.h"
#include "flatbuffers_generated/AngleControlMessage_generated.h"
namespace Flatbuffers {
class AngleControlMessageBuilder {
public:
AngleControlMessageBuilder() : builder_(256) {
}
SerializedMessage build_angle_control_message(int16_t angle);
static const Messaging::AngleControlMessage *parse_angle_control_message(const uint8_t *buffer);
private:
flatbuffers::FlatBufferBuilder builder_;
};
} // namespace Flatbuffers
#endif

View File

@@ -0,0 +1,45 @@
//
// Created by Johnathon Slightham on 2025-07-25.
//
#ifndef ROBOTCONFIGURATIONBUILDER_H
#define ROBOTCONFIGURATIONBUILDER_H
#include "SerializedMessage.h"
#include <cstdint>
#include <flatbuffers_generated/RobotConfiguration_generated.h>
#include <flatbuffers_generated/RobotModule_generated.h>
#include <vector>
namespace Flatbuffers {
struct ModuleInstance {
uint8_t id;
ModuleType type;
int angle;
};
struct ModuleConnectionInstance {
uint8_t from_module_id;
uint8_t to_module_id;
uint8_t from_socket;
uint8_t to_socket;
Orientation orientation;
};
class RobotConfigurationBuilder {
public:
RobotConfigurationBuilder() : builder_(1024) {
}
SerializedMessage
build_robot_configuration(const std::vector<ModuleInstance> &modules,
const std::vector<ModuleConnectionInstance> &connections);
static const Frontend::RobotConfiguration *
parse_robot_configuration(const std::uint8_t *buffer);
private:
flatbuffers::FlatBufferBuilder builder_;
};
} // namespace Flatbuffers
#endif // ROBOTCONFIGURATIONBUILDER_H

View File

@@ -0,0 +1,54 @@
//
// Created by Johnathon Slightham on 2025-06-30.
//
#ifndef SENSORMESSAGEBUILDER_H
#define SENSORMESSAGEBUILDER_H
#include <variant>
#include "flatbuffers_generated/SensorMessage_generated.h"
namespace Flatbuffers {
struct target_angle {
int16_t angle;
};
struct current_angle {
int16_t angle;
};
typedef std::variant<target_angle, current_angle> sensor_value;
class SensorMessageBuilder {
public:
SensorMessageBuilder() : builder_(1024) {
}
static const Messaging::SensorMessage *parse_sensor_message(const std::uint8_t *buffer);
template <typename T>
static std::optional<sensor_value> build_sensor_value(Messaging::SensorValue type, T value) {
switch (type) {
case Messaging::SensorValue_TargetAngle: {
const Messaging::TargetAngle *target =
static_cast<const Messaging::TargetAngle *>(value);
return target_angle{target->value()};
}
case Messaging::SensorValue_CurrentAngle: {
const Messaging::CurrentAngle *current =
static_cast<const Messaging::CurrentAngle *>(value);
return current_angle{current->value()};
}
default:
return std::nullopt;
}
}
private:
flatbuffers::FlatBufferBuilder builder_;
};
} // namespace Flatbuffers
#endif // TOPOLOGYMESSAGEBUILDER_H

View File

@@ -0,0 +1,17 @@
//
// Created by Johnathon Slightham on 2025-07-05.
//
#ifndef SERIALIZEDMESSAGE_H
#define SERIALIZEDMESSAGE_H
#include <stddef.h>
namespace Flatbuffers {
struct SerializedMessage {
void *data;
size_t size;
};
} // namespace Flatbuffers
#endif // SERIALIZEDMESSAGE_H

View File

@@ -0,0 +1,32 @@
//
// Created by Johnathon Slightham on 2025-06-30.
//
#ifndef TOPOLOGYMESSAGEBUILDER_H
#define TOPOLOGYMESSAGEBUILDER_H
#include <vector>
#include "SerializedMessage.h"
#include "flatbuffers_generated/TopologyMessage_generated.h"
namespace Flatbuffers {
class TopologyMessageBuilder {
public:
TopologyMessageBuilder() : builder_(1024) {
}
SerializedMessage build_topology_message(uint8_t module_id, ModuleType module_type,
const std::vector<uint8_t> &channel_to_module,
const std::vector<int8_t> &orientation_to_module);
static const Messaging::TopologyMessage *parse_topology_message(const uint8_t *buffer);
static bool is_valid_topology_message(const uint8_t *buffer, size_t size);
private:
flatbuffers::FlatBufferBuilder builder_;
};
} // namespace Flatbuffers
#endif // TOPOLOGYMESSAGEBUILDER_H

View File

@@ -0,0 +1,86 @@
// automatically generated by the FlatBuffers compiler, do not modify
#ifndef FLATBUFFERS_GENERATED_ANGLECONTROLMESSAGE_MESSAGING_H_
#define FLATBUFFERS_GENERATED_ANGLECONTROLMESSAGE_MESSAGING_H_
#include "flatbuffers/flatbuffers.h"
// Ensure the included flatbuffers.h is the same version as when this file was
// generated, otherwise it may not be compatible.
// static_assert(FLATBUFFERS_VERSION_MAJOR == 25 &&
// FLATBUFFERS_VERSION_MINOR == 2 &&
// FLATBUFFERS_VERSION_REVISION == 10,
//"Non-compatible flatbuffers version included");
namespace Messaging {
struct AngleControlMessage;
struct AngleControlMessageBuilder;
struct AngleControlMessage FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
typedef AngleControlMessageBuilder Builder;
enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { VT_ANGLE = 4 };
int16_t angle() const {
return GetField<int16_t>(VT_ANGLE, 0);
}
bool Verify(::flatbuffers::Verifier &verifier) const {
return VerifyTableStart(verifier) && VerifyField<int16_t>(verifier, VT_ANGLE, 2) &&
verifier.EndTable();
}
};
struct AngleControlMessageBuilder {
typedef AngleControlMessage Table;
::flatbuffers::FlatBufferBuilder &fbb_;
::flatbuffers::uoffset_t start_;
void add_angle(int16_t angle) {
fbb_.AddElement<int16_t>(AngleControlMessage::VT_ANGLE, angle, 0);
}
explicit AngleControlMessageBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) {
start_ = fbb_.StartTable();
}
::flatbuffers::Offset<AngleControlMessage> Finish() {
const auto end = fbb_.EndTable(start_);
auto o = ::flatbuffers::Offset<AngleControlMessage>(end);
return o;
}
};
inline ::flatbuffers::Offset<AngleControlMessage>
CreateAngleControlMessage(::flatbuffers::FlatBufferBuilder &_fbb, int16_t angle = 0) {
AngleControlMessageBuilder builder_(_fbb);
builder_.add_angle(angle);
return builder_.Finish();
}
inline const Messaging::AngleControlMessage *GetAngleControlMessage(const void *buf) {
return ::flatbuffers::GetRoot<Messaging::AngleControlMessage>(buf);
}
inline const Messaging::AngleControlMessage *GetSizePrefixedAngleControlMessage(const void *buf) {
return ::flatbuffers::GetSizePrefixedRoot<Messaging::AngleControlMessage>(buf);
}
inline bool VerifyAngleControlMessageBuffer(::flatbuffers::Verifier &verifier) {
return verifier.VerifyBuffer<Messaging::AngleControlMessage>(nullptr);
}
inline bool VerifySizePrefixedAngleControlMessageBuffer(::flatbuffers::Verifier &verifier) {
return verifier.VerifySizePrefixedBuffer<Messaging::AngleControlMessage>(nullptr);
}
inline void
FinishAngleControlMessageBuffer(::flatbuffers::FlatBufferBuilder &fbb,
::flatbuffers::Offset<Messaging::AngleControlMessage> root) {
fbb.Finish(root);
}
inline void FinishSizePrefixedAngleControlMessageBuffer(
::flatbuffers::FlatBufferBuilder &fbb,
::flatbuffers::Offset<Messaging::AngleControlMessage> root) {
fbb.FinishSizePrefixed(root);
}
} // namespace Messaging
#endif // FLATBUFFERS_GENERATED_ANGLECONTROLMESSAGE_MESSAGING_H_

View File

@@ -0,0 +1,201 @@
// automatically generated by the FlatBuffers compiler, do not modify
#ifndef FLATBUFFERS_GENERATED_ROBOTCONFIGURATION_FRONTEND_H_
#define FLATBUFFERS_GENERATED_ROBOTCONFIGURATION_FRONTEND_H_
#include "flatbuffers/flatbuffers.h"
// Ensure the included flatbuffers.h is the same version as when this file was
// generated, otherwise it may not be compatible.
// static_assert(FLATBUFFERS_VERSION_MAJOR == 25 &&
// FLATBUFFERS_VERSION_MINOR == 2 &&
// FLATBUFFERS_VERSION_REVISION == 10,
// "Non-compatible flatbuffers version included");
#include "RobotModule_generated.h"
namespace Frontend {
struct ModuleConnection;
struct ModuleConnectionBuilder;
struct RobotConfiguration;
struct RobotConfigurationBuilder;
struct ModuleConnection FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
typedef ModuleConnectionBuilder Builder;
enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
VT_FROM_MODULE_ID = 4,
VT_TO_MODULE_ID = 6,
VT_FROM_SOCKET = 8,
VT_TO_SOCKET = 10,
VT_ORIENTATION = 12
};
uint8_t from_module_id() const {
return GetField<uint8_t>(VT_FROM_MODULE_ID, 0);
}
uint8_t to_module_id() const {
return GetField<uint8_t>(VT_TO_MODULE_ID, 0);
}
uint8_t from_socket() const {
return GetField<uint8_t>(VT_FROM_SOCKET, 0);
}
uint8_t to_socket() const {
return GetField<uint8_t>(VT_TO_SOCKET, 0);
}
Orientation orientation() const {
return static_cast<Orientation>(GetField<int8_t>(VT_ORIENTATION, 0));
}
bool Verify(::flatbuffers::Verifier &verifier) const {
return VerifyTableStart(verifier) && VerifyField<uint8_t>(verifier, VT_FROM_MODULE_ID, 1) &&
VerifyField<uint8_t>(verifier, VT_TO_MODULE_ID, 1) &&
VerifyField<uint8_t>(verifier, VT_FROM_SOCKET, 1) &&
VerifyField<uint8_t>(verifier, VT_TO_SOCKET, 1) &&
VerifyField<int8_t>(verifier, VT_ORIENTATION, 1) && verifier.EndTable();
}
};
struct ModuleConnectionBuilder {
typedef ModuleConnection Table;
::flatbuffers::FlatBufferBuilder &fbb_;
::flatbuffers::uoffset_t start_;
void add_from_module_id(uint8_t from_module_id) {
fbb_.AddElement<uint8_t>(ModuleConnection::VT_FROM_MODULE_ID, from_module_id, 0);
}
void add_to_module_id(uint8_t to_module_id) {
fbb_.AddElement<uint8_t>(ModuleConnection::VT_TO_MODULE_ID, to_module_id, 0);
}
void add_from_socket(uint8_t from_socket) {
fbb_.AddElement<uint8_t>(ModuleConnection::VT_FROM_SOCKET, from_socket, 0);
}
void add_to_socket(uint8_t to_socket) {
fbb_.AddElement<uint8_t>(ModuleConnection::VT_TO_SOCKET, to_socket, 0);
}
void add_orientation(Orientation orientation) {
fbb_.AddElement<int8_t>(ModuleConnection::VT_ORIENTATION, static_cast<int8_t>(orientation),
0);
}
explicit ModuleConnectionBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) {
start_ = fbb_.StartTable();
}
::flatbuffers::Offset<ModuleConnection> Finish() {
const auto end = fbb_.EndTable(start_);
auto o = ::flatbuffers::Offset<ModuleConnection>(end);
return o;
}
};
inline ::flatbuffers::Offset<ModuleConnection>
CreateModuleConnection(::flatbuffers::FlatBufferBuilder &_fbb, uint8_t from_module_id = 0,
uint8_t to_module_id = 0, uint8_t from_socket = 0, uint8_t to_socket = 0,
Orientation orientation = Orientation_Deg0) {
ModuleConnectionBuilder builder_(_fbb);
builder_.add_orientation(orientation);
builder_.add_to_socket(to_socket);
builder_.add_from_socket(from_socket);
builder_.add_to_module_id(to_module_id);
builder_.add_from_module_id(from_module_id);
return builder_.Finish();
}
struct RobotConfiguration FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
typedef RobotConfigurationBuilder Builder;
enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
VT_MODULES = 4,
VT_CONNECTIONS = 6
};
const ::flatbuffers::Vector<::flatbuffers::Offset<RobotModule>> *modules() const {
return GetPointer<const ::flatbuffers::Vector<::flatbuffers::Offset<RobotModule>> *>(
VT_MODULES);
}
const ::flatbuffers::Vector<::flatbuffers::Offset<Frontend::ModuleConnection>> *
connections() const {
return GetPointer<
const ::flatbuffers::Vector<::flatbuffers::Offset<Frontend::ModuleConnection>> *>(
VT_CONNECTIONS);
}
bool Verify(::flatbuffers::Verifier &verifier) const {
return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_MODULES) &&
verifier.VerifyVector(modules()) && verifier.VerifyVectorOfTables(modules()) &&
VerifyOffset(verifier, VT_CONNECTIONS) && verifier.VerifyVector(connections()) &&
verifier.VerifyVectorOfTables(connections()) && verifier.EndTable();
}
};
struct RobotConfigurationBuilder {
typedef RobotConfiguration Table;
::flatbuffers::FlatBufferBuilder &fbb_;
::flatbuffers::uoffset_t start_;
void add_modules(
::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<RobotModule>>> modules) {
fbb_.AddOffset(RobotConfiguration::VT_MODULES, modules);
}
void add_connections(::flatbuffers::Offset<
::flatbuffers::Vector<::flatbuffers::Offset<Frontend::ModuleConnection>>>
connections) {
fbb_.AddOffset(RobotConfiguration::VT_CONNECTIONS, connections);
}
explicit RobotConfigurationBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) {
start_ = fbb_.StartTable();
}
::flatbuffers::Offset<RobotConfiguration> Finish() {
const auto end = fbb_.EndTable(start_);
auto o = ::flatbuffers::Offset<RobotConfiguration>(end);
return o;
}
};
inline ::flatbuffers::Offset<RobotConfiguration> CreateRobotConfiguration(
::flatbuffers::FlatBufferBuilder &_fbb,
::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<RobotModule>>> modules = 0,
::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<Frontend::ModuleConnection>>>
connections = 0) {
RobotConfigurationBuilder builder_(_fbb);
builder_.add_connections(connections);
builder_.add_modules(modules);
return builder_.Finish();
}
inline ::flatbuffers::Offset<RobotConfiguration> CreateRobotConfigurationDirect(
::flatbuffers::FlatBufferBuilder &_fbb,
const std::vector<::flatbuffers::Offset<RobotModule>> *modules = nullptr,
const std::vector<::flatbuffers::Offset<Frontend::ModuleConnection>> *connections = nullptr) {
auto modules__ = modules ? _fbb.CreateVector<::flatbuffers::Offset<RobotModule>>(*modules) : 0;
auto connections__ =
connections
? _fbb.CreateVector<::flatbuffers::Offset<Frontend::ModuleConnection>>(*connections)
: 0;
return Frontend::CreateRobotConfiguration(_fbb, modules__, connections__);
}
inline const Frontend::RobotConfiguration *GetRobotConfiguration(const void *buf) {
return ::flatbuffers::GetRoot<Frontend::RobotConfiguration>(buf);
}
inline const Frontend::RobotConfiguration *GetSizePrefixedRobotConfiguration(const void *buf) {
return ::flatbuffers::GetSizePrefixedRoot<Frontend::RobotConfiguration>(buf);
}
inline bool VerifyRobotConfigurationBuffer(::flatbuffers::Verifier &verifier) {
return verifier.VerifyBuffer<Frontend::RobotConfiguration>(nullptr);
}
inline bool VerifySizePrefixedRobotConfigurationBuffer(::flatbuffers::Verifier &verifier) {
return verifier.VerifySizePrefixedBuffer<Frontend::RobotConfiguration>(nullptr);
}
inline void
FinishRobotConfigurationBuffer(::flatbuffers::FlatBufferBuilder &fbb,
::flatbuffers::Offset<Frontend::RobotConfiguration> root) {
fbb.Finish(root);
}
inline void FinishSizePrefixedRobotConfigurationBuffer(
::flatbuffers::FlatBufferBuilder &fbb,
::flatbuffers::Offset<Frontend::RobotConfiguration> root) {
fbb.FinishSizePrefixed(root);
}
} // namespace Frontend
#endif // FLATBUFFERS_GENERATED_ROBOTCONFIGURATION_FRONTEND_H_

View File

@@ -0,0 +1,288 @@
// automatically generated by the FlatBuffers compiler, do not modify
#ifndef FLATBUFFERS_GENERATED_ROBOTMODULE_H_
#define FLATBUFFERS_GENERATED_ROBOTMODULE_H_
#include "flatbuffers/flatbuffers.h"
// Ensure the included flatbuffers.h is the same version as when this file was
// generated, otherwise it may not be compatible.
// static_assert(FLATBUFFERS_VERSION_MAJOR == 25 &&
// FLATBUFFERS_VERSION_MINOR == 2 &&
// FLATBUFFERS_VERSION_REVISION == 10,
// "Non-compatible flatbuffers version included");
struct MotorState;
struct MotorStateBuilder;
struct RobotModule;
struct RobotModuleBuilder;
enum ModuleType : int8_t {
ModuleType_SPLITTER = 0,
ModuleType_SERVO_1 = 1,
ModuleType_DC_MOTOR = 2,
ModuleType_BATTERY = 3,
ModuleType_SERVO_2 = 4,
ModuleType_MIN = ModuleType_SPLITTER,
ModuleType_MAX = ModuleType_SERVO_2
};
inline const ModuleType (&EnumValuesModuleType())[5] {
static const ModuleType values[] = {ModuleType_SPLITTER, ModuleType_SERVO_1,
ModuleType_DC_MOTOR, ModuleType_BATTERY,
ModuleType_SERVO_2};
return values;
}
inline const char *const *EnumNamesModuleType() {
static const char *const names[6] = {"SPLITTER", "SERVO_1", "DC_MOTOR",
"BATTERY", "SERVO_2", nullptr};
return names;
}
inline const char *EnumNameModuleType(ModuleType e) {
if (::flatbuffers::IsOutRange(e, ModuleType_SPLITTER, ModuleType_SERVO_2))
return "";
const size_t index = static_cast<size_t>(e);
return EnumNamesModuleType()[index];
}
enum Orientation : int8_t {
Orientation_Deg0 = 0,
Orientation_Deg90 = 1,
Orientation_Deg180 = 2,
Orientation_Deg270 = 3,
Orientation_MIN = Orientation_Deg0,
Orientation_MAX = Orientation_Deg270
};
inline const Orientation (&EnumValuesOrientation())[4] {
static const Orientation values[] = {Orientation_Deg0, Orientation_Deg90, Orientation_Deg180,
Orientation_Deg270};
return values;
}
inline const char *const *EnumNamesOrientation() {
static const char *const names[5] = {"Deg0", "Deg90", "Deg180", "Deg270", nullptr};
return names;
}
inline const char *EnumNameOrientation(Orientation e) {
if (::flatbuffers::IsOutRange(e, Orientation_Deg0, Orientation_Deg270))
return "";
const size_t index = static_cast<size_t>(e);
return EnumNamesOrientation()[index];
}
enum ModuleState : uint8_t {
ModuleState_NONE = 0,
ModuleState_MotorState = 1,
ModuleState_MIN = ModuleState_NONE,
ModuleState_MAX = ModuleState_MotorState
};
inline const ModuleState (&EnumValuesModuleState())[2] {
static const ModuleState values[] = {ModuleState_NONE, ModuleState_MotorState};
return values;
}
inline const char *const *EnumNamesModuleState() {
static const char *const names[3] = {"NONE", "MotorState", nullptr};
return names;
}
inline const char *EnumNameModuleState(ModuleState e) {
if (::flatbuffers::IsOutRange(e, ModuleState_NONE, ModuleState_MotorState))
return "";
const size_t index = static_cast<size_t>(e);
return EnumNamesModuleState()[index];
}
template <typename T> struct ModuleStateTraits {
static const ModuleState enum_value = ModuleState_NONE;
};
template <> struct ModuleStateTraits<MotorState> {
static const ModuleState enum_value = ModuleState_MotorState;
};
bool VerifyModuleState(::flatbuffers::Verifier &verifier, const void *obj, ModuleState type);
bool VerifyModuleStateVector(::flatbuffers::Verifier &verifier,
const ::flatbuffers::Vector<::flatbuffers::Offset<void>> *values,
const ::flatbuffers::Vector<uint8_t> *types);
struct MotorState FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
typedef MotorStateBuilder Builder;
enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { VT_ANGLE = 4 };
int32_t angle() const {
return GetField<int32_t>(VT_ANGLE, 0);
}
bool Verify(::flatbuffers::Verifier &verifier) const {
return VerifyTableStart(verifier) && VerifyField<int32_t>(verifier, VT_ANGLE, 4) &&
verifier.EndTable();
}
};
struct MotorStateBuilder {
typedef MotorState Table;
::flatbuffers::FlatBufferBuilder &fbb_;
::flatbuffers::uoffset_t start_;
void add_angle(int32_t angle) {
fbb_.AddElement<int32_t>(MotorState::VT_ANGLE, angle, 0);
}
explicit MotorStateBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) {
start_ = fbb_.StartTable();
}
::flatbuffers::Offset<MotorState> Finish() {
const auto end = fbb_.EndTable(start_);
auto o = ::flatbuffers::Offset<MotorState>(end);
return o;
}
};
inline ::flatbuffers::Offset<MotorState> CreateMotorState(::flatbuffers::FlatBufferBuilder &_fbb,
int32_t angle = 0) {
MotorStateBuilder builder_(_fbb);
builder_.add_angle(angle);
return builder_.Finish();
}
struct RobotModule FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
typedef RobotModuleBuilder Builder;
enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
VT_ID = 4,
VT_MODULE_TYPE = 6,
VT_CONFIGURATION_TYPE = 8,
VT_CONFIGURATION = 10
};
uint8_t id() const {
return GetField<uint8_t>(VT_ID, 0);
}
ModuleType module_type() const {
return static_cast<ModuleType>(GetField<int8_t>(VT_MODULE_TYPE, 0));
}
ModuleState configuration_type() const {
return static_cast<ModuleState>(GetField<uint8_t>(VT_CONFIGURATION_TYPE, 0));
}
const void *configuration() const {
return GetPointer<const void *>(VT_CONFIGURATION);
}
template <typename T> const T *configuration_as() const;
const MotorState *configuration_as_MotorState() const {
return configuration_type() == ModuleState_MotorState
? static_cast<const MotorState *>(configuration())
: nullptr;
}
bool Verify(::flatbuffers::Verifier &verifier) const {
return VerifyTableStart(verifier) && VerifyField<uint8_t>(verifier, VT_ID, 1) &&
VerifyField<int8_t>(verifier, VT_MODULE_TYPE, 1) &&
VerifyField<uint8_t>(verifier, VT_CONFIGURATION_TYPE, 1) &&
VerifyOffset(verifier, VT_CONFIGURATION) &&
VerifyModuleState(verifier, configuration(), configuration_type()) &&
verifier.EndTable();
}
};
template <> inline const MotorState *RobotModule::configuration_as<MotorState>() const {
return configuration_as_MotorState();
}
struct RobotModuleBuilder {
typedef RobotModule Table;
::flatbuffers::FlatBufferBuilder &fbb_;
::flatbuffers::uoffset_t start_;
void add_id(uint8_t id) {
fbb_.AddElement<uint8_t>(RobotModule::VT_ID, id, 0);
}
void add_module_type(ModuleType module_type) {
fbb_.AddElement<int8_t>(RobotModule::VT_MODULE_TYPE, static_cast<int8_t>(module_type), 0);
}
void add_configuration_type(ModuleState configuration_type) {
fbb_.AddElement<uint8_t>(RobotModule::VT_CONFIGURATION_TYPE,
static_cast<uint8_t>(configuration_type), 0);
}
void add_configuration(::flatbuffers::Offset<void> configuration) {
fbb_.AddOffset(RobotModule::VT_CONFIGURATION, configuration);
}
explicit RobotModuleBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) {
start_ = fbb_.StartTable();
}
::flatbuffers::Offset<RobotModule> Finish() {
const auto end = fbb_.EndTable(start_);
auto o = ::flatbuffers::Offset<RobotModule>(end);
return o;
}
};
inline ::flatbuffers::Offset<RobotModule>
CreateRobotModule(::flatbuffers::FlatBufferBuilder &_fbb, uint8_t id = 0,
ModuleType module_type = ModuleType_SPLITTER,
ModuleState configuration_type = ModuleState_NONE,
::flatbuffers::Offset<void> configuration = 0) {
RobotModuleBuilder builder_(_fbb);
builder_.add_configuration(configuration);
builder_.add_configuration_type(configuration_type);
builder_.add_module_type(module_type);
builder_.add_id(id);
return builder_.Finish();
}
inline bool VerifyModuleState(::flatbuffers::Verifier &verifier, const void *obj,
ModuleState type) {
switch (type) {
case ModuleState_NONE: {
return true;
}
case ModuleState_MotorState: {
auto ptr = reinterpret_cast<const MotorState *>(obj);
return verifier.VerifyTable(ptr);
}
default:
return true;
}
}
inline bool
VerifyModuleStateVector(::flatbuffers::Verifier &verifier,
const ::flatbuffers::Vector<::flatbuffers::Offset<void>> *values,
const ::flatbuffers::Vector<uint8_t> *types) {
if (!values || !types)
return !values && !types;
if (values->size() != types->size())
return false;
for (::flatbuffers::uoffset_t i = 0; i < values->size(); ++i) {
if (!VerifyModuleState(verifier, values->Get(i), types->GetEnum<ModuleState>(i))) {
return false;
}
}
return true;
}
inline const RobotModule *GetRobotModule(const void *buf) {
return ::flatbuffers::GetRoot<RobotModule>(buf);
}
inline const RobotModule *GetSizePrefixedRobotModule(const void *buf) {
return ::flatbuffers::GetSizePrefixedRoot<RobotModule>(buf);
}
inline bool VerifyRobotModuleBuffer(::flatbuffers::Verifier &verifier) {
return verifier.VerifyBuffer<RobotModule>(nullptr);
}
inline bool VerifySizePrefixedRobotModuleBuffer(::flatbuffers::Verifier &verifier) {
return verifier.VerifySizePrefixedBuffer<RobotModule>(nullptr);
}
inline void FinishRobotModuleBuffer(::flatbuffers::FlatBufferBuilder &fbb,
::flatbuffers::Offset<RobotModule> root) {
fbb.Finish(root);
}
inline void FinishSizePrefixedRobotModuleBuffer(::flatbuffers::FlatBufferBuilder &fbb,
::flatbuffers::Offset<RobotModule> root) {
fbb.FinishSizePrefixed(root);
}
#endif // FLATBUFFERS_GENERATED_ROBOTMODULE_H_

View File

@@ -0,0 +1,265 @@
// automatically generated by the FlatBuffers compiler, do not modify
#ifndef FLATBUFFERS_GENERATED_SENSORMESSAGE_MESSAGING_H_
#define FLATBUFFERS_GENERATED_SENSORMESSAGE_MESSAGING_H_
#include "flatbuffers/flatbuffers.h"
// Ensure the included flatbuffers.h is the same version as when this file was
// generated, otherwise it may not be compatible.
// static_assert(FLATBUFFERS_VERSION_MAJOR == 25 &&
// FLATBUFFERS_VERSION_MINOR == 2 &&
// FLATBUFFERS_VERSION_REVISION == 10,
// "Non-compatible flatbuffers version included");
namespace Messaging {
struct TargetAngle;
struct TargetAngleBuilder;
struct CurrentAngle;
struct CurrentAngleBuilder;
struct SensorMessage;
struct SensorMessageBuilder;
enum SensorValue : uint8_t {
SensorValue_NONE = 0,
SensorValue_TargetAngle = 1,
SensorValue_CurrentAngle = 2,
SensorValue_MIN = SensorValue_NONE,
SensorValue_MAX = SensorValue_CurrentAngle
};
inline const SensorValue (&EnumValuesSensorValue())[3] {
static const SensorValue values[] = {SensorValue_NONE, SensorValue_TargetAngle,
SensorValue_CurrentAngle};
return values;
}
inline const char *const *EnumNamesSensorValue() {
static const char *const names[4] = {"NONE", "TargetAngle", "CurrentAngle", nullptr};
return names;
}
inline const char *EnumNameSensorValue(SensorValue e) {
if (::flatbuffers::IsOutRange(e, SensorValue_NONE, SensorValue_CurrentAngle))
return "";
const size_t index = static_cast<size_t>(e);
return EnumNamesSensorValue()[index];
}
template <typename T> struct SensorValueTraits {
static const SensorValue enum_value = SensorValue_NONE;
};
template <> struct SensorValueTraits<Messaging::TargetAngle> {
static const SensorValue enum_value = SensorValue_TargetAngle;
};
template <> struct SensorValueTraits<Messaging::CurrentAngle> {
static const SensorValue enum_value = SensorValue_CurrentAngle;
};
bool VerifySensorValue(::flatbuffers::Verifier &verifier, const void *obj, SensorValue type);
bool VerifySensorValueVector(::flatbuffers::Verifier &verifier,
const ::flatbuffers::Vector<::flatbuffers::Offset<void>> *values,
const ::flatbuffers::Vector<uint8_t> *types);
struct TargetAngle FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
typedef TargetAngleBuilder Builder;
enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { VT_VALUE = 4 };
int16_t value() const {
return GetField<int16_t>(VT_VALUE, 0);
}
bool Verify(::flatbuffers::Verifier &verifier) const {
return VerifyTableStart(verifier) && VerifyField<int16_t>(verifier, VT_VALUE, 2) &&
verifier.EndTable();
}
};
struct TargetAngleBuilder {
typedef TargetAngle Table;
::flatbuffers::FlatBufferBuilder &fbb_;
::flatbuffers::uoffset_t start_;
void add_value(int16_t value) {
fbb_.AddElement<int16_t>(TargetAngle::VT_VALUE, value, 0);
}
explicit TargetAngleBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) {
start_ = fbb_.StartTable();
}
::flatbuffers::Offset<TargetAngle> Finish() {
const auto end = fbb_.EndTable(start_);
auto o = ::flatbuffers::Offset<TargetAngle>(end);
return o;
}
};
inline ::flatbuffers::Offset<TargetAngle> CreateTargetAngle(::flatbuffers::FlatBufferBuilder &_fbb,
int16_t value = 0) {
TargetAngleBuilder builder_(_fbb);
builder_.add_value(value);
return builder_.Finish();
}
struct CurrentAngle FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
typedef CurrentAngleBuilder Builder;
enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { VT_VALUE = 4 };
int16_t value() const {
return GetField<int16_t>(VT_VALUE, 0);
}
bool Verify(::flatbuffers::Verifier &verifier) const {
return VerifyTableStart(verifier) && VerifyField<int16_t>(verifier, VT_VALUE, 2) &&
verifier.EndTable();
}
};
struct CurrentAngleBuilder {
typedef CurrentAngle Table;
::flatbuffers::FlatBufferBuilder &fbb_;
::flatbuffers::uoffset_t start_;
void add_value(int16_t value) {
fbb_.AddElement<int16_t>(CurrentAngle::VT_VALUE, value, 0);
}
explicit CurrentAngleBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) {
start_ = fbb_.StartTable();
}
::flatbuffers::Offset<CurrentAngle> Finish() {
const auto end = fbb_.EndTable(start_);
auto o = ::flatbuffers::Offset<CurrentAngle>(end);
return o;
}
};
inline ::flatbuffers::Offset<CurrentAngle>
CreateCurrentAngle(::flatbuffers::FlatBufferBuilder &_fbb, int16_t value = 0) {
CurrentAngleBuilder builder_(_fbb);
builder_.add_value(value);
return builder_.Finish();
}
struct SensorMessage FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
typedef SensorMessageBuilder Builder;
enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
VT_VALUES_TYPE = 4,
VT_VALUES = 6
};
const ::flatbuffers::Vector<uint8_t> *values_type() const {
return GetPointer<const ::flatbuffers::Vector<uint8_t> *>(VT_VALUES_TYPE);
}
const ::flatbuffers::Vector<::flatbuffers::Offset<void>> *values() const {
return GetPointer<const ::flatbuffers::Vector<::flatbuffers::Offset<void>> *>(VT_VALUES);
}
bool Verify(::flatbuffers::Verifier &verifier) const {
return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_VALUES_TYPE) &&
verifier.VerifyVector(values_type()) && VerifyOffset(verifier, VT_VALUES) &&
verifier.VerifyVector(values()) &&
VerifySensorValueVector(verifier, values(), values_type()) && verifier.EndTable();
}
};
struct SensorMessageBuilder {
typedef SensorMessage Table;
::flatbuffers::FlatBufferBuilder &fbb_;
::flatbuffers::uoffset_t start_;
void add_values_type(::flatbuffers::Offset<::flatbuffers::Vector<uint8_t>> values_type) {
fbb_.AddOffset(SensorMessage::VT_VALUES_TYPE, values_type);
}
void
add_values(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<void>>> values) {
fbb_.AddOffset(SensorMessage::VT_VALUES, values);
}
explicit SensorMessageBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) {
start_ = fbb_.StartTable();
}
::flatbuffers::Offset<SensorMessage> Finish() {
const auto end = fbb_.EndTable(start_);
auto o = ::flatbuffers::Offset<SensorMessage>(end);
return o;
}
};
inline ::flatbuffers::Offset<SensorMessage> CreateSensorMessage(
::flatbuffers::FlatBufferBuilder &_fbb,
::flatbuffers::Offset<::flatbuffers::Vector<uint8_t>> values_type = 0,
::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<void>>> values = 0) {
SensorMessageBuilder builder_(_fbb);
builder_.add_values(values);
builder_.add_values_type(values_type);
return builder_.Finish();
}
inline ::flatbuffers::Offset<SensorMessage>
CreateSensorMessageDirect(::flatbuffers::FlatBufferBuilder &_fbb,
const std::vector<uint8_t> *values_type = nullptr,
const std::vector<::flatbuffers::Offset<void>> *values = nullptr) {
auto values_type__ = values_type ? _fbb.CreateVector<uint8_t>(*values_type) : 0;
auto values__ = values ? _fbb.CreateVector<::flatbuffers::Offset<void>>(*values) : 0;
return Messaging::CreateSensorMessage(_fbb, values_type__, values__);
}
inline bool VerifySensorValue(::flatbuffers::Verifier &verifier, const void *obj,
SensorValue type) {
switch (type) {
case SensorValue_NONE: {
return true;
}
case SensorValue_TargetAngle: {
auto ptr = reinterpret_cast<const Messaging::TargetAngle *>(obj);
return verifier.VerifyTable(ptr);
}
case SensorValue_CurrentAngle: {
auto ptr = reinterpret_cast<const Messaging::CurrentAngle *>(obj);
return verifier.VerifyTable(ptr);
}
default:
return true;
}
}
inline bool
VerifySensorValueVector(::flatbuffers::Verifier &verifier,
const ::flatbuffers::Vector<::flatbuffers::Offset<void>> *values,
const ::flatbuffers::Vector<uint8_t> *types) {
if (!values || !types)
return !values && !types;
if (values->size() != types->size())
return false;
for (::flatbuffers::uoffset_t i = 0; i < values->size(); ++i) {
if (!VerifySensorValue(verifier, values->Get(i), types->GetEnum<SensorValue>(i))) {
return false;
}
}
return true;
}
inline const Messaging::SensorMessage *GetSensorMessage(const void *buf) {
return ::flatbuffers::GetRoot<Messaging::SensorMessage>(buf);
}
inline const Messaging::SensorMessage *GetSizePrefixedSensorMessage(const void *buf) {
return ::flatbuffers::GetSizePrefixedRoot<Messaging::SensorMessage>(buf);
}
inline bool VerifySensorMessageBuffer(::flatbuffers::Verifier &verifier) {
return verifier.VerifyBuffer<Messaging::SensorMessage>(nullptr);
}
inline bool VerifySizePrefixedSensorMessageBuffer(::flatbuffers::Verifier &verifier) {
return verifier.VerifySizePrefixedBuffer<Messaging::SensorMessage>(nullptr);
}
inline void FinishSensorMessageBuffer(::flatbuffers::FlatBufferBuilder &fbb,
::flatbuffers::Offset<Messaging::SensorMessage> root) {
fbb.Finish(root);
}
inline void
FinishSizePrefixedSensorMessageBuffer(::flatbuffers::FlatBufferBuilder &fbb,
::flatbuffers::Offset<Messaging::SensorMessage> root) {
fbb.FinishSizePrefixed(root);
}
} // namespace Messaging
#endif // FLATBUFFERS_GENERATED_SENSORMESSAGE_MESSAGING_H_

View File

@@ -0,0 +1,203 @@
// automatically generated by the FlatBuffers compiler, do not modify
#ifndef FLATBUFFERS_GENERATED_TOPOLOGYMESSAGE_MESSAGING_H_
#define FLATBUFFERS_GENERATED_TOPOLOGYMESSAGE_MESSAGING_H_
#include "flatbuffers/flatbuffers.h"
// Ensure the included flatbuffers.h is the same version as when this file was
// generated, otherwise it may not be compatible.
// static_assert(FLATBUFFERS_VERSION_MAJOR == 25 &&
// FLATBUFFERS_VERSION_MINOR == 2 &&
// FLATBUFFERS_VERSION_REVISION == 10,
// "Non-compatible flatbuffers version included");
#include "RobotModule_generated.h"
namespace Messaging {
struct TopologyMessage;
struct TopologyMessageBuilder;
enum ConnectionType : int8_t {
ConnectionType_DIRECT = 0,
ConnectionType_HOP = 1,
ConnectionType_MIN = ConnectionType_DIRECT,
ConnectionType_MAX = ConnectionType_HOP
};
inline const ConnectionType (&EnumValuesConnectionType())[2] {
static const ConnectionType values[] = {ConnectionType_DIRECT, ConnectionType_HOP};
return values;
}
inline const char *const *EnumNamesConnectionType() {
static const char *const names[3] = {"DIRECT", "HOP", nullptr};
return names;
}
inline const char *EnumNameConnectionType(ConnectionType e) {
if (::flatbuffers::IsOutRange(e, ConnectionType_DIRECT, ConnectionType_HOP))
return "";
const size_t index = static_cast<size_t>(e);
return EnumNamesConnectionType()[index];
}
struct TopologyMessage FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
typedef TopologyMessageBuilder Builder;
enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
VT_MODULE_ID = 4,
VT_MODULE_TYPE = 6,
VT_NUM_CHANNELS = 8,
VT_CHANNEL_TO_MODULE = 10,
VT_CHANNEL_TO_ORIENTATION = 12,
VT_CONNECTION = 14,
VT_LEADER = 16,
VT_FIRMWARE = 18
};
uint8_t module_id() const {
return GetField<uint8_t>(VT_MODULE_ID, 0);
}
ModuleType module_type() const {
return static_cast<ModuleType>(GetField<int8_t>(VT_MODULE_TYPE, 0));
}
uint8_t num_channels() const {
return GetField<uint8_t>(VT_NUM_CHANNELS, 0);
}
const ::flatbuffers::Vector<uint8_t> *channel_to_module() const {
return GetPointer<const ::flatbuffers::Vector<uint8_t> *>(VT_CHANNEL_TO_MODULE);
}
const ::flatbuffers::Vector<int8_t> *channel_to_orientation() const {
return GetPointer<const ::flatbuffers::Vector<int8_t> *>(VT_CHANNEL_TO_ORIENTATION);
}
Messaging::ConnectionType connection() const {
return static_cast<Messaging::ConnectionType>(GetField<int8_t>(VT_CONNECTION, 0));
}
uint8_t leader() const {
return GetField<uint8_t>(VT_LEADER, 0);
}
const ::flatbuffers::String *firmware() const {
return GetPointer<const ::flatbuffers::String *>(VT_FIRMWARE);
}
bool Verify(::flatbuffers::Verifier &verifier) const {
return VerifyTableStart(verifier) && VerifyField<uint8_t>(verifier, VT_MODULE_ID, 1) &&
VerifyField<int8_t>(verifier, VT_MODULE_TYPE, 1) &&
VerifyField<uint8_t>(verifier, VT_NUM_CHANNELS, 1) &&
VerifyOffset(verifier, VT_CHANNEL_TO_MODULE) &&
verifier.VerifyVector(channel_to_module()) &&
VerifyOffset(verifier, VT_CHANNEL_TO_ORIENTATION) &&
verifier.VerifyVector(channel_to_orientation()) &&
VerifyField<int8_t>(verifier, VT_CONNECTION, 1) &&
VerifyField<uint8_t>(verifier, VT_LEADER, 1) &&
VerifyOffset(verifier, VT_FIRMWARE) && verifier.VerifyString(firmware()) &&
verifier.EndTable();
}
};
struct TopologyMessageBuilder {
typedef TopologyMessage Table;
::flatbuffers::FlatBufferBuilder &fbb_;
::flatbuffers::uoffset_t start_;
void add_module_id(uint8_t module_id) {
fbb_.AddElement<uint8_t>(TopologyMessage::VT_MODULE_ID, module_id, 0);
}
void add_module_type(ModuleType module_type) {
fbb_.AddElement<int8_t>(TopologyMessage::VT_MODULE_TYPE, static_cast<int8_t>(module_type),
0);
}
void add_num_channels(uint8_t num_channels) {
fbb_.AddElement<uint8_t>(TopologyMessage::VT_NUM_CHANNELS, num_channels, 0);
}
void
add_channel_to_module(::flatbuffers::Offset<::flatbuffers::Vector<uint8_t>> channel_to_module) {
fbb_.AddOffset(TopologyMessage::VT_CHANNEL_TO_MODULE, channel_to_module);
}
void add_channel_to_orientation(
::flatbuffers::Offset<::flatbuffers::Vector<int8_t>> channel_to_orientation) {
fbb_.AddOffset(TopologyMessage::VT_CHANNEL_TO_ORIENTATION, channel_to_orientation);
}
void add_connection(Messaging::ConnectionType connection) {
fbb_.AddElement<int8_t>(TopologyMessage::VT_CONNECTION, static_cast<int8_t>(connection), 0);
}
void add_leader(uint8_t leader) {
fbb_.AddElement<uint8_t>(TopologyMessage::VT_LEADER, leader, 0);
}
void add_firmware(::flatbuffers::Offset<::flatbuffers::String> firmware) {
fbb_.AddOffset(TopologyMessage::VT_FIRMWARE, firmware);
}
explicit TopologyMessageBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) {
start_ = fbb_.StartTable();
}
::flatbuffers::Offset<TopologyMessage> Finish() {
const auto end = fbb_.EndTable(start_);
auto o = ::flatbuffers::Offset<TopologyMessage>(end);
return o;
}
};
inline ::flatbuffers::Offset<TopologyMessage> CreateTopologyMessage(
::flatbuffers::FlatBufferBuilder &_fbb, uint8_t module_id = 0,
ModuleType module_type = ModuleType_SPLITTER, uint8_t num_channels = 0,
::flatbuffers::Offset<::flatbuffers::Vector<uint8_t>> channel_to_module = 0,
::flatbuffers::Offset<::flatbuffers::Vector<int8_t>> channel_to_orientation = 0,
Messaging::ConnectionType connection = Messaging::ConnectionType_DIRECT, uint8_t leader = 0,
::flatbuffers::Offset<::flatbuffers::String> firmware = 0) {
TopologyMessageBuilder builder_(_fbb);
builder_.add_firmware(firmware);
builder_.add_channel_to_orientation(channel_to_orientation);
builder_.add_channel_to_module(channel_to_module);
builder_.add_leader(leader);
builder_.add_connection(connection);
builder_.add_num_channels(num_channels);
builder_.add_module_type(module_type);
builder_.add_module_id(module_id);
return builder_.Finish();
}
inline ::flatbuffers::Offset<TopologyMessage>
CreateTopologyMessageDirect(::flatbuffers::FlatBufferBuilder &_fbb, uint8_t module_id = 0,
ModuleType module_type = ModuleType_SPLITTER, uint8_t num_channels = 0,
const std::vector<uint8_t> *channel_to_module = nullptr,
const std::vector<int8_t> *channel_to_orientation = nullptr,
Messaging::ConnectionType connection = Messaging::ConnectionType_DIRECT,
uint8_t leader = 0, const char *firmware = nullptr) {
auto channel_to_module__ =
channel_to_module ? _fbb.CreateVector<uint8_t>(*channel_to_module) : 0;
auto channel_to_orientation__ =
channel_to_orientation ? _fbb.CreateVector<int8_t>(*channel_to_orientation) : 0;
auto firmware__ = firmware ? _fbb.CreateString(firmware) : 0;
return Messaging::CreateTopologyMessage(_fbb, module_id, module_type, num_channels,
channel_to_module__, channel_to_orientation__,
connection, leader, firmware__);
}
inline const Messaging::TopologyMessage *GetTopologyMessage(const void *buf) {
return ::flatbuffers::GetRoot<Messaging::TopologyMessage>(buf);
}
inline const Messaging::TopologyMessage *GetSizePrefixedTopologyMessage(const void *buf) {
return ::flatbuffers::GetSizePrefixedRoot<Messaging::TopologyMessage>(buf);
}
inline bool VerifyTopologyMessageBuffer(::flatbuffers::Verifier &verifier) {
return verifier.VerifyBuffer<Messaging::TopologyMessage>(nullptr);
}
inline bool VerifySizePrefixedTopologyMessageBuffer(::flatbuffers::Verifier &verifier) {
return verifier.VerifySizePrefixedBuffer<Messaging::TopologyMessage>(nullptr);
}
inline void FinishTopologyMessageBuffer(::flatbuffers::FlatBufferBuilder &fbb,
::flatbuffers::Offset<Messaging::TopologyMessage> root) {
fbb.Finish(root);
}
inline void
FinishSizePrefixedTopologyMessageBuffer(::flatbuffers::FlatBufferBuilder &fbb,
::flatbuffers::Offset<Messaging::TopologyMessage> root) {
fbb.FinishSizePrefixed(root);
}
} // namespace Messaging
#endif // FLATBUFFERS_GENERATED_TOPOLOGYMESSAGE_MESSAGING_H_

26
include/lib_c_control.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef CONTROL_LIBRARY_H
#define CONTROL_LIBRARY_H
#if defined(_WIN32) || defined(__CYGWIN__)
#ifdef CONTROL_EXPORTS
#define LIB_API __declspec(dllexport)
#else
#define LIB_API __declspec(dllimport)
#endif
#else
#define LIB_API __attribute__((visibility("default")))
#endif
extern "C" {
LIB_API void init();
LIB_API void cleanup();
LIB_API int send_angle_control(int module_id, int angle);
LIB_API char *get_configuration(int *size_out);
LIB_API bool control_sentry_init(const char *dsn, const char *environment, const char *release);
LIB_API void control_sentry_set_app_info(const char *app_name, const char *app_version,
const char *build_number);
LIB_API void control_sentry_shutdown(void);
}
#endif // CONTROL_LIBRARY_H

126
include/libcontrol.h Normal file
View File

@@ -0,0 +1,126 @@
//
// Created by Johnathon Slightham on 2025-11-13.
//
#ifndef CONTROL_LIBCONTROL_H
#define CONTROL_LIBCONTROL_H
#include <memory>
#include <shared_mutex>
#include <thread>
#include <unordered_map>
#include "spdlog/logger.h"
#include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/spdlog.h"
#include "Module.h"
#include "flatbuffers/RobotConfigurationBuilder.h"
#include "librpc.h"
/**
* \brief The root class of the BotChain control library.
*
* This class finds and connects to BotChain modules accessible to the PC.
* Providing an interface to control actuator modules, and get sensor readings
* from sensor modules.
*/
class RobotController {
public:
/**
* \brief Creates a new RobotController.
*
* Each RobotController will establish unique connections to accessible
* BotChain modules.
*/
RobotController()
: m_messaging_interface(std::make_unique<MessagingInterface>()),
m_metadata_loop(std::thread(&RobotController::metadata_loop, this)),
m_transmit_loop(std::thread(&RobotController::transmit_loop, this)),
m_configuration_loop(std::thread(&RobotController::configuration_loop, this)),
m_sensor_loop(std::thread(&RobotController::sensor_loop, this)),
m_expiry_looop(std::thread(&RobotController::expiry_loop, this)) {
m_logger = spdlog::basic_logger_mt("default_logger", "libcontrol.log");
spdlog::flush_on(spdlog::level::info);
spdlog::set_default_logger(m_logger);
}
~RobotController();
/**
* \brief Get a list of accessible modules
*
* Returns a std::vector containing robotic modules. This list includes
* modules connected directly to the PC, as well as modules connected
* through eachother. Sensor readings can be obtained, and actuation
* commands can be sent by calling the "as" function for the correct
* type (can be identified through the ModuleType).
*/
std::vector<std::weak_ptr<Module>> getModules();
/**
* \brief Get a module by ID
*
* Returns a std::vector containing robotic modules. This list includes
* modules connected directly to the PC, as well as modules connected
* through eachother. Sensor readings can be obtained, and actuation
* commands can be sent by calling the "as" function for the correct
* type (can be identified through the ModuleType).
*/
std::optional<std::weak_ptr<Module>> getModule(uint8_t device_id);
/**
* \brief Get a list of all connections.
*
* Returns a list containing all connections between modules.
*/
std::vector<Flatbuffers::ModuleConnectionInstance> getConnections();
/**
* \brief Get a list of accessible modules.
*
* Returns a list containing ID and types of each module.
*/
std::vector<Flatbuffers::ModuleInstance> getModuleList();
/**
* \brief Reset the list of modules.
*
* Reset the internal list containing known modules. Note: This list is
* automatically updated, only call this function when you need to update the
* list faster than the internal refresh logic.
*/
void resetModules();
/**
* \brief Poll for devices accessible to the PC.
*
* Manually trigger a poll for devices accessible to the PC.
*/
void fetchDirectlyConnectedModules(bool block);
private:
std::shared_ptr<spdlog::logger> m_logger;
std::unordered_map<uint8_t, std::shared_ptr<Module>> m_id_to_module{};
std::unordered_map<uint8_t, std::vector<Flatbuffers::ModuleConnectionInstance>>
m_connection_map{};
std::shared_mutex m_module_lock{};
std::shared_mutex m_connection_lock{};
std::shared_ptr<MessagingInterface> m_messaging_interface;
std::atomic<bool> m_stop_thread{false}; // todo: make sure threads stop if we
// dont get any messages (timeouts)
std::thread m_metadata_loop;
std::thread m_transmit_loop;
std::thread m_configuration_loop;
std::thread m_sensor_loop;
std::thread m_expiry_looop;
void metadata_loop();
void transmit_loop();
void configuration_loop();
void sensor_loop();
void expiry_loop();
};
#endif // CONTROL_LIBCONTROL_H

50
include/util/Event.h Normal file
View File

@@ -0,0 +1,50 @@
#ifndef CONTROL_EVENT_H
#define CONTROL_EVENT_H
#include <functional>
class Event;
class CallbackHandle {
public:
CallbackHandle(Event *evt, int id) : evt(evt), id(id) {
}
~CallbackHandle();
private:
Event *evt;
int id;
};
class Event {
public:
using Callback = std::function<void()>;
CallbackHandle addListener(Callback cb) {
int id = nextId++;
callbacks[id] = std::move(cb);
return CallbackHandle(this, id);
}
void remove(int id) {
callbacks.erase(id);
}
void fire() {
for (auto &kv : callbacks)
kv.second();
}
private:
friend class CallbackHandle;
std::unordered_map<int, Callback> callbacks;
int nextId = 0;
};
CallbackHandle::~CallbackHandle() {
if (evt)
evt->remove(id);
}
#endif // CONTROL_EVENT_H

18
include/util/Map.h Normal file
View File

@@ -0,0 +1,18 @@
#ifndef CONTROL_MAP_H
#define CONTROL_MAP_H
#include <unordered_map>
#include <vector>
template <typename K, typename V, typename H>
std::vector<V> map_to_values(const std::unordered_map<K, V, H> &map) {
std::vector<V> out;
out.reserve(map.size());
for (auto const &[key, value] : map) {
out.push_back(value);
}
return out;
}
#endif // CONTROL_MAP_H

14
include/util/PairHash.h Normal file
View File

@@ -0,0 +1,14 @@
#ifndef CONTROL_PAIRHASH_H
#define CONTROL_PAIRHASH_H
#include <unordered_map>
// Custom hash function for std::pair
template <typename T> struct pair_hash {
std::size_t operator()(const std::pair<T, T> &p) const {
return std::hash<T>()(p.first) ^ (std::hash<T>()(p.second) << 1);
}
};
#endif // CONTROL_PAIRHASH_H

13
include/util/Variant.h Normal file
View File

@@ -0,0 +1,13 @@
#ifndef VARIANT_H
#define VARIANT_H
#include <variant> // NOLINT
template <class... Ts> struct overloaded : Ts... {
using Ts::operator()...;
};
template <class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
#endif // VARIANT_H

24
src/Hub.cpp Normal file
View File

@@ -0,0 +1,24 @@
#include "Hub.h"
#include "flatbuffers/SensorMessageBuilder.h"
double Hub::get_position() {
// no-op
return 0;
}
void Hub::actuate(double /* position */) {
// no-op
}
void Hub::actuate(double /* x */, double /* y */) {
// no-op
}
std::vector<uint8_t> Hub::get_actuation_message() {
// no-op
return {};
}
void Hub::update_sensor_data(const Flatbuffers::sensor_value & /* value */) {
// no-op
}

43
src/Module.cpp Normal file
View File

@@ -0,0 +1,43 @@
#include <chrono>
#include <ranges>
#include "Module.h"
std::vector<neighbour> Module::get_neighbours() {
return m_neighbours;
}
uint8_t Module::get_device_id() {
return m_device_id;
}
ModuleType Module::get_type() {
return m_module_type;
}
Messaging::ConnectionType Module::get_connection_type() {
return m_connection_type;
}
uint8_t Module::get_leader() {
return m_leader;
}
std::chrono::time_point<std::chrono::system_clock> Module::get_last_updated_time() {
return m_last_updated;
}
void Module::update_module_metadata(const Messaging::TopologyMessage &message) {
m_module_type = message.module_type();
m_connection_type = message.connection();
m_leader = message.leader();
m_last_updated = std::chrono::system_clock::now();
m_neighbours.clear();
for (auto [id, ori] :
std::views::zip(*message.channel_to_module(), *message.channel_to_orientation())) {
m_neighbours.emplace_back(neighbour{id, static_cast<Orientation>(ori)});
}
}

28
src/ModuleFactory.cpp Normal file
View File

@@ -0,0 +1,28 @@
#include "ModuleFactory.h"
#include "Hub.h"
#include "actuators/BoundedPositionalActuator1D.h"
#include "actuators/PositionalActuator1D.h"
#include "flatbuffers_generated/RobotModule_generated.h"
#define SERVO1_MAX_ANGLE 180
#define SERVO1_MIN_ANGLE 0
#define SERVO1_DEFAULT_ANGLE 90
std::shared_ptr<Module>
ModuleFactory::createModule(uint8_t device_id, ModuleType type,
std::shared_ptr<MessagingInterface> &messaging_interface) {
switch (type) {
case ModuleType_SPLITTER:
return std::make_shared<Hub>(device_id, type);
case ModuleType_BATTERY:
return std::make_shared<Hub>(device_id, type);
case ModuleType_SERVO_1:
case ModuleType_SERVO_2:
return std::make_shared<BoundedPositionalActuator1D>(
device_id, type, SERVO1_MAX_ANGLE, SERVO1_MIN_ANGLE, SERVO1_DEFAULT_ANGLE);
case ModuleType_DC_MOTOR:
return std::make_shared<PositionalActuator1D>(device_id, type);
}
return nullptr;
}

View File

@@ -0,0 +1,43 @@
#include <cstring>
#include <variant>
#include "actuators/BoundedPositionalActuator1D.h"
#include "flatbuffers/SensorMessageBuilder.h"
#include "util/Variant.h"
double BoundedPositionalActuator1D::get_position() {
return m_current_position;
}
void BoundedPositionalActuator1D::actuate(double position) {
if (position < m_min_value || position > m_max_value) {
return;
}
m_target_position = position;
}
void BoundedPositionalActuator1D::actuate(double x, double y) {
}
std::vector<uint8_t> BoundedPositionalActuator1D::get_actuation_message() {
std::vector<uint8_t> message{};
if (m_target_position == m_current_position) {
return message;
}
auto [data, size] = acm_builder->build_angle_control_message(m_target_position);
message.resize(size);
memcpy(message.data(), data, size);
return message;
}
void BoundedPositionalActuator1D::update_sensor_data(const Flatbuffers::sensor_value &value) {
std::visit(overloaded{
[this](Flatbuffers::target_angle a) { m_current_position = a.angle; },
[this](Flatbuffers::current_angle a) { m_current_position = a.angle; },
},
value);
}

View File

@@ -0,0 +1,36 @@
#include "actuators/PositionalActuator1D.h"
#include "flatbuffers/SensorMessageBuilder.h"
#include "util/Variant.h"
#include <cstring>
double PositionalActuator1D::get_position() {
return m_current_position;
}
void PositionalActuator1D::actuate(double position) {
m_target_position = position;
}
void PositionalActuator1D::actuate(double /* x */, double /* y */) {
}
std::vector<uint8_t> PositionalActuator1D::get_actuation_message() {
std::vector<uint8_t> message{};
if (m_target_position == m_board_target_position) {
return message;
}
auto [data, size] = m_acm_builder->build_angle_control_message(m_target_position);
message.resize(size);
memcpy(message.data(), data, size);
return message;
}
void PositionalActuator1D::update_sensor_data(const Flatbuffers::sensor_value &value) {
std::visit(overloaded{
[this](Flatbuffers::target_angle a) { m_board_target_position = a.angle; },
[this](Flatbuffers::current_angle a) { m_current_position = a.angle; },
},
value);
}

View File

@@ -0,0 +1,22 @@
//
// Created by Johnathon Slightham on 2025-06-30.
//
#include "flatbuffers/AngleControlMessageBuilder.h"
namespace Flatbuffers {
const Messaging::AngleControlMessage *
AngleControlMessageBuilder::parse_angle_control_message(const uint8_t *buffer) {
return flatbuffers::GetRoot<Messaging::AngleControlMessage>(buffer);
}
SerializedMessage AngleControlMessageBuilder::build_angle_control_message(const int16_t angle) {
builder_.Clear();
const auto message = Messaging::CreateAngleControlMessage(builder_, angle);
builder_.Finish(message);
return {builder_.GetBufferPointer(), builder_.GetSize()};
}
} // namespace Flatbuffers

View File

@@ -0,0 +1,49 @@
//
// Created by Johnathon Slightham on 2025-07-25.
//
#include "flatbuffers/RobotConfigurationBuilder.h"
#include "flatbuffers/SerializedMessage.h"
#include "flatbuffers_generated/RobotConfiguration_generated.h"
namespace Flatbuffers {
SerializedMessage RobotConfigurationBuilder::build_robot_configuration(
const std::vector<ModuleInstance> &modules,
const std::vector<ModuleConnectionInstance> &connections) {
builder_.Clear();
std::vector<flatbuffers::Offset<RobotModule>> module_vector;
std::vector<flatbuffers::Offset<Frontend::ModuleConnection>> connection_vector;
for (const auto &connection : connections) {
connection_vector.push_back(Frontend::CreateModuleConnection(
builder_, connection.from_module_id, connection.to_module_id, connection.from_socket,
connection.to_socket, connection.orientation));
}
for (const auto &module : modules) {
// todo: this only works for motors right now
auto motor_state = CreateMotorState(builder_, module.angle);
const flatbuffers::Offset<void> config_union = motor_state.Union();
module_vector.push_back(CreateRobotModule(builder_, module.id, module.type,
ModuleState_MotorState, config_union));
}
const auto connection_vector_fb = builder_.CreateVector(connection_vector);
const auto module_vector_fb = builder_.CreateVector(module_vector);
const auto message =
Frontend::CreateRobotConfiguration(builder_, module_vector_fb, connection_vector_fb);
builder_.Finish(message);
return {builder_.GetBufferPointer(), builder_.GetSize()};
}
const Frontend::RobotConfiguration *
RobotConfigurationBuilder::parse_robot_configuration(const std::uint8_t *buffer) {
return flatbuffers::GetRoot<Frontend::RobotConfiguration>(buffer);
}
} // namespace Flatbuffers

View File

@@ -0,0 +1,10 @@
#include "flatbuffers/SensorMessageBuilder.h"
#include "flatbuffers_generated/SensorMessage_generated.h"
namespace Flatbuffers {
const Messaging::SensorMessage *SensorMessageBuilder::parse_sensor_message(const uint8_t *buffer) {
return flatbuffers::GetRoot<Messaging::SensorMessage>(buffer);
}
} // namespace Flatbuffers

View File

@@ -0,0 +1,38 @@
//
// Created by Johnathon Slightham on 2025-06-30.
//
#include "flatbuffers/TopologyMessageBuilder.h"
#include "flatbuffers/SerializedMessage.h"
namespace Flatbuffers {
SerializedMessage
TopologyMessageBuilder::build_topology_message(const uint8_t module_id,
const ModuleType module_type,
const std::vector<uint8_t> &channel_to_module,
const std::vector<int8_t> &orientation_to_module) {
builder_.Clear();
const auto orientation_to_module_vector = builder_.CreateVector(orientation_to_module);
const auto channel_to_module_vector = builder_.CreateVector(channel_to_module);
const auto message =
Messaging::CreateTopologyMessage(builder_, module_id, module_type, channel_to_module.size(),
channel_to_module_vector, orientation_to_module_vector);
builder_.Finish(message);
return {builder_.GetBufferPointer(), builder_.GetSize()};
}
const Messaging::TopologyMessage *
TopologyMessageBuilder::parse_topology_message(const uint8_t *buffer) {
return flatbuffers::GetRoot<Messaging::TopologyMessage>(buffer);
}
bool TopologyMessageBuilder::is_valid_topology_message(const uint8_t *buffer, size_t size) {
flatbuffers::Verifier verifier(buffer, size);
return Messaging::VerifyTopologyMessageBuffer(verifier);
}
} // namespace Flatbuffers

102
src/lib_c_control.cpp Normal file
View File

@@ -0,0 +1,102 @@
#include "lib_c_control.h"
#include "flatbuffers/AngleControlMessageBuilder.h"
#include "flatbuffers/RobotConfigurationBuilder.h"
#include "libcontrol.h"
#include "sentry.h"
#undef min
#undef max
#define ACTUATOR_CONTROL_TAG 5
#define TOPOLOGY_CMD_TAG 6
#define MAX_RMT_CHANNELS 4
extern "C" {
const auto robot_controller = std::make_unique<RobotController>();
const auto acm_builder = std::make_unique<Flatbuffers::AngleControlMessageBuilder>();
const auto robot_configuration_builder = std::make_unique<Flatbuffers::RobotConfigurationBuilder>();
LIB_API void init() {
spdlog::info("[c_control] Initializing");
robot_controller->fetchDirectlyConnectedModules(false);
}
LIB_API void cleanup() {
spdlog::info("[c_control] Cleanup");
}
LIB_API int send_angle_control(int module_id, int angle) {
if (const auto maybe_module = robot_controller->getModule(module_id)) {
const auto module = (*maybe_module).lock();
module->actuate(angle);
}
return 0;
}
LIB_API char *get_configuration(int *size_out) {
std::vector<Flatbuffers::ModuleInstance> modules_vec{};
std::vector<Flatbuffers::ModuleConnectionInstance> connections_vec{};
for (const auto &module : robot_controller->getModuleList()) {
modules_vec.emplace_back(module);
}
for (const auto &connection : robot_controller->getConnections()) {
connections_vec.emplace_back(connection);
}
const auto [data, size] =
robot_configuration_builder->build_robot_configuration(modules_vec, connections_vec);
*size_out = size;
return reinterpret_cast<char *>(data);
}
LIB_API bool control_sentry_init(const char *dsn, const char *environment, const char *release) {
sentry_options_t *options = sentry_options_new();
sentry_options_set_dsn(options, dsn);
sentry_options_set_environment(options, environment);
sentry_options_set_release(options, release);
sentry_options_set_auto_session_tracking(options, 1);
sentry_init(options);
return true;
}
LIB_API void control_sentry_shutdown(void) {
sentry_close();
}
LIB_API void control_sentry_set_app_info(const char *app_name, const char *app_version,
const char *build_number) {
sentry_value_t app = sentry_value_new_object();
if (app_name && *app_name) {
sentry_value_set_by_key(app, "name", sentry_value_new_string(app_name));
}
if (app_version && *app_version) {
sentry_value_set_by_key(app, "version", sentry_value_new_string(app_version));
}
if (build_number && *build_number) {
sentry_value_set_by_key(app, "build", sentry_value_new_string(build_number));
}
sentry_set_context("app", app);
if (app_version && *app_version) {
if (build_number && *build_number) {
char release[256];
snprintf(release, sizeof(release), "%s@%s+%s", app_name && *app_name ? app_name : "app",
app_version, build_number);
} else {
// Example: mygame@1.2.3
char release[256];
snprintf(release, sizeof(release), "%s@%s", app_name && *app_name ? app_name : "app",
app_version);
}
}
}
}

249
src/libcontrol.cpp Normal file
View File

@@ -0,0 +1,249 @@
//
// Created by Johnathon Slightham on 2025-11-13.
//
#include <chrono>
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <system_error>
#include <thread>
#include "flatbuffers_generated/SensorMessage_generated.h"
#include "spdlog/spdlog.h"
#include "ModuleFactory.h"
#include "flatbuffers/RobotConfigurationBuilder.h"
#include "flatbuffers/SensorMessageBuilder.h"
#include "flatbuffers/TopologyMessageBuilder.h"
#include "flatbuffers_generated/TopologyMessage_generated.h"
#include "libcontrol.h"
#include "util/Map.h"
#define SCAN_DURATION_MS 2000
#define ACTUATOR_CMD_TAG 5
#define TOPOLOGY_CMD_TAG 6
#define SENSOR_CMD_TAG 8
#define NUM_CHANNELS 4
#define TOPOLOGY_BUFFER_SIZE 1024
#define SENSOR_BUFFER_SIZE 1024
#define NETWORK_CONFIG_FETCH_RATE 3s
#define MODULE_EXPIRE_TIME 3s
#define CONTROL_MESSAGE_FREQUENCY 33ms
using namespace std::chrono_literals;
RobotController::~RobotController() {
m_stop_thread = true;
m_metadata_loop.join();
m_transmit_loop.join();
m_configuration_loop.join();
m_sensor_loop.join();
m_expiry_looop.join();
}
std::vector<std::weak_ptr<Module>> RobotController::getModules() {
std::vector<std::weak_ptr<Module>> out;
std::shared_lock lock(m_module_lock);
out.reserve(m_id_to_module.size());
for (const auto m : map_to_values(m_id_to_module)) {
out.emplace_back(m);
}
return out;
}
std::vector<Flatbuffers::ModuleConnectionInstance> RobotController::getConnections() {
std::vector<Flatbuffers::ModuleConnectionInstance> out;
std::shared_lock lock(m_connection_lock);
for (auto const &[_, value] : m_connection_map) {
for (const auto conn : value) {
out.push_back(conn);
}
}
return out;
}
std::vector<Flatbuffers::ModuleInstance> RobotController::getModuleList() {
std::vector<Flatbuffers::ModuleInstance> out;
std::shared_lock lock(m_module_lock);
for (auto const &[key, value] : m_id_to_module) {
out.push_back({key, value->get_type()});
}
return out;
}
std::optional<std::weak_ptr<Module>> RobotController::getModule(uint8_t device_id) {
std::shared_lock lock(m_module_lock);
if (m_id_to_module.contains(device_id)) {
return m_id_to_module[device_id];
} else {
return std::nullopt;
}
}
void RobotController::resetModules() {
std::unique_lock module_lock(m_module_lock);
std::unique_lock conn_lock(m_connection_lock);
m_id_to_module.erase(m_id_to_module.begin(), m_id_to_module.end());
m_connection_map.erase(m_connection_map.begin(), m_connection_map.end());
}
void RobotController::fetchDirectlyConnectedModules(bool block) {
spdlog::info("[Control] Fetching modules from network");
auto t = std::thread([&] {
auto out = m_messaging_interface->find_connected_modules(
std::chrono::milliseconds(SCAN_DURATION_MS));
spdlog::info("[Control] Found {} modules on the network", out.size());
});
if (block) {
t.join();
} else {
t.detach();
}
}
void RobotController::metadata_loop() {
unsigned char buf[TOPOLOGY_BUFFER_SIZE];
const auto builder = std::make_unique<Flatbuffers::TopologyMessageBuilder>();
while (!m_stop_thread) {
if (auto result =
m_messaging_interface->recv(buf, TOPOLOGY_BUFFER_SIZE, TOPOLOGY_CMD_TAG)) {
const auto &[rx_size, from] = *result;
flatbuffers::Verifier verifier(buf, rx_size);
if (!Messaging::VerifyTopologyMessageBuffer(verifier)) {
continue;
}
const auto metadata = builder->parse_topology_message(reinterpret_cast<uint8_t *>(buf));
std::unique_lock lock(m_module_lock);
if (!m_id_to_module.contains(metadata->module_id())) {
spdlog::info("[Control] Creating module entry for {}", metadata->module_id());
m_id_to_module.insert(
{metadata->module_id(),
ModuleFactory::createModule(metadata->module_id(), metadata->module_type(),
m_messaging_interface)});
}
m_id_to_module[metadata->module_id()]->update_module_metadata(*metadata);
std::unique_lock conn_lock(m_connection_lock);
std::vector<Flatbuffers::ModuleConnectionInstance> conns;
for (uint8_t i = 1; i < NUM_CHANNELS; i++) {
if (metadata->channel_to_module()->Get(i) > 0) {
conns.push_back(Flatbuffers::ModuleConnectionInstance{
.from_module_id = metadata->module_id(),
.to_module_id = metadata->channel_to_module()->Get(i),
.from_socket = i,
.to_socket = 0,
.orientation =
static_cast<Orientation>(metadata->channel_to_orientation()->Get(i))});
}
}
m_connection_map[metadata->module_id()] = conns;
}
}
}
void RobotController::transmit_loop() {
const auto builder = std::make_unique<Flatbuffers::TopologyMessageBuilder>();
while (!m_stop_thread) {
std::this_thread::sleep_for(CONTROL_MESSAGE_FREQUENCY);
std::shared_lock lock(m_module_lock);
for (const auto [id, module] : m_id_to_module) {
auto out = module->get_actuation_message();
if (out.size() > 0) {
m_messaging_interface->send(out.data(), out.size(), id, ACTUATOR_CMD_TAG, false);
}
}
}
}
void RobotController::configuration_loop() {
while (!m_stop_thread) {
std::this_thread::sleep_for(NETWORK_CONFIG_FETCH_RATE);
fetchDirectlyConnectedModules(true);
}
}
void RobotController::expiry_loop() {
while (!m_stop_thread) {
std::this_thread::sleep_for(MODULE_EXPIRE_TIME);
std::unordered_set<uint8_t> delete_modules{};
std::unique_lock module_lock(m_module_lock);
std::unique_lock connection_lock(m_connection_lock);
for (auto it = m_id_to_module.begin(); it != m_id_to_module.end();) {
if (it->second->get_last_updated_time() <
std::chrono::system_clock::now() - MODULE_EXPIRE_TIME) {
delete_modules.emplace(it->first);
it = m_id_to_module.erase(it);
} else {
++it;
}
}
// todo
// Remove connections
// for (auto it = m_connection_map.begin(); it != m_connection_map.end();) {
// // Remove it->x connections
// if (delete_modules.contains(it->first)) {
// it = m_connection_map.erase(it);
// } else {
// ++it;
// }
// // Remove x->it connections
// for (auto it2 = it->second.begin(); it2 != it->second.end();) {
// if (delete_modules.contains(it2->to_module_id)) {
// it2 = it->second.erase(it2);
// } else {
// ++it2;
// }
// }
// }
}
}
void RobotController::sensor_loop() {
unsigned char buf[SENSOR_BUFFER_SIZE];
const auto builder = std::make_unique<Flatbuffers::SensorMessageBuilder>();
while (!m_stop_thread) {
if (auto result = m_messaging_interface->recv(buf, SENSOR_BUFFER_SIZE, SENSOR_CMD_TAG)) {
const auto &[rx_size, from] = *result;
flatbuffers::Verifier verifier(buf, rx_size);
if (!Messaging::VerifySensorMessageBuffer(verifier)) {
continue;
}
const auto sensor_message =
builder->parse_sensor_message(reinterpret_cast<uint8_t *>(buf));
if (sensor_message->values()->size() != sensor_message->values_type()->size()) {
spdlog::error(
"[Control] Got a sensor message with different value ({}) and type ({}) sizes",
sensor_message->values()->size(), sensor_message->values_type()->size());
continue;
}
std::shared_lock module_lock(m_module_lock);
if (!m_id_to_module.contains(from)) {
continue;
}
for (int i = 0; i < sensor_message->values()->size(); i++) {
if (auto maybe_value = Flatbuffers::SensorMessageBuilder::build_sensor_value(
static_cast<Messaging::SensorValue>(sensor_message->values_type()->Get(i)),
sensor_message->values()->Get(i))) {
m_id_to_module[from]->update_sensor_data(*maybe_value);
}
}
}
}
}