diff --git a/.gitignore b/.gitignore index d4fb281..c0b0372 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,9 @@ # debug information files *.dwo + +/build +.DS_Store +/cmake-build-debug +/.cache +**/.DS_Store diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3807246 --- /dev/null +++ b/CMakeLists.txt @@ -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) diff --git a/CMakeUserPresets.json b/CMakeUserPresets.json new file mode 100644 index 0000000..52acfdc --- /dev/null +++ b/CMakeUserPresets.json @@ -0,0 +1,9 @@ +{ + "version": 4, + "vendor": { + "conan": {} + }, + "include": [ + "build/RelWithDebInfo/generators/CMakePresets.json" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a72f3af --- /dev/null +++ b/README.md @@ -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 -p +``` + +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 +``` diff --git a/build_control_library.sh b/build_control_library.sh new file mode 100755 index 0000000..4faaedc --- /dev/null +++ b/build_control_library.sh @@ -0,0 +1,61 @@ +#!/bin/bash +set -e + +function usage() { + echo "Usage:" + echo "${SCRIPT_NAME} [-b ] [-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}" diff --git a/compile_commands.json b/compile_commands.json new file mode 120000 index 0000000..ddafb2a --- /dev/null +++ b/compile_commands.json @@ -0,0 +1 @@ +build/RelWithDebInfo/compile_commands.json \ No newline at end of file diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 0000000..4494ef2 --- /dev/null +++ b/conanfile.py @@ -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 diff --git a/control.def b/control.def new file mode 100644 index 0000000..d819679 --- /dev/null +++ b/control.def @@ -0,0 +1,8 @@ +EXPORTS +init +cleanup +send_angle_control +get_configuration +control_sentry_init +control_sentry_set_app_info +control_sentry_shutdown diff --git a/include/Hub.h b/include/Hub.h new file mode 100644 index 0000000..56c02b2 --- /dev/null +++ b/include/Hub.h @@ -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 get_actuation_message() override; + void update_sensor_data(const Flatbuffers::sensor_value &value) override; +}; + +#endif // CONTROL_HUB_H diff --git a/include/Module.h b/include/Module.h new file mode 100644 index 0000000..7c88e17 --- /dev/null +++ b/include/Module.h @@ -0,0 +1,66 @@ +// +// Created by Johnathon Slightham on 2025-11-14. +// + +#ifndef CONTROL_MODULE_H +#define CONTROL_MODULE_H + +#include +#include + +#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 get_neighbours(); + + uint8_t get_device_id(); + + ModuleType get_type(); + + Messaging::ConnectionType get_connection_type(); + + uint8_t get_leader(); + + std::chrono::time_point 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 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 m_last_updated; + std::vector m_neighbours; + std::shared_ptr m_messaging_interface; +}; + +#endif // CONTROL_MODULE_H diff --git a/include/ModuleFactory.h b/include/ModuleFactory.h new file mode 100644 index 0000000..32f9ecb --- /dev/null +++ b/include/ModuleFactory.h @@ -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 + createModule(uint8_t device_id, ModuleType type, + std::shared_ptr &messaging_interface); +}; + +#endif // CONTROL_MODULEFACTORY_H diff --git a/include/actuators/Actuator.h b/include/actuators/Actuator.h new file mode 100644 index 0000000..90c7ddf --- /dev/null +++ b/include/actuators/Actuator.h @@ -0,0 +1,28 @@ + +#ifndef CONTROL_ACTUATOR_H +#define CONTROL_ACTUATOR_H + +#include + +#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 get_actuation_message() = 0; + + private: + [[noreturn]] void actuator_tx_loop(); +}; + +#endif // CONTROL_MODULE_H diff --git a/include/actuators/BoundedPositionalActuator1D.h b/include/actuators/BoundedPositionalActuator1D.h new file mode 100644 index 0000000..af6ae40 --- /dev/null +++ b/include/actuators/BoundedPositionalActuator1D.h @@ -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()) { + } + + double get_position() override; + void actuate(double position) override; + void actuate(double x, double y) override; // no-op + + std::vector 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 acm_builder; +}; + +#endif // CONTROL_1DPOSITIONALACTUATOR_H diff --git a/include/actuators/PositionalActuator1D.h b/include/actuators/PositionalActuator1D.h new file mode 100644 index 0000000..97128eb --- /dev/null +++ b/include/actuators/PositionalActuator1D.h @@ -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()) {}; + + double get_position() override; + void actuate(double position) override; + void actuate(double x, double y) override; // no-op + + std::vector 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 m_acm_builder; +}; + +#endif // CONTROL_1DPOSITIONALACTUATOR_H diff --git a/include/flatbuffers/AngleControlMessageBuilder.h b/include/flatbuffers/AngleControlMessageBuilder.h new file mode 100644 index 0000000..291c7e1 --- /dev/null +++ b/include/flatbuffers/AngleControlMessageBuilder.h @@ -0,0 +1,29 @@ +// +// Created by Johnathon Slightham on 2025-06-30. +// + +#ifndef ANGLECONTROLMESSAGEBUILDER_H_ +#define ANGLECONTROLMESSAGEBUILDER_H_ + +#include +#include + +#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 diff --git a/include/flatbuffers/RobotConfigurationBuilder.h b/include/flatbuffers/RobotConfigurationBuilder.h new file mode 100644 index 0000000..90a4fb7 --- /dev/null +++ b/include/flatbuffers/RobotConfigurationBuilder.h @@ -0,0 +1,45 @@ +// +// Created by Johnathon Slightham on 2025-07-25. +// + +#ifndef ROBOTCONFIGURATIONBUILDER_H +#define ROBOTCONFIGURATIONBUILDER_H +#include "SerializedMessage.h" +#include +#include +#include +#include + +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 &modules, + const std::vector &connections); + + static const Frontend::RobotConfiguration * + parse_robot_configuration(const std::uint8_t *buffer); + + private: + flatbuffers::FlatBufferBuilder builder_; +}; +} // namespace Flatbuffers + +#endif // ROBOTCONFIGURATIONBUILDER_H diff --git a/include/flatbuffers/SensorMessageBuilder.h b/include/flatbuffers/SensorMessageBuilder.h new file mode 100644 index 0000000..d712ee4 --- /dev/null +++ b/include/flatbuffers/SensorMessageBuilder.h @@ -0,0 +1,54 @@ +// +// Created by Johnathon Slightham on 2025-06-30. +// + +#ifndef SENSORMESSAGEBUILDER_H +#define SENSORMESSAGEBUILDER_H + +#include + +#include "flatbuffers_generated/SensorMessage_generated.h" + +namespace Flatbuffers { + +struct target_angle { + int16_t angle; +}; + +struct current_angle { + int16_t angle; +}; + +typedef std::variant sensor_value; + +class SensorMessageBuilder { + public: + SensorMessageBuilder() : builder_(1024) { + } + + static const Messaging::SensorMessage *parse_sensor_message(const std::uint8_t *buffer); + + template + static std::optional build_sensor_value(Messaging::SensorValue type, T value) { + switch (type) { + case Messaging::SensorValue_TargetAngle: { + const Messaging::TargetAngle *target = + static_cast(value); + return target_angle{target->value()}; + } + case Messaging::SensorValue_CurrentAngle: { + const Messaging::CurrentAngle *current = + static_cast(value); + return current_angle{current->value()}; + } + default: + return std::nullopt; + } + } + + private: + flatbuffers::FlatBufferBuilder builder_; +}; +} // namespace Flatbuffers + +#endif // TOPOLOGYMESSAGEBUILDER_H diff --git a/include/flatbuffers/SerializedMessage.h b/include/flatbuffers/SerializedMessage.h new file mode 100644 index 0000000..ac51bb8 --- /dev/null +++ b/include/flatbuffers/SerializedMessage.h @@ -0,0 +1,17 @@ +// +// Created by Johnathon Slightham on 2025-07-05. +// + +#ifndef SERIALIZEDMESSAGE_H +#define SERIALIZEDMESSAGE_H + +#include + +namespace Flatbuffers { +struct SerializedMessage { + void *data; + size_t size; +}; +} // namespace Flatbuffers + +#endif // SERIALIZEDMESSAGE_H diff --git a/include/flatbuffers/TopologyMessageBuilder.h b/include/flatbuffers/TopologyMessageBuilder.h new file mode 100644 index 0000000..d1ff220 --- /dev/null +++ b/include/flatbuffers/TopologyMessageBuilder.h @@ -0,0 +1,32 @@ +// +// Created by Johnathon Slightham on 2025-06-30. +// + +#ifndef TOPOLOGYMESSAGEBUILDER_H +#define TOPOLOGYMESSAGEBUILDER_H + +#include + +#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 &channel_to_module, + const std::vector &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 diff --git a/include/flatbuffers_generated/AngleControlMessage_generated.h b/include/flatbuffers_generated/AngleControlMessage_generated.h new file mode 100644 index 0000000..4724a5c --- /dev/null +++ b/include/flatbuffers_generated/AngleControlMessage_generated.h @@ -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(VT_ANGLE, 0); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && VerifyField(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(AngleControlMessage::VT_ANGLE, angle, 0); + } + explicit AngleControlMessageBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset +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(buf); +} + +inline const Messaging::AngleControlMessage *GetSizePrefixedAngleControlMessage(const void *buf) { + return ::flatbuffers::GetSizePrefixedRoot(buf); +} + +inline bool VerifyAngleControlMessageBuffer(::flatbuffers::Verifier &verifier) { + return verifier.VerifyBuffer(nullptr); +} + +inline bool VerifySizePrefixedAngleControlMessageBuffer(::flatbuffers::Verifier &verifier) { + return verifier.VerifySizePrefixedBuffer(nullptr); +} + +inline void +FinishAngleControlMessageBuffer(::flatbuffers::FlatBufferBuilder &fbb, + ::flatbuffers::Offset root) { + fbb.Finish(root); +} + +inline void FinishSizePrefixedAngleControlMessageBuffer( + ::flatbuffers::FlatBufferBuilder &fbb, + ::flatbuffers::Offset root) { + fbb.FinishSizePrefixed(root); +} + +} // namespace Messaging + +#endif // FLATBUFFERS_GENERATED_ANGLECONTROLMESSAGE_MESSAGING_H_ diff --git a/include/flatbuffers_generated/RobotConfiguration_generated.h b/include/flatbuffers_generated/RobotConfiguration_generated.h new file mode 100644 index 0000000..7749b05 --- /dev/null +++ b/include/flatbuffers_generated/RobotConfiguration_generated.h @@ -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(VT_FROM_MODULE_ID, 0); + } + uint8_t to_module_id() const { + return GetField(VT_TO_MODULE_ID, 0); + } + uint8_t from_socket() const { + return GetField(VT_FROM_SOCKET, 0); + } + uint8_t to_socket() const { + return GetField(VT_TO_SOCKET, 0); + } + Orientation orientation() const { + return static_cast(GetField(VT_ORIENTATION, 0)); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && VerifyField(verifier, VT_FROM_MODULE_ID, 1) && + VerifyField(verifier, VT_TO_MODULE_ID, 1) && + VerifyField(verifier, VT_FROM_SOCKET, 1) && + VerifyField(verifier, VT_TO_SOCKET, 1) && + VerifyField(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(ModuleConnection::VT_FROM_MODULE_ID, from_module_id, 0); + } + void add_to_module_id(uint8_t to_module_id) { + fbb_.AddElement(ModuleConnection::VT_TO_MODULE_ID, to_module_id, 0); + } + void add_from_socket(uint8_t from_socket) { + fbb_.AddElement(ModuleConnection::VT_FROM_SOCKET, from_socket, 0); + } + void add_to_socket(uint8_t to_socket) { + fbb_.AddElement(ModuleConnection::VT_TO_SOCKET, to_socket, 0); + } + void add_orientation(Orientation orientation) { + fbb_.AddElement(ModuleConnection::VT_ORIENTATION, static_cast(orientation), + 0); + } + explicit ModuleConnectionBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset +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> *modules() const { + return GetPointer> *>( + VT_MODULES); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> * + connections() const { + return GetPointer< + const ::flatbuffers::Vector<::flatbuffers::Offset> *>( + 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>> modules) { + fbb_.AddOffset(RobotConfiguration::VT_MODULES, modules); + } + void add_connections(::flatbuffers::Offset< + ::flatbuffers::Vector<::flatbuffers::Offset>> + connections) { + fbb_.AddOffset(RobotConfiguration::VT_CONNECTIONS, connections); + } + explicit RobotConfigurationBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateRobotConfiguration( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> modules = 0, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> + connections = 0) { + RobotConfigurationBuilder builder_(_fbb); + builder_.add_connections(connections); + builder_.add_modules(modules); + return builder_.Finish(); +} + +inline ::flatbuffers::Offset CreateRobotConfigurationDirect( + ::flatbuffers::FlatBufferBuilder &_fbb, + const std::vector<::flatbuffers::Offset> *modules = nullptr, + const std::vector<::flatbuffers::Offset> *connections = nullptr) { + auto modules__ = modules ? _fbb.CreateVector<::flatbuffers::Offset>(*modules) : 0; + auto connections__ = + connections + ? _fbb.CreateVector<::flatbuffers::Offset>(*connections) + : 0; + return Frontend::CreateRobotConfiguration(_fbb, modules__, connections__); +} + +inline const Frontend::RobotConfiguration *GetRobotConfiguration(const void *buf) { + return ::flatbuffers::GetRoot(buf); +} + +inline const Frontend::RobotConfiguration *GetSizePrefixedRobotConfiguration(const void *buf) { + return ::flatbuffers::GetSizePrefixedRoot(buf); +} + +inline bool VerifyRobotConfigurationBuffer(::flatbuffers::Verifier &verifier) { + return verifier.VerifyBuffer(nullptr); +} + +inline bool VerifySizePrefixedRobotConfigurationBuffer(::flatbuffers::Verifier &verifier) { + return verifier.VerifySizePrefixedBuffer(nullptr); +} + +inline void +FinishRobotConfigurationBuffer(::flatbuffers::FlatBufferBuilder &fbb, + ::flatbuffers::Offset root) { + fbb.Finish(root); +} + +inline void FinishSizePrefixedRobotConfigurationBuffer( + ::flatbuffers::FlatBufferBuilder &fbb, + ::flatbuffers::Offset root) { + fbb.FinishSizePrefixed(root); +} + +} // namespace Frontend + +#endif // FLATBUFFERS_GENERATED_ROBOTCONFIGURATION_FRONTEND_H_ diff --git a/include/flatbuffers_generated/RobotModule_generated.h b/include/flatbuffers_generated/RobotModule_generated.h new file mode 100644 index 0000000..823a912 --- /dev/null +++ b/include/flatbuffers_generated/RobotModule_generated.h @@ -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(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(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(e); + return EnumNamesModuleState()[index]; +} + +template struct ModuleStateTraits { + static const ModuleState enum_value = ModuleState_NONE; +}; + +template <> struct ModuleStateTraits { + 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> *values, + const ::flatbuffers::Vector *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(VT_ANGLE, 0); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && VerifyField(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(MotorState::VT_ANGLE, angle, 0); + } + explicit MotorStateBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset 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(VT_ID, 0); + } + ModuleType module_type() const { + return static_cast(GetField(VT_MODULE_TYPE, 0)); + } + ModuleState configuration_type() const { + return static_cast(GetField(VT_CONFIGURATION_TYPE, 0)); + } + const void *configuration() const { + return GetPointer(VT_CONFIGURATION); + } + template const T *configuration_as() const; + const MotorState *configuration_as_MotorState() const { + return configuration_type() == ModuleState_MotorState + ? static_cast(configuration()) + : nullptr; + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && VerifyField(verifier, VT_ID, 1) && + VerifyField(verifier, VT_MODULE_TYPE, 1) && + VerifyField(verifier, VT_CONFIGURATION_TYPE, 1) && + VerifyOffset(verifier, VT_CONFIGURATION) && + VerifyModuleState(verifier, configuration(), configuration_type()) && + verifier.EndTable(); + } +}; + +template <> inline const MotorState *RobotModule::configuration_as() 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(RobotModule::VT_ID, id, 0); + } + void add_module_type(ModuleType module_type) { + fbb_.AddElement(RobotModule::VT_MODULE_TYPE, static_cast(module_type), 0); + } + void add_configuration_type(ModuleState configuration_type) { + fbb_.AddElement(RobotModule::VT_CONFIGURATION_TYPE, + static_cast(configuration_type), 0); + } + void add_configuration(::flatbuffers::Offset configuration) { + fbb_.AddOffset(RobotModule::VT_CONFIGURATION, configuration); + } + explicit RobotModuleBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset +CreateRobotModule(::flatbuffers::FlatBufferBuilder &_fbb, uint8_t id = 0, + ModuleType module_type = ModuleType_SPLITTER, + ModuleState configuration_type = ModuleState_NONE, + ::flatbuffers::Offset 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(obj); + return verifier.VerifyTable(ptr); + } + default: + return true; + } +} + +inline bool +VerifyModuleStateVector(::flatbuffers::Verifier &verifier, + const ::flatbuffers::Vector<::flatbuffers::Offset> *values, + const ::flatbuffers::Vector *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(i))) { + return false; + } + } + return true; +} + +inline const RobotModule *GetRobotModule(const void *buf) { + return ::flatbuffers::GetRoot(buf); +} + +inline const RobotModule *GetSizePrefixedRobotModule(const void *buf) { + return ::flatbuffers::GetSizePrefixedRoot(buf); +} + +inline bool VerifyRobotModuleBuffer(::flatbuffers::Verifier &verifier) { + return verifier.VerifyBuffer(nullptr); +} + +inline bool VerifySizePrefixedRobotModuleBuffer(::flatbuffers::Verifier &verifier) { + return verifier.VerifySizePrefixedBuffer(nullptr); +} + +inline void FinishRobotModuleBuffer(::flatbuffers::FlatBufferBuilder &fbb, + ::flatbuffers::Offset root) { + fbb.Finish(root); +} + +inline void FinishSizePrefixedRobotModuleBuffer(::flatbuffers::FlatBufferBuilder &fbb, + ::flatbuffers::Offset root) { + fbb.FinishSizePrefixed(root); +} + +#endif // FLATBUFFERS_GENERATED_ROBOTMODULE_H_ diff --git a/include/flatbuffers_generated/SensorMessage_generated.h b/include/flatbuffers_generated/SensorMessage_generated.h new file mode 100644 index 0000000..29920a7 --- /dev/null +++ b/include/flatbuffers_generated/SensorMessage_generated.h @@ -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(e); + return EnumNamesSensorValue()[index]; +} + +template struct SensorValueTraits { + static const SensorValue enum_value = SensorValue_NONE; +}; + +template <> struct SensorValueTraits { + static const SensorValue enum_value = SensorValue_TargetAngle; +}; + +template <> struct SensorValueTraits { + 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> *values, + const ::flatbuffers::Vector *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(VT_VALUE, 0); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && VerifyField(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(TargetAngle::VT_VALUE, value, 0); + } + explicit TargetAngleBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset 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(VT_VALUE, 0); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && VerifyField(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(CurrentAngle::VT_VALUE, value, 0); + } + explicit CurrentAngleBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset +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 *values_type() const { + return GetPointer *>(VT_VALUES_TYPE); + } + const ::flatbuffers::Vector<::flatbuffers::Offset> *values() const { + return GetPointer> *>(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> values_type) { + fbb_.AddOffset(SensorMessage::VT_VALUES_TYPE, values_type); + } + void + add_values(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> values) { + fbb_.AddOffset(SensorMessage::VT_VALUES, values); + } + explicit SensorMessageBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateSensorMessage( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset<::flatbuffers::Vector> values_type = 0, + ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> values = 0) { + SensorMessageBuilder builder_(_fbb); + builder_.add_values(values); + builder_.add_values_type(values_type); + return builder_.Finish(); +} + +inline ::flatbuffers::Offset +CreateSensorMessageDirect(::flatbuffers::FlatBufferBuilder &_fbb, + const std::vector *values_type = nullptr, + const std::vector<::flatbuffers::Offset> *values = nullptr) { + auto values_type__ = values_type ? _fbb.CreateVector(*values_type) : 0; + auto values__ = values ? _fbb.CreateVector<::flatbuffers::Offset>(*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(obj); + return verifier.VerifyTable(ptr); + } + case SensorValue_CurrentAngle: { + auto ptr = reinterpret_cast(obj); + return verifier.VerifyTable(ptr); + } + default: + return true; + } +} + +inline bool +VerifySensorValueVector(::flatbuffers::Verifier &verifier, + const ::flatbuffers::Vector<::flatbuffers::Offset> *values, + const ::flatbuffers::Vector *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(i))) { + return false; + } + } + return true; +} + +inline const Messaging::SensorMessage *GetSensorMessage(const void *buf) { + return ::flatbuffers::GetRoot(buf); +} + +inline const Messaging::SensorMessage *GetSizePrefixedSensorMessage(const void *buf) { + return ::flatbuffers::GetSizePrefixedRoot(buf); +} + +inline bool VerifySensorMessageBuffer(::flatbuffers::Verifier &verifier) { + return verifier.VerifyBuffer(nullptr); +} + +inline bool VerifySizePrefixedSensorMessageBuffer(::flatbuffers::Verifier &verifier) { + return verifier.VerifySizePrefixedBuffer(nullptr); +} + +inline void FinishSensorMessageBuffer(::flatbuffers::FlatBufferBuilder &fbb, + ::flatbuffers::Offset root) { + fbb.Finish(root); +} + +inline void +FinishSizePrefixedSensorMessageBuffer(::flatbuffers::FlatBufferBuilder &fbb, + ::flatbuffers::Offset root) { + fbb.FinishSizePrefixed(root); +} + +} // namespace Messaging + +#endif // FLATBUFFERS_GENERATED_SENSORMESSAGE_MESSAGING_H_ diff --git a/include/flatbuffers_generated/TopologyMessage_generated.h b/include/flatbuffers_generated/TopologyMessage_generated.h new file mode 100644 index 0000000..47898bb --- /dev/null +++ b/include/flatbuffers_generated/TopologyMessage_generated.h @@ -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(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(VT_MODULE_ID, 0); + } + ModuleType module_type() const { + return static_cast(GetField(VT_MODULE_TYPE, 0)); + } + uint8_t num_channels() const { + return GetField(VT_NUM_CHANNELS, 0); + } + const ::flatbuffers::Vector *channel_to_module() const { + return GetPointer *>(VT_CHANNEL_TO_MODULE); + } + const ::flatbuffers::Vector *channel_to_orientation() const { + return GetPointer *>(VT_CHANNEL_TO_ORIENTATION); + } + Messaging::ConnectionType connection() const { + return static_cast(GetField(VT_CONNECTION, 0)); + } + uint8_t leader() const { + return GetField(VT_LEADER, 0); + } + const ::flatbuffers::String *firmware() const { + return GetPointer(VT_FIRMWARE); + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && VerifyField(verifier, VT_MODULE_ID, 1) && + VerifyField(verifier, VT_MODULE_TYPE, 1) && + VerifyField(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(verifier, VT_CONNECTION, 1) && + VerifyField(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(TopologyMessage::VT_MODULE_ID, module_id, 0); + } + void add_module_type(ModuleType module_type) { + fbb_.AddElement(TopologyMessage::VT_MODULE_TYPE, static_cast(module_type), + 0); + } + void add_num_channels(uint8_t num_channels) { + fbb_.AddElement(TopologyMessage::VT_NUM_CHANNELS, num_channels, 0); + } + void + add_channel_to_module(::flatbuffers::Offset<::flatbuffers::Vector> channel_to_module) { + fbb_.AddOffset(TopologyMessage::VT_CHANNEL_TO_MODULE, channel_to_module); + } + void add_channel_to_orientation( + ::flatbuffers::Offset<::flatbuffers::Vector> channel_to_orientation) { + fbb_.AddOffset(TopologyMessage::VT_CHANNEL_TO_ORIENTATION, channel_to_orientation); + } + void add_connection(Messaging::ConnectionType connection) { + fbb_.AddElement(TopologyMessage::VT_CONNECTION, static_cast(connection), 0); + } + void add_leader(uint8_t leader) { + fbb_.AddElement(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 Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateTopologyMessage( + ::flatbuffers::FlatBufferBuilder &_fbb, uint8_t module_id = 0, + ModuleType module_type = ModuleType_SPLITTER, uint8_t num_channels = 0, + ::flatbuffers::Offset<::flatbuffers::Vector> channel_to_module = 0, + ::flatbuffers::Offset<::flatbuffers::Vector> 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 +CreateTopologyMessageDirect(::flatbuffers::FlatBufferBuilder &_fbb, uint8_t module_id = 0, + ModuleType module_type = ModuleType_SPLITTER, uint8_t num_channels = 0, + const std::vector *channel_to_module = nullptr, + const std::vector *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(*channel_to_module) : 0; + auto channel_to_orientation__ = + channel_to_orientation ? _fbb.CreateVector(*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(buf); +} + +inline const Messaging::TopologyMessage *GetSizePrefixedTopologyMessage(const void *buf) { + return ::flatbuffers::GetSizePrefixedRoot(buf); +} + +inline bool VerifyTopologyMessageBuffer(::flatbuffers::Verifier &verifier) { + return verifier.VerifyBuffer(nullptr); +} + +inline bool VerifySizePrefixedTopologyMessageBuffer(::flatbuffers::Verifier &verifier) { + return verifier.VerifySizePrefixedBuffer(nullptr); +} + +inline void FinishTopologyMessageBuffer(::flatbuffers::FlatBufferBuilder &fbb, + ::flatbuffers::Offset root) { + fbb.Finish(root); +} + +inline void +FinishSizePrefixedTopologyMessageBuffer(::flatbuffers::FlatBufferBuilder &fbb, + ::flatbuffers::Offset root) { + fbb.FinishSizePrefixed(root); +} + +} // namespace Messaging + +#endif // FLATBUFFERS_GENERATED_TOPOLOGYMESSAGE_MESSAGING_H_ diff --git a/include/lib_c_control.h b/include/lib_c_control.h new file mode 100644 index 0000000..0eba421 --- /dev/null +++ b/include/lib_c_control.h @@ -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 diff --git a/include/libcontrol.h b/include/libcontrol.h new file mode 100644 index 0000000..4aff73f --- /dev/null +++ b/include/libcontrol.h @@ -0,0 +1,126 @@ +// +// Created by Johnathon Slightham on 2025-11-13. +// + +#ifndef CONTROL_LIBCONTROL_H +#define CONTROL_LIBCONTROL_H + +#include +#include +#include +#include + +#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()), + 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> 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> getModule(uint8_t device_id); + + /** + * \brief Get a list of all connections. + * + * Returns a list containing all connections between modules. + */ + std::vector getConnections(); + + /** + * \brief Get a list of accessible modules. + * + * Returns a list containing ID and types of each module. + */ + std::vector 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 m_logger; + std::unordered_map> m_id_to_module{}; + std::unordered_map> + m_connection_map{}; + std::shared_mutex m_module_lock{}; + std::shared_mutex m_connection_lock{}; + std::shared_ptr m_messaging_interface; + std::atomic 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 diff --git a/include/util/Event.h b/include/util/Event.h new file mode 100644 index 0000000..c2d84c7 --- /dev/null +++ b/include/util/Event.h @@ -0,0 +1,50 @@ + +#ifndef CONTROL_EVENT_H +#define CONTROL_EVENT_H + +#include + +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; + + 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 callbacks; + int nextId = 0; +}; + +CallbackHandle::~CallbackHandle() { + if (evt) + evt->remove(id); +} + +#endif // CONTROL_EVENT_H diff --git a/include/util/Map.h b/include/util/Map.h new file mode 100644 index 0000000..cd59f6c --- /dev/null +++ b/include/util/Map.h @@ -0,0 +1,18 @@ + +#ifndef CONTROL_MAP_H +#define CONTROL_MAP_H + +#include +#include + +template +std::vector map_to_values(const std::unordered_map &map) { + std::vector out; + out.reserve(map.size()); + for (auto const &[key, value] : map) { + out.push_back(value); + } + return out; +} + +#endif // CONTROL_MAP_H diff --git a/include/util/PairHash.h b/include/util/PairHash.h new file mode 100644 index 0000000..779db90 --- /dev/null +++ b/include/util/PairHash.h @@ -0,0 +1,14 @@ + +#ifndef CONTROL_PAIRHASH_H +#define CONTROL_PAIRHASH_H + +#include + +// Custom hash function for std::pair +template struct pair_hash { + std::size_t operator()(const std::pair &p) const { + return std::hash()(p.first) ^ (std::hash()(p.second) << 1); + } +}; + +#endif // CONTROL_PAIRHASH_H diff --git a/include/util/Variant.h b/include/util/Variant.h new file mode 100644 index 0000000..8cd6dda --- /dev/null +++ b/include/util/Variant.h @@ -0,0 +1,13 @@ + +#ifndef VARIANT_H +#define VARIANT_H + +#include // NOLINT + +template struct overloaded : Ts... { + using Ts::operator()...; +}; + +template overloaded(Ts...) -> overloaded; + +#endif // VARIANT_H diff --git a/src/Hub.cpp b/src/Hub.cpp new file mode 100644 index 0000000..ae99bcc --- /dev/null +++ b/src/Hub.cpp @@ -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 Hub::get_actuation_message() { + // no-op + return {}; +} + +void Hub::update_sensor_data(const Flatbuffers::sensor_value & /* value */) { + // no-op +} diff --git a/src/Module.cpp b/src/Module.cpp new file mode 100644 index 0000000..8955aba --- /dev/null +++ b/src/Module.cpp @@ -0,0 +1,43 @@ + +#include +#include + +#include "Module.h" + +std::vector 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 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(ori)}); + } +} diff --git a/src/ModuleFactory.cpp b/src/ModuleFactory.cpp new file mode 100644 index 0000000..633fcfd --- /dev/null +++ b/src/ModuleFactory.cpp @@ -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 +ModuleFactory::createModule(uint8_t device_id, ModuleType type, + std::shared_ptr &messaging_interface) { + switch (type) { + case ModuleType_SPLITTER: + return std::make_shared(device_id, type); + case ModuleType_BATTERY: + return std::make_shared(device_id, type); + case ModuleType_SERVO_1: + case ModuleType_SERVO_2: + return std::make_shared( + device_id, type, SERVO1_MAX_ANGLE, SERVO1_MIN_ANGLE, SERVO1_DEFAULT_ANGLE); + case ModuleType_DC_MOTOR: + return std::make_shared(device_id, type); + } + return nullptr; +} diff --git a/src/actuators/BoundedPositionalActuator1D.cpp b/src/actuators/BoundedPositionalActuator1D.cpp new file mode 100644 index 0000000..8de39ad --- /dev/null +++ b/src/actuators/BoundedPositionalActuator1D.cpp @@ -0,0 +1,43 @@ + +#include +#include + +#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 BoundedPositionalActuator1D::get_actuation_message() { + std::vector 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); +} diff --git a/src/actuators/PositionalActuator1D.cpp b/src/actuators/PositionalActuator1D.cpp new file mode 100644 index 0000000..f228c96 --- /dev/null +++ b/src/actuators/PositionalActuator1D.cpp @@ -0,0 +1,36 @@ +#include "actuators/PositionalActuator1D.h" +#include "flatbuffers/SensorMessageBuilder.h" +#include "util/Variant.h" +#include + +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 PositionalActuator1D::get_actuation_message() { + std::vector 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); +} diff --git a/src/flatbuffers/AngleControlMessageBuilder.cpp b/src/flatbuffers/AngleControlMessageBuilder.cpp new file mode 100644 index 0000000..763d8eb --- /dev/null +++ b/src/flatbuffers/AngleControlMessageBuilder.cpp @@ -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(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 diff --git a/src/flatbuffers/RobotConfigurationBuilder.cpp b/src/flatbuffers/RobotConfigurationBuilder.cpp new file mode 100644 index 0000000..8c3e935 --- /dev/null +++ b/src/flatbuffers/RobotConfigurationBuilder.cpp @@ -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 &modules, + const std::vector &connections) { + builder_.Clear(); + + std::vector> module_vector; + std::vector> 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 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(buffer); +} +} // namespace Flatbuffers diff --git a/src/flatbuffers/SensorMessageBuilder.cpp b/src/flatbuffers/SensorMessageBuilder.cpp new file mode 100644 index 0000000..3b7f47e --- /dev/null +++ b/src/flatbuffers/SensorMessageBuilder.cpp @@ -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(buffer); +} + +} // namespace Flatbuffers diff --git a/src/flatbuffers/TopologyMessageBuilder.cpp b/src/flatbuffers/TopologyMessageBuilder.cpp new file mode 100644 index 0000000..4c6c60e --- /dev/null +++ b/src/flatbuffers/TopologyMessageBuilder.cpp @@ -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 &channel_to_module, + const std::vector &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(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 diff --git a/src/lib_c_control.cpp b/src/lib_c_control.cpp new file mode 100644 index 0000000..b2adf74 --- /dev/null +++ b/src/lib_c_control.cpp @@ -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(); + +const auto acm_builder = std::make_unique(); + +const auto robot_configuration_builder = std::make_unique(); + +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 modules_vec{}; + std::vector 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(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); + } + } +} +} diff --git a/src/libcontrol.cpp b/src/libcontrol.cpp new file mode 100644 index 0000000..83f6076 --- /dev/null +++ b/src/libcontrol.cpp @@ -0,0 +1,249 @@ +// +// Created by Johnathon Slightham on 2025-11-13. +// + +#include +#include +#include +#include +#include +#include + +#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> RobotController::getModules() { + std::vector> 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 RobotController::getConnections() { + std::vector 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 RobotController::getModuleList() { + std::vector 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> 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(); + 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(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 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(metadata->channel_to_orientation()->Get(i))}); + } + } + + m_connection_map[metadata->module_id()] = conns; + } + } +} + +void RobotController::transmit_loop() { + const auto builder = std::make_unique(); + 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 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(); + 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(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(sensor_message->values_type()->Get(i)), + sensor_message->values()->Get(i))) { + m_id_to_module[from]->update_sensor_data(*maybe_value); + } + } + } + } +}