Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hugo/feature/Update MotionKit to Fusion #1292

Closed
wants to merge 9 commits into from
6 changes: 2 additions & 4 deletions app/os/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#include "FlashNumberCounting.h"
#include "FoodRecognition.h"
#include "HelloWorld.h"
#include "IMUKit.hpp"
#include "LedColorRecognition.h"
#include "LedKit.h"
#include "LedNumberCounting.h"
Expand Down Expand Up @@ -270,13 +271,11 @@ auto imukit = IMUKit {imu::lsm6dsox};

namespace motion::internal {

EventLoopKit event_loop {};
CoreTimeout timeout {};

} // namespace motion::internal

auto motionkit = MotionKit {motors::left::motor, motors::right::motor, imukit, motion::internal::event_loop,
motion::internal::timeout};
auto motionkit = MotionKit {motors::left::motor, motors::right::motor, imukit, motion::internal::timeout};

auto behaviorkit = BehaviorKit {videokit, ledkit, motors::left::motor, motors::right::motor};
auto reinforcerkit = ReinforcerKit {videokit, ledkit, motionkit};
Expand Down Expand Up @@ -562,7 +561,6 @@ auto main() -> int

imu::lsm6dsox.init();
imukit.init();
motionkit.init();

robot::controller.initializeComponents();
robot::controller.registerOnUpdateLoadedCallback(firmware::setPendingUpdate);
Expand Down
36 changes: 36 additions & 0 deletions include/interface/libs/IMUKit.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Leka - LekaOS
// Copyright 2023 APF France handicap
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <functional>

namespace leka {

struct EulerAngles {
float pitch;
float roll;
float yaw;
};

namespace interface {

class IMUKit
{
public:
using angles_ready_callback_t = std::function<void(const EulerAngles &)>;

virtual ~IMUKit() = default;

virtual void start() = 0;
virtual void stop() = 0;

virtual void setOrigin() = 0;
virtual void onEulerAnglesReady(angles_ready_callback_t const &callback) = 0;
[[nodiscard]] virtual auto getEulerAngles() const -> EulerAngles = 0;
};

} // namespace interface

} // namespace leka
19 changes: 8 additions & 11 deletions libs/IMUKit/include/IMUKit.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,29 @@
#pragma once

#include "interface/LSM6DSOX.hpp"
#include "interface/libs/IMUKit.hpp"

namespace leka {

struct EulerAngles {
float pitch;
float roll;
float yaw;
};

class IMUKit
class IMUKit : public interface::IMUKit
{
public:
explicit IMUKit(interface::LSM6DSOX &lsm6dsox) : _lsm6dsox(lsm6dsox) {}

void init();
void start();
void stop();
void start() final;
void stop() final;

void setOrigin();
[[nodiscard]] auto getEulerAngles() const -> EulerAngles;
void setOrigin() final;
void onEulerAnglesReady(angles_ready_callback_t const &callback) final;
[[nodiscard]] auto getEulerAngles() const -> EulerAngles final;

private:
void drdy_callback(interface::LSM6DSOX::SensorData data);

interface::LSM6DSOX &_lsm6dsox;
EulerAngles _euler_angles {};
angles_ready_callback_t _on_euler_angles_rdy_callback {};
};

} // namespace leka
9 changes: 9 additions & 0 deletions libs/IMUKit/source/IMUKit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ auto IMUKit::getEulerAngles() const -> EulerAngles
return _euler_angles;
}

void IMUKit::onEulerAnglesReady(angles_ready_callback_t const &callback)
{
_on_euler_angles_rdy_callback = callback;
}

void IMUKit::drdy_callback(const interface::LSM6DSOX::SensorData data)
{
// ? Note: For a detailed explanation on the code below, checkout
Expand Down Expand Up @@ -113,4 +118,8 @@ void IMUKit::drdy_callback(const interface::LSM6DSOX::SensorData data)
.roll = euler.angle.roll,
.yaw = euler.angle.yaw,
};

if (_on_euler_angles_rdy_callback) {
_on_euler_angles_rdy_callback(_euler_angles);
}
};
35 changes: 35 additions & 0 deletions libs/IMUKit/tests/IMUKit_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,41 @@ TEST_F(IMUKitTest, setOrigin)

TEST_F(IMUKitTest, onDataReady)
{
testing::MockFunction<void(EulerAngles angles)> mock_callback {};

imukit.onEulerAnglesReady(mock_callback.AsStdFunction());

const auto data_initial = interface::LSM6DSOX::SensorData {
.xl = {.x = 0.F, .y = 0.F, .z = 0.F}, .gy = {.x = 0.F, .y = 0.F, .z = 0.F }
};

EXPECT_CALL(mock_callback, Call);

mock_lsm6dox.call_drdy_callback(data_initial);

const auto angles_initial = imukit.getEulerAngles();

spy_kernel_addElapsedTimeToTickCount(80ms);

const auto data_updated = interface::LSM6DSOX::SensorData {
.xl = {.x = 1.F, .y = 2.F, .z = 3.F}, .gy = {.x = 1.F, .y = 2.F, .z = 3.F }
};

EXPECT_CALL(mock_callback, Call);

mock_lsm6dox.call_drdy_callback(data_updated);

auto angles_updated = imukit.getEulerAngles();

EXPECT_NE(angles_initial.pitch, angles_updated.pitch);
EXPECT_NE(angles_initial.roll, angles_updated.roll);
EXPECT_NE(angles_initial.yaw, angles_updated.yaw);
}

TEST_F(IMUKitTest, onDataReadyEmptyEulerAngleCallback)
{
imukit.onEulerAnglesReady({});

const auto data_initial = interface::LSM6DSOX::SensorData {
.xl = {.x = 0.F, .y = 0.F, .z = 0.F}, .gy = {.x = 0.F, .y = 0.F, .z = 0.F }
};
Expand Down
6 changes: 4 additions & 2 deletions libs/MotionKit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ target_include_directories(MotionKit
target_sources(MotionKit
PRIVATE
source/MotionKit.cpp
source/PID.cpp
source/RotationControl.cpp
source/StabilizationControl.cpp
)

target_link_libraries(MotionKit
Expand All @@ -23,6 +24,7 @@ target_link_libraries(MotionKit
if(${CMAKE_PROJECT_NAME} STREQUAL "LekaOSUnitTests")
leka_unit_tests_sources(
tests/MotionKit_test.cpp
tests/PID_test.cpp
tests/RotationControl_test.cpp
tests/StabilizationControl_test.cpp
)
endif()
47 changes: 15 additions & 32 deletions libs/MotionKit/include/MotionKit.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,63 +7,46 @@
#include <interface/drivers/Motor.h>
#include <interface/libs/EventLoop.h>

#include "IMUKit.hpp"
#include "PID.hpp"
#include "RotationControl.hpp"
#include "StabilizationControl.hpp"
#include "interface/drivers/Timeout.h"
#include "interface/libs/IMUKit.hpp"

namespace leka {

class MotionKit
{
public:
MotionKit(interface::Motor &motor_left, interface::Motor &motor_right, IMUKit &imu_kit,
interface::EventLoop &event_loop, interface::Timeout &timeout)
: _motor_left(motor_left),
_motor_right(motor_right),
_imukit(imu_kit),
_event_loop(event_loop),
_timeout(timeout)
MotionKit(interface::Motor &motor_left, interface::Motor &motor_right, interface::IMUKit &imu_kit,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in a futur PR/refactor, I think MotionKit will not depend on IMUKit but instead, each control class that needs it will depend on what is needed.

just like CommandKit

same for the motors, etc.

MotionKit will just need to start and stop the control without knowing what the control actually does.

interface::Timeout &timeout)
: _motor_left(motor_left), _motor_right(motor_right), _imukit(imu_kit), _timeout(timeout)
{
}

void init();

void rotate(uint8_t number_of_rotations, Rotation direction,
void rotate(float number_of_rotations, Rotation direction,
const std::function<void()> &on_rotation_ended_callback = {});
void startStabilisation();
void startStabilization();

void stop();

private:
void run();
void processAngleForRotation(const EulerAngles &angles, Rotation direction);
void processAngleForStabilization(const EulerAngles &angles);
Comment on lines +33 to +34
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in a future PR, this should replaced by start(control_name)


[[nodiscard]] auto mapSpeed(float speed) const -> float;
void executeSpeed(float speed, Rotation direction);
void setMotorsSpeedAndDirection(float speed, Rotation direction);

interface::Motor &_motor_left;
interface::Motor &_motor_right;
IMUKit &_imukit;
interface::EventLoop &_event_loop;
interface::IMUKit &_imukit;
interface::Timeout &_timeout;
PID _pid;

uint8_t _rotations_to_execute = 0;
RotationControl _rotation_control;
StabilizationControl _stabilization_control;
Comment on lines +43 to +44
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for now, we'll say this is okay.

for 1.5.0, it must be refactored to instanciate control classes outside and register/start them when needed.

on the same model as CommandKit

This will allow us to add more controls without the need to recompile MotionKit


std::function<void()> _on_rotation_ended_callback {};

bool _target_not_reached = false;
bool _stabilisation_requested = false;
bool _rotate_x_turns_requested = false;

const float kReferenceAngle = 180.F;
const float kPIDMaxValue = 1.8F;

// ? When the motor is stopped, PWM values under kMinimalViableRobotPwm are too low to generate enough torque for
// ? the motor to start spinning ? At the same time, kMinimalViableRobotPwm needs to be the lowest possible to avoid
// ? overshooting when the target is reached

const float kMinimalViableRobotPwm = 0.15F;
const float kPwmMaxValue = 1.F;
const float kEpsilon = 0.005F;
};

} // namespace leka
44 changes: 0 additions & 44 deletions libs/MotionKit/include/PID.hpp

This file was deleted.

57 changes: 57 additions & 0 deletions libs/MotionKit/include/RotationControl.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Leka - LekaOS
// Copyright 2023 APF France handicap
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include "interface/libs/IMUKit.hpp"
namespace leka {

class RotationControl
{
public:
RotationControl() = default;

void init(EulerAngles starting_angles, float number_of_rotations);
auto processRotationAngle(EulerAngles current_angles) -> float;

private:
void calculateTotalYawRotation(EulerAngles angle);
[[nodiscard]] auto mapSpeed(float speed) const -> float;

// ? Kp, Ki, Kd were found empirically by increasing Kp until the rotation angle exceeds the target angle
// ? Then increase Kd to fix this excess angle
// ? Repeat this protocol until there is no Kd high enough to compensate Kp
// ? Then take the last set of Kp, Kd value with no excess angle
// ? Finally choose a low Ki that smooth out the movement
struct Parameters {
static constexpr auto Kp = float {0.3F};
static constexpr auto Ki = float {0.0001F};
static constexpr auto Kd = float {0.4F};
};

static constexpr float kStaticBound = 5.F;
static constexpr float kDeltaT = 20.F;

// ? When the motor is stopped, PWM values under kMinimalViableRobotPwm are too low to generate enough torque for
// ? the motor to start spinning ? At the same time, kMinimalViableRobotPwm needs to be the lowest possible to avoid
// ? overshooting when the target is reached
static constexpr float kMinimalViableRobotPwm = 0.15F;
static constexpr float kPwmMaxValue = 1.F;
static constexpr float kEpsilon = 0.005F;

static constexpr float kInputSpeedLimit = 90 * (Parameters::Kp + Parameters::Kd) / kDeltaT;

float _angle_rotation_target = 0.F;
float _angle_rotation_sum = 0.F;

EulerAngles _euler_angles_previous {};

float _error_position_total = 0.F;
float _error_position_last = 0.F;
float _proportional = 0.F;
float _integral = 0.F;
float _derivative = 0.F;
};

} // namespace leka
Loading