From efb92445a4ef30f66a167f113266fd5576f9ccd1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Niccol=C3=B2=20Betto?= <niccolo.betto@skywarder.eu>
Date: Tue, 11 Feb 2025 00:47:55 +0100
Subject: [PATCH] [RIGv2] Update Actuators to the 11 Orion servo valves

Boardcore has also been updated to include the updated BSP enabling up to 12 servos.
---
 skyward-boardcore                   |   2 +-
 src/RIGv2/Actuators/Actuators.cpp   | 304 ++++++++++++----------------
 src/RIGv2/Actuators/Actuators.h     |  73 ++++---
 src/RIGv2/Configs/ActuatorsConfig.h |  82 +++++---
 src/RIGv2/Registry/Registry.cpp     |  72 +++----
 src/RIGv2/Registry/Registry.h       |  36 ++--
 src/common/Events.h                 |  51 +++--
 7 files changed, 309 insertions(+), 311 deletions(-)

diff --git a/skyward-boardcore b/skyward-boardcore
index ef4e55b65..ead06db6a 160000
--- a/skyward-boardcore
+++ b/skyward-boardcore
@@ -1 +1 @@
-Subproject commit ef4e55b6575914cc021c4eb36173db9339d3a7c8
+Subproject commit ead06db6a21548d24e3004a0027ae19a501bbaf1
diff --git a/src/RIGv2/Actuators/Actuators.cpp b/src/RIGv2/Actuators/Actuators.cpp
index c05c94d56..0b6de233d 100644
--- a/src/RIGv2/Actuators/Actuators.cpp
+++ b/src/RIGv2/Actuators/Actuators.cpp
@@ -1,5 +1,5 @@
-/* Copyright (c) 2024 Skyward Experimental Rocketry
- * Authors: Davide Mor
+/* Copyright (c) 2025 Skyward Experimental Rocketry
+ * Authors: Davide Mor, Niccolò Betto
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -27,6 +27,7 @@
 #include <common/Events.h>
 #include <drivers/timer/TimestampTimer.h>
 #include <events/EventBroker.h>
+#include <interfaces-impl/hwmapping.h>
 
 using namespace Boardcore;
 using namespace miosix;
@@ -40,8 +41,8 @@ void Actuators::ServoInfo::openServoWithTime(uint32_t time)
     closeTs      = currentTime + (time * Constants::NS_IN_MS);
     lastActionTs = currentTime;
 
-    if (openingEvent != 0)
-        EventBroker::getInstance().post(openingEvent, TOPIC_MOTOR);
+    if (config.openingEvent != 0)
+        EventBroker::getInstance().post(config.openingEvent, TOPIC_MOTOR);
 }
 
 void Actuators::ServoInfo::closeServo()
@@ -49,8 +50,8 @@ void Actuators::ServoInfo::closeServo()
     closeTs      = 0;
     lastActionTs = getTime();
 
-    if (closingEvent != 0)
-        EventBroker::getInstance().post(closingEvent, TOPIC_MOTOR);
+    if (config.closingEvent != 0)
+        EventBroker::getInstance().post(config.closingEvent, TOPIC_MOTOR);
 }
 
 void Actuators::ServoInfo::unsafeSetServoPosition(float position)
@@ -59,8 +60,8 @@ void Actuators::ServoInfo::unsafeSetServoPosition(float position)
     if (!servo)
         return;
 
-    position *= limit;
-    if (flipped)
+    position *= config.limit;
+    if (config.flipped)
         position = 1.0f - position;
 
     servo->setPosition(position);
@@ -73,30 +74,30 @@ float Actuators::ServoInfo::getServoPosition()
         return 0.0f;
 
     float position = servo->getPosition();
-    if (flipped)
+    if (config.flipped)
         position = 1.0f - position;
 
-    position /= limit;
+    position /= config.limit;
     return position;
 }
 
 float Actuators::ServoInfo::getMaxAperture()
 {
-    return getModule<Registry>()->getOrSetDefaultUnsafe(maxApertureKey,
-                                                        defaultMaxAperture);
+    return getModule<Registry>()->getOrSetDefaultUnsafe(
+        config.maxApertureRegKey, config.defaultMaxAperture);
 }
 
 uint32_t Actuators::ServoInfo::getOpeningTime()
 {
-    return getModule<Registry>()->getOrSetDefaultUnsafe(openingTimeKey,
-                                                        defaultOpeningTime);
+    return getModule<Registry>()->getOrSetDefaultUnsafe(
+        config.openingTimeRegKey, config.defaultOpeningTime);
 }
 
 bool Actuators::ServoInfo::setMaxAperture(float aperture)
 {
     if (aperture >= 0.0 && aperture <= 1.0)
     {
-        getModule<Registry>()->setUnsafe(maxApertureKey, aperture);
+        getModule<Registry>()->setUnsafe(config.maxApertureRegKey, aperture);
         return true;
     }
     else
@@ -108,145 +109,96 @@ bool Actuators::ServoInfo::setMaxAperture(float aperture)
 
 bool Actuators::ServoInfo::setOpeningTime(uint32_t time)
 {
-    getModule<Registry>()->setUnsafe(openingTimeKey, time);
+    getModule<Registry>()->setUnsafe(config.openingTimeRegKey, time);
     return true;
 }
 
+/**
+ * @brief Shorthand to create a ServoInfo struct from the servo name
+ */
+#define MAKE_SERVO(name)                                          \
+    ServoInfo                                                     \
+    {                                                             \
+        std::make_unique<Servo>(                                  \
+            MIOSIX_SERVOS_##name##_TIM,                           \
+            TimerUtils::Channel::MIOSIX_SERVOS_##name##_CHANNEL,  \
+            Config::Servos::MIN_PULSE, Config::Servos::MAX_PULSE, \
+            Config::Servos::FREQUENCY),                           \
+            ServoInfo::ServoConfig                                \
+        {                                                         \
+            .limit   = Config::Servos::name##_LIMIT,              \
+            .flipped = Config::Servos::name##_FLIPPED,            \
+            .defaultOpeningTime =                                 \
+                Config::Servos::DEFAULT_##name##_OPENING_TIME,    \
+            .defaultMaxAperture =                                 \
+                Config::Servos::DEFAULT_##name##_MAX_APERTURE,    \
+            .openingEvent      = MOTOR_##name##_OPEN,             \
+            .closingEvent      = MOTOR_##name##_CLOSE,            \
+            .openingTimeRegKey = CONFIG_ID_##name##_OPENING_TIME, \
+            .maxApertureRegKey = CONFIG_ID_##name##_MAX_APERTURE  \
+        }                                                         \
+    }
+
+/**
+ * @brief Shorthand to create a detach ServoInfo struct from the servo name
+ */
+#define MAKE_DETACH_SERVO(name)                                           \
+    ServoInfo                                                             \
+    {                                                                     \
+        std::make_unique<Servo>(                                          \
+            MIOSIX_SERVOS_##name##_TIM,                                   \
+            TimerUtils::Channel::MIOSIX_SERVOS_##name##_CHANNEL,          \
+            Config::Servos::DETACH_MIN_PULSE,                             \
+            Config::Servos::DETACH_MAX_PULSE, Config::Servos::FREQUENCY), \
+            ServoInfo::ServoConfig                                        \
+        {                                                                 \
+            .limit   = Config::Servos::name##_LIMIT,                      \
+            .flipped = Config::Servos::name##_FLIPPED,                    \
+            .defaultOpeningTime =                                         \
+                Config::Servos::DEFAULT_##name##_OPENING_TIME,            \
+            .defaultMaxAperture =                                         \
+                Config::Servos::DEFAULT_##name##_MAX_APERTURE,            \
+            .openingEvent      = MOTOR_##name##_OPEN,                     \
+            .closingEvent      = MOTOR_##name##_CLOSE,                    \
+            .openingTimeRegKey = CONFIG_ID_##name##_OPENING_TIME,         \
+            .maxApertureRegKey = CONFIG_ID_##name##_MAX_APERTURE          \
+        }                                                                 \
+    }
+
+/**
+ * @brief Shorthand to create a non-atomic ServoInfo struct from the servo name
+ */
+#define MAKE_SIMPLE_SERVO(name)                                   \
+    ServoInfo                                                     \
+    {                                                             \
+        std::make_unique<Servo>(                                  \
+            MIOSIX_SERVOS_##name##_TIM,                           \
+            TimerUtils::Channel::MIOSIX_SERVOS_##name##_CHANNEL,  \
+            Config::Servos::MIN_PULSE, Config::Servos::MAX_PULSE, \
+            Config::Servos::FREQUENCY),                           \
+            ServoInfo::ServoConfig                                \
+        {                                                         \
+            .limit        = Config::Servos::name##_LIMIT,         \
+            .flipped      = Config::Servos::name##_FLIPPED,       \
+            .openingEvent = MOTOR_##name##_OPEN,                  \
+            .closingEvent = MOTOR_##name##_CLOSE,                 \
+        }                                                         \
+    }
+
 Actuators::Actuators()
+    : infos{
+          MAKE_SERVO(OX_FIL),        MAKE_SERVO(OX_REL),
+          MAKE_DETACH_SERVO(OX_DET),
+          MAKE_SERVO(N2_FIL),        MAKE_SERVO(N2_REL),
+          MAKE_DETACH_SERVO(N2_DET), MAKE_SERVO(NITR),
+          MAKE_SERVO(OX_VEN),        MAKE_SERVO(N2_QUE),
+          MAKE_SERVO(MAIN),
+      }, n2_3wayValveInfo(MAKE_SIMPLE_SERVO(N2_3W))
 {
-    // Initialize servos
-    infos[0].servo = std::make_unique<Servo>(
-        MIOSIX_SERVOS_1_TIM, TimerUtils::Channel::MIOSIX_SERVOS_1_CHANNEL,
-        Config::Servos::MIN_PULSE, Config::Servos::MAX_PULSE,
-        Config::Servos::FREQUENCY);
-    infos[1].servo = std::make_unique<Servo>(
-        MIOSIX_SERVOS_2_TIM, TimerUtils::Channel::MIOSIX_SERVOS_2_CHANNEL,
-        Config::Servos::MIN_PULSE, Config::Servos::MAX_PULSE,
-        Config::Servos::FREQUENCY);
-    infos[2].servo = std::make_unique<Servo>(
-        MIOSIX_SERVOS_3_TIM, TimerUtils::Channel::MIOSIX_SERVOS_3_CHANNEL,
-        Config::Servos::MIN_PULSE, Config::Servos::MAX_PULSE,
-        Config::Servos::FREQUENCY);
-    infos[3].servo = std::make_unique<Servo>(
-        MIOSIX_SERVOS_4_TIM, TimerUtils::Channel::MIOSIX_SERVOS_4_CHANNEL,
-        Config::Servos::MIN_PULSE, Config::Servos::MAX_PULSE,
-        Config::Servos::FREQUENCY);
-    // This servo is currently unusable, due to it sharing the same timer as
-    // miosix, TIM5
-    // infos[4].servo = std::make_unique<Servo>(
-    //     MIOSIX_SERVOS_5_TIM, TimerUtils::Channel::MIOSIX_SERVOS_5_CHANNEL,
-    //     Config::Servos::MIN_PULSE, Config::Servos::MAX_PULSE,
-    //     Config::Servos::FREQUENCY);
-    infos[5].servo = std::make_unique<Servo>(
-        MIOSIX_SERVOS_7_TIM, TimerUtils::Channel::MIOSIX_SERVOS_7_CHANNEL,
-        Config::Servos::MIN_PULSE, Config::Servos::MAX_PULSE,
-        Config::Servos::FREQUENCY);
-    infos[6].servo = std::make_unique<Servo>(
-        MIOSIX_SERVOS_9_TIM, TimerUtils::Channel::MIOSIX_SERVOS_9_CHANNEL,
-        Config::Servos::SERVO2_MIN_PULSE, Config::Servos::SERVO2_MAX_PULSE,
-        Config::Servos::FREQUENCY);
-    // This servo is currently unusable, due to it sharing the same timer as
-    // servo 1
-    // infos[7].servo = std::make_unique<Servo>(
-    //     MIOSIX_SERVOS_8_TIM, TimerUtils::Channel::MIOSIX_SERVOS_8_CHANNEL,
-    //     Config::Servos::MIN_PULSE, Config::Servos::MAX_PULSE,
-    //     Config::Servos::FREQUENCY);
-    infos[8].servo = std::make_unique<Servo>(
-        MIOSIX_SERVOS_6_TIM, TimerUtils::Channel::MIOSIX_SERVOS_6_CHANNEL,
-        Config::Servos::MIN_PULSE, Config::Servos::MAX_PULSE,
-        Config::Servos::FREQUENCY);
-    infos[9].servo = std::make_unique<Servo>(
-        MIOSIX_SERVOS_8_TIM, TimerUtils::Channel::MIOSIX_SERVOS_8_CHANNEL,
-        Config::Servos::MIN_PULSE, Config::Servos::MAX_PULSE,
-        Config::Servos::FREQUENCY);
-
-    ServoInfo* info;
-    info                     = getServo(ServosList::N2O_FILLING_VALVE);
-    info->defaultMaxAperture = Config::Servos::DEFAULT_FILLING_MAX_APERTURE;
-    info->defaultOpeningTime = Config::Servos::DEFAULT_FILLING_OPENING_TIME;
-    info->limit              = Config::Servos::FILLING_LIMIT;
-    info->flipped            = Config::Servos::FILLING_FLIPPED;
-    info->openingEvent       = Common::Events::MOTOR_OPEN_FILLING_VALVE;
-    info->closingEvent       = Common::Events::MOTOR_CLOSE_FILLING_VALVE;
-    info->openingTimeKey     = CONFIG_ID_N2O_FILLING_OPENING_TIME;
-    info->maxApertureKey     = CONFIG_ID_N2O_FILLING_MAX_APERTURE;
-    info->unsafeSetServoPosition(0.0f);
-
-    info                     = getServo(ServosList::N2O_RELEASE_VALVE);
-    info->defaultMaxAperture = Config::Servos::DEFAULT_RELEASE_MAX_APERTURE;
-    info->defaultOpeningTime = Config::Servos::DEFAULT_RELEASE_OPENING_TIME;
-    info->limit              = Config::Servos::RELEASE_LIMIT;
-    info->flipped            = Config::Servos::RELEASE_FLIPPED;
-    info->openingEvent       = Common::Events::MOTOR_OPEN_RELEASE_VALVE;
-    info->closingEvent       = Common::Events::MOTOR_CLOSE_RELEASE_VALVE;
-    info->openingTimeKey     = CONFIG_ID_N2O_RELEASE_OPENING_TIME;
-    info->maxApertureKey     = CONFIG_ID_N2O_RELEASE_MAX_APERTURE;
-    info->unsafeSetServoPosition(0.0f);
-
-    info                     = getServo(ServosList::N2O_VENTING_VALVE);
-    info->defaultMaxAperture = Config::Servos::DEFAULT_VENTING_MAX_APERTURE;
-    info->defaultOpeningTime = Config::Servos::DEFAULT_VENTING_OPENING_TIME;
-    info->limit              = Config::Servos::VENTING_LIMIT;
-    info->flipped            = Config::Servos::VENTING_FLIPPED;
-    info->openingEvent       = Common::Events::MOTOR_OPEN_VENTING_VALVE;
-    info->closingEvent       = Common::Events::MOTOR_CLOSE_VENTING_VALVE;
-    info->openingTimeKey     = CONFIG_ID_N2O_VENTING_OPENING_TIME;
-    info->maxApertureKey     = CONFIG_ID_N2O_VENTING_MAX_APERTURE;
-    info->unsafeSetServoPosition(0.0f);
-
-    info                     = getServo(ServosList::N2_FILLING_VALVE);
-    info->defaultMaxAperture = Config::Servos::DEFAULT_FILLING_MAX_APERTURE;
-    info->defaultOpeningTime = Config::Servos::DEFAULT_FILLING_OPENING_TIME;
-    info->limit              = Config::Servos::FILLING_LIMIT;
-    info->flipped            = Config::Servos::FILLING_FLIPPED;
-    info->openingEvent       = Common::Events::MOTOR_OPEN_FILLING_VALVE;
-    info->closingEvent       = Common::Events::MOTOR_CLOSE_FILLING_VALVE;
-    info->openingTimeKey     = CONFIG_ID_N2_FILLING_OPENING_TIME;
-    info->maxApertureKey     = CONFIG_ID_N2_FILLING_MAX_APERTURE;
-    info->unsafeSetServoPosition(0.0f);
-
-    info                     = getServo(ServosList::N2_RELEASE_VALVE);
-    info->defaultMaxAperture = Config::Servos::DEFAULT_RELEASE_MAX_APERTURE;
-    info->defaultOpeningTime = Config::Servos::DEFAULT_RELEASE_OPENING_TIME;
-    info->limit              = Config::Servos::RELEASE_LIMIT;
-    info->flipped            = Config::Servos::RELEASE_FLIPPED;
-    info->openingEvent       = Common::Events::MOTOR_OPEN_RELEASE_VALVE;
-    info->closingEvent       = Common::Events::MOTOR_CLOSE_RELEASE_VALVE;
-    info->openingTimeKey     = CONFIG_ID_N2_RELEASE_OPENING_TIME;
-    info->maxApertureKey     = CONFIG_ID_N2_RELEASE_MAX_APERTURE;
-    info->unsafeSetServoPosition(0.0f);
-
-    info                     = getServo(ServosList::N2_DETACH_SERVO);
-    info->defaultMaxAperture = Config::Servos::DEFAULT_DISCONNECT_MAX_APERTURE;
-    info->defaultOpeningTime = Config::Servos::DEFAULT_DISCONNECT_OPENING_TIME;
-    info->limit              = Config::Servos::DISCONNECT_LIMIT;
-    info->flipped            = Config::Servos::DISCONNECT_FLIPPED;
-    info->openingEvent       = Common::Events::MOTOR_DISCONNECT;
-    info->openingTimeKey     = CONFIG_ID_N2_DETACH_OPENING_TIME;
-    info->maxApertureKey     = CONFIG_ID_N2_DETACH_MAX_APERTURE;
-    info->unsafeSetServoPosition(0.0f);
-
-    info                     = getServo(ServosList::MAIN_VALVE);
-    info->defaultMaxAperture = Config::Servos::DEFAULT_MAIN_MAX_APERTURE;
-    info->defaultOpeningTime = Config::Servos::DEFAULT_MAIN_OPENING_TIME;
-    info->limit              = Config::Servos::MAIN_LIMIT;
-    info->flipped            = Config::Servos::MAIN_FLIPPED;
-    info->openingEvent       = Common::Events::MOTOR_OPEN_FEED_VALVE;
-    info->closingEvent       = Common::Events::MOTOR_CLOSE_FEED_VALVE;
-    info->openingTimeKey     = CONFIG_ID_MAIN_OPENING_TIME;
-    info->maxApertureKey     = CONFIG_ID_MAIN_MAX_APERTURE;
-    info->unsafeSetServoPosition(0.0f);
-
-    info                     = getServo(ServosList::NITROGEN_VALVE);
-    info->defaultMaxAperture = Config::Servos::DEFAULT_MAIN_MAX_APERTURE;
-    info->defaultOpeningTime = Config::Servos::DEFAULT_MAIN_OPENING_TIME;
-    info->limit              = Config::Servos::MAIN_LIMIT;
-    info->flipped            = Config::Servos::MAIN_FLIPPED;
-    info->openingEvent       = 0;
-    info->closingEvent       = 0;
-    info->openingTimeKey     = CONFIG_ID_NITROGEN_OPENING_TIME;
-    info->maxApertureKey     = CONFIG_ID_NITROGEN_MAX_APERTURE;
-    info->unsafeSetServoPosition(0.0f);
+    for (auto& servo : infos)
+        servo.unsafeSetServoPosition(0.0f);
+
+    n2_3wayValveInfo.unsafeSetServoPosition(0.0f);
 }
 
 bool Actuators::isStarted() { return started; }
@@ -256,16 +208,9 @@ bool Actuators::start()
     TaskScheduler& scheduler =
         getModule<BoardScheduler>()->getActuatorsScheduler();
 
-    infos[0].servo->enable();
-    infos[1].servo->enable();
-    infos[2].servo->enable();
-    infos[3].servo->enable();
-    // infos[4].servo->enable();
-    infos[5].servo->enable();
-    infos[6].servo->enable();
-    // infos[7].servo->enable();
-    infos[8].servo->enable();
-    infos[9].servo->enable();
+    // Enable all servos
+    for (ServoInfo& info : infos)
+        info.servo->enable();
 
     uint8_t result =
         scheduler.addTask([this]() { updatePositionsTask(); },
@@ -408,25 +353,31 @@ bool Actuators::isCanServoOpen(ServosList servo)
         return false;
 }
 
+void Actuators::set3wayValveState(bool state)
+{
+    auto position = state ? 1.0f : 0.0f;
+    n2_3wayValveInfo.unsafeSetServoPosition(position);
+}
+
 void Actuators::openChamberWithTime(uint32_t time)
 {
     Lock<FastMutex> lock(infosMutex);
     long long currentTime = getTime();
 
-    nitrogenCloseTs      = currentTime + (time * Constants::NS_IN_MS);
-    nitrogenLastActionTs = currentTime;
+    chamberCloseTs      = currentTime + (time * Constants::NS_IN_MS);
+    chamberLastActionTs = currentTime;
 }
 
 void Actuators::closeChamber()
 {
     Lock<FastMutex> lock(infosMutex);
-    nitrogenCloseTs = 0;
+    chamberCloseTs = 0;
 }
 
 bool Actuators::isChamberOpen()
 {
     Lock<FastMutex> lock(infosMutex);
-    return nitrogenCloseTs != 0;
+    return chamberCloseTs != 0;
 }
 
 uint32_t Actuators::getServoOpeningTime(ServosList servo)
@@ -467,25 +418,28 @@ void Actuators::inject(DependencyInjector& injector)
 
 Actuators::ServoInfo* Actuators::getServo(ServosList servo)
 {
-    // info[4] and info[7] are currently unavailable
     switch (servo)
     {
-        case N2O_FILLING_VALVE:
+        case N2O_FILLING_VALVE:  // OX_FIL
             return &infos[0];
-        case N2O_RELEASE_VALVE:
+        case N2O_RELEASE_VALVE:  // OX_REL
             return &infos[1];
-        case N2O_VENTING_VALVE:
+        case N2O_DETACH_SERVO:  // OX_DET
             return &infos[2];
-        case N2_FILLING_VALVE:
-            return &infos[3];
-        case N2_RELEASE_VALVE:
+        case N2_FILLING_VALVE:  // N2_FIL
+            return &infos[4];
+        case N2_RELEASE_VALVE:  // N2_REL
             return &infos[5];
-        case N2_DETACH_SERVO:
+        case N2_DETACH_SERVO:  // N2_DET
             return &infos[6];
-        case MAIN_VALVE:
+        case NITROGEN_VALVE:  // NITR
+            return &infos[7];
+        case N2O_VENTING_VALVE:  // OX_VEN
             return &infos[8];
-        case NITROGEN_VALVE:
+        case N2_QUENCHING_VALVE:  // N2_QUE
             return &infos[9];
+        case MAIN_VALVE:  // MAIN
+            return &infos[10];
 
         default:
             // Oh FUCK
@@ -564,13 +518,13 @@ void Actuators::updatePositionsTask()
     }
 
     // Handle nitrogen logic
-    if (currentTime < nitrogenCloseTs)
+    if (currentTime < chamberCloseTs)
     {
         unsafeOpenChamber();
     }
     else
     {
-        nitrogenCloseTs = 0;
+        chamberCloseTs = 0;
 
         unsafeCloseChamber();
     }
diff --git a/src/RIGv2/Actuators/Actuators.h b/src/RIGv2/Actuators/Actuators.h
index 08b66c634..75c413fcc 100644
--- a/src/RIGv2/Actuators/Actuators.h
+++ b/src/RIGv2/Actuators/Actuators.h
@@ -1,5 +1,5 @@
-/* Copyright (c) 2024 Skyward Experimental Rocketry
- * Authors: Davide Mor
+/* Copyright (c) 2025 Skyward Experimental Rocketry
+ * Authors: Davide Mor, Niccolò Betto
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -41,29 +41,34 @@ class Actuators
 private:
     struct ServoInfo : public Boardcore::InjectableWithDeps<Registry>
     {
+        struct ServoConfig
+        {
+            float limit  = 1.0;    ///< Movement range limit
+            bool flipped = false;  ///< Whether the servo is flipped
+            uint32_t defaultOpeningTime = 1000;  // Default opening time [ms]
+            float defaultMaxAperture    = 1.0;   // Max aperture
+
+            uint8_t openingEvent = 0;  ///< Event to fire after opening
+            uint8_t closingEvent = 0;  ///< Event to fire after closing
+            uint32_t openingTimeRegKey =
+                CONFIG_ID_DEFAULT_OPENING_TIME;  ///< Registry key for opening
+                                                 ///< time
+            uint32_t maxApertureRegKey =
+                CONFIG_ID_DEFAULT_MAX_APERTURE;  ///< Registry key for max
+                                                 ///< aperture
+        };
+
+        ServoInfo(std::unique_ptr<Boardcore::Servo> servo,
+                  const ServoConfig& config)
+            : servo(std::move(servo)), config(config)
+        {
+        }
+
         std::unique_ptr<Boardcore::Servo> servo;
-        // Hard limit of the aperture
-        float limit = 1.0;
-        // Should this servo be reversed?
-        bool flipped = false;
-        // How much time to stay open
-        uint32_t defaultOpeningTime = 100000;  // Default 100s [ms]
-        // What angle is the maximum
-        float defaultMaxAperture = 1.0;
-
-        // What event to fire while opening?
-        uint8_t openingEvent = 0;
-        // What event to fire while closing?
-        uint8_t closingEvent = 0;
-        // How much time to stay open
-        uint32_t openingTimeKey = CONFIG_ID_DEFAULT_OPENING_TIME;
-        // What angle is the maximum
-        uint32_t maxApertureKey = CONFIG_ID_DEFAULT_MAX_APERTURE;
-
-        // Timestamp of when the servo should close, 0 if closed
-        long long closeTs = 0;
-        // Timestamp of last servo action (open/close)
-        long long lastActionTs = 0;
+        ServoConfig config;
+
+        long long closeTs = 0;  ///< Timestamp to close the servo (0 if closed)
+        long long lastActionTs = 0;  ///< Timestamp of last servo action
 
         void openServoWithTime(uint32_t time);
         void closeServo();
@@ -93,6 +98,9 @@ public:
     bool isServoOpen(ServosList servo);
     bool isCanServoOpen(ServosList servo);
 
+    // N2 3-way valve control
+    void set3wayValveState(bool state);
+
     // Chamber valve control
     void openChamberWithTime(uint32_t time);
     void closeChamber();
@@ -120,20 +128,21 @@ private:
 
     void updatePositionsTask();
 
-    Boardcore::Logger& sdLogger   = Boardcore::Logger::getInstance();
-    Boardcore::PrintLogger logger = Boardcore::Logging::getLogger("actuators");
-
     std::atomic<bool> started{false};
 
     miosix::FastMutex infosMutex;
-    ServoInfo infos[10] = {};
-    // Timestamp of when the servo should close, 0 if closed
-    long long nitrogenCloseTs = 0;
-    // Timestamp of last servo action (open/close)
-    long long nitrogenLastActionTs = 0;
+    ServoInfo infos[10];
+    ServoInfo n2_3wayValveInfo;
+
+    long long chamberCloseTs =
+        0;  ///< Timestamp to close the chamber (0 if closed)
+    long long chamberLastActionTs = 0;  ///< Timestamp of last chamber action
 
     bool canMainOpen    = false;
     bool canVentingOpen = false;
+
+    Boardcore::Logger& sdLogger   = Boardcore::Logger::getInstance();
+    Boardcore::PrintLogger logger = Boardcore::Logging::getLogger("actuators");
 };
 
 }  // namespace RIGv2
diff --git a/src/RIGv2/Configs/ActuatorsConfig.h b/src/RIGv2/Configs/ActuatorsConfig.h
index 74b5a06ac..6a8f819fd 100644
--- a/src/RIGv2/Configs/ActuatorsConfig.h
+++ b/src/RIGv2/Configs/ActuatorsConfig.h
@@ -1,5 +1,5 @@
-/* Copyright (c) 2024 Skyward Experimental Rocketry
- * Authors: Davide Mor
+/* Copyright (c) 2025 Skyward Experimental Rocketry
+ * Authors: Davide Mor, Niccolò Betto
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -22,27 +22,26 @@
 
 #pragma once
 
-#include <interfaces-impl/hwmapping.h>
 #include <units/Frequency.h>
 
+#include <chrono>
+
 namespace RIGv2
 {
 namespace Config
 {
-
 namespace Servos
 {
-
 /* linter off */ using namespace Boardcore::Units::Frequency;
+/* linter off */ using namespace std::chrono;
 
-// Generic pulse width for all servos
+// Pulse width for all servos
 constexpr unsigned int MIN_PULSE = 500;
 constexpr unsigned int MAX_PULSE = 2440;
 
-// Pulse width specific to SERVO 2 (disconnect servo)
-// TODO(davide.mor): This actually needs tweaking
-constexpr unsigned int SERVO2_MIN_PULSE = 900;
-constexpr unsigned int SERVO2_MAX_PULSE = 2100;
+// Pulse width for detach servos
+constexpr unsigned int DETACH_MIN_PULSE = 900;
+constexpr unsigned int DETACH_MAX_PULSE = 2100;
 
 constexpr unsigned int FREQUENCY = 333;
 
@@ -50,31 +49,52 @@ constexpr Hertz SERVO_TIMINGS_CHECK_PERIOD = 10_hz;
 constexpr long long SERVO_CONFIDENCE_TIME  = 500;   // 0.5s
 constexpr float SERVO_CONFIDENCE           = 0.02;  // 2%
 
-constexpr uint32_t DEFAULT_FILLING_OPENING_TIME    = 15000;  // 15s
-constexpr uint32_t DEFAULT_VENTING_OPENING_TIME    = 15000;  // 15s
-constexpr uint32_t DEFAULT_MAIN_OPENING_TIME       = 6000;   // 6s
-constexpr uint32_t DEFAULT_RELEASE_OPENING_TIME    = 10000;  // 10s
-constexpr uint32_t DEFAULT_DISCONNECT_OPENING_TIME = 2000;   // 2s
+constexpr uint32_t DEFAULT_OX_FIL_OPENING_TIME = 15000;
+constexpr uint32_t DEFAULT_OX_REL_OPENING_TIME = 10000;
+constexpr uint32_t DEFAULT_OX_DET_OPENING_TIME = 2000;
+constexpr uint32_t DEFAULT_N2_FIL_OPENING_TIME = 15000;
+constexpr uint32_t DEFAULT_N2_REL_OPENING_TIME = 10000;
+constexpr uint32_t DEFAULT_N2_DET_OPENING_TIME = 2000;
+constexpr uint32_t DEFAULT_NITR_OPENING_TIME   = 600000;
+constexpr uint32_t DEFAULT_OX_VEN_OPENING_TIME = 15000;
+constexpr uint32_t DEFAULT_N2_QUE_OPENING_TIME = 15000;
+constexpr uint32_t DEFAULT_MAIN_OPENING_TIME   = 6000;
 
-constexpr float DEFAULT_FILLING_MAX_APERTURE    = 1.00f;
-constexpr float DEFAULT_VENTING_MAX_APERTURE    = 1.00f;
-constexpr float DEFAULT_MAIN_MAX_APERTURE       = 1.00f;
-constexpr float DEFAULT_RELEASE_MAX_APERTURE    = 1.00f;
-constexpr float DEFAULT_DISCONNECT_MAX_APERTURE = 1.00f;
+constexpr float DEFAULT_OX_FIL_MAX_APERTURE = 1.0;
+constexpr float DEFAULT_OX_REL_MAX_APERTURE = 1.0;
+constexpr float DEFAULT_OX_DET_MAX_APERTURE = 1.0;
+constexpr float DEFAULT_N2_FIL_MAX_APERTURE = 1.0;
+constexpr float DEFAULT_N2_REL_MAX_APERTURE = 1.0;
+constexpr float DEFAULT_N2_DET_MAX_APERTURE = 1.0;
+constexpr float DEFAULT_NITR_MAX_APERTURE   = 1.0;
+constexpr float DEFAULT_OX_VEN_MAX_APERTURE = 1.0;
+constexpr float DEFAULT_N2_QUE_MAX_APERTURE = 1.0;
+constexpr float DEFAULT_MAIN_MAX_APERTURE   = 1.0;
 
-constexpr float FILLING_LIMIT    = 0.90f;
-constexpr float VENTING_LIMIT    = 0.90f;
-constexpr float MAIN_LIMIT       = 0.90f;
-constexpr float RELEASE_LIMIT    = 0.50f;
-constexpr float DISCONNECT_LIMIT = 1.00f;
+constexpr float OX_FIL_LIMIT = 0.9;
+constexpr float OX_REL_LIMIT = 0.5;
+constexpr float OX_DET_LIMIT = 1.0;
+constexpr float N2_3W_LIMIT  = 1.0;
+constexpr float N2_FIL_LIMIT = 0.9;
+constexpr float N2_REL_LIMIT = 0.5;
+constexpr float N2_DET_LIMIT = 1.0;
+constexpr float NITR_LIMIT   = 0.9;
+constexpr float OX_VEN_LIMIT = 0.9;
+constexpr float N2_QUE_LIMIT = 0.9;
+constexpr float MAIN_LIMIT   = 0.9;
 
-constexpr bool FILLING_FLIPPED    = true;
-constexpr bool VENTING_FLIPPED    = true;
-constexpr bool MAIN_FLIPPED       = true;
-constexpr bool RELEASE_FLIPPED    = true;
-constexpr bool DISCONNECT_FLIPPED = false;
+constexpr bool OX_FIL_FLIPPED = true;
+constexpr bool OX_REL_FLIPPED = true;
+constexpr bool OX_DET_FLIPPED = false;
+constexpr bool N2_3W_FLIPPED  = true;
+constexpr bool N2_FIL_FLIPPED = true;
+constexpr bool N2_REL_FLIPPED = true;
+constexpr bool N2_DET_FLIPPED = false;
+constexpr bool NITR_FLIPPED   = true;
+constexpr bool OX_VEN_FLIPPED = true;
+constexpr bool N2_QUE_FLIPPED = true;
+constexpr bool MAIN_FLIPPED   = true;
 
 }  // namespace Servos
-
 }  // namespace Config
 }  // namespace RIGv2
diff --git a/src/RIGv2/Registry/Registry.cpp b/src/RIGv2/Registry/Registry.cpp
index 2fa748947..bd02273bd 100644
--- a/src/RIGv2/Registry/Registry.cpp
+++ b/src/RIGv2/Registry/Registry.cpp
@@ -33,46 +33,46 @@ const char* RIGv2::configurationIdToName(ConfigurationId id)
 {
     switch (id)
     {
-        case CONFIG_ID_N2O_FILLING_OPENING_TIME:
-            return "N2O_FILLING_OPENING_TIME";
-        case CONFIG_ID_N2O_FILLING_MAX_APERTURE:
-            return "N2O_FILLING_MAX_APERTURE";
-        case CONFIG_ID_N2O_RELEASE_OPENING_TIME:
-            return "N2O_RELEASE_OPENING_TIME";
-        case CONFIG_ID_N2O_RELEASE_MAX_APERTURE:
-            return "N2O_RELEASE_MAX_APERTURE";
-        case CONFIG_ID_N2O_DETACH_OPENING_TIME:
-            return "N2O_DETACH_OPENING_TIME";
-        case CONFIG_ID_N2O_DETACH_MAX_APERTURE:
-            return "N2O_DETACH_MAX_APERTURE";
-        case CONFIG_ID_N2O_VENTING_OPENING_TIME:
-            return "N2O_VENTING_OPENING_TIME";
-        case CONFIG_ID_N2O_VENTING_MAX_APERTURE:
-            return "N2O_VENTING_MAX_APERTURE";
-        case CONFIG_ID_N2_FILLING_OPENING_TIME:
-            return "N2_FILLING_OPENING_TIME";
-        case CONFIG_ID_N2_FILLING_MAX_APERTURE:
-            return "N2_FILLING_MAX_APERTURE";
-        case CONFIG_ID_N2_RELEASE_OPENING_TIME:
-            return "N2_RELEASE_OPENING_TIME";
-        case CONFIG_ID_N2_RELEASE_MAX_APERTURE:
-            return "N2_RELEASE_MAX_APERTURE";
-        case CONFIG_ID_N2_DETACH_OPENING_TIME:
-            return "N2_DETACH_OPENING_TIME";
-        case CONFIG_ID_N2_DETACH_MAX_APERTURE:
-            return "N2_DETACH_MAX_APERTURE";
-        case CONFIG_ID_N2_QUENCHING_OPENING_TIME:
-            return "N2_QUENCHING_OPENING_TIME";
-        case CONFIG_ID_N2_QUENCHING_MAX_APERTURE:
-            return "N2_QUENCHING_MAX_APERTURE";
+        case CONFIG_ID_OX_FIL_OPENING_TIME:
+            return "OX_FIL_OPENING_TIME";
+        case CONFIG_ID_OX_FIL_MAX_APERTURE:
+            return "OX_FIL_MAX_APERTURE";
+        case CONFIG_ID_OX_REL_OPENING_TIME:
+            return "OX_REL_OPENING_TIME";
+        case CONFIG_ID_OX_REL_MAX_APERTURE:
+            return "OX_REL_MAX_APERTURE";
+        case CONFIG_ID_OX_DET_OPENING_TIME:
+            return "OX_DET_OPENING_TIME";
+        case CONFIG_ID_OX_DET_MAX_APERTURE:
+            return "OX_DET_MAX_APERTURE";
+        case CONFIG_ID_OX_VEN_OPENING_TIME:
+            return "OX_VEN_OPENING_TIME";
+        case CONFIG_ID_OX_VEN_MAX_APERTURE:
+            return "OX_VEN_MAX_APERTURE";
+        case CONFIG_ID_N2_FIL_OPENING_TIME:
+            return "N2_FIL_OPENING_TIME";
+        case CONFIG_ID_N2_FIL_MAX_APERTURE:
+            return "N2_FIL_MAX_APERTURE";
+        case CONFIG_ID_N2_REL_OPENING_TIME:
+            return "N2_REL_OPENING_TIME";
+        case CONFIG_ID_N2_REL_MAX_APERTURE:
+            return "N2_REL_MAX_APERTURE";
+        case CONFIG_ID_N2_DET_OPENING_TIME:
+            return "N2_DET_OPENING_TIME";
+        case CONFIG_ID_N2_DET_MAX_APERTURE:
+            return "N2_DET_MAX_APERTURE";
+        case CONFIG_ID_N2_QUE_OPENING_TIME:
+            return "N2_QUE_OPENING_TIME";
+        case CONFIG_ID_N2_QUE_MAX_APERTURE:
+            return "N2_QUE_MAX_APERTURE";
         case CONFIG_ID_MAIN_OPENING_TIME:
             return "MAIN_OPENING_TIME";
         case CONFIG_ID_MAIN_MAX_APERTURE:
             return "MAIN_MAX_APERTURE";
-        case CONFIG_ID_NITROGEN_OPENING_TIME:
-            return "NITROGEN_OPENING_TIME";
-        case CONFIG_ID_NITROGEN_MAX_APERTURE:
-            return "NITROGEN_MAX_APERTURE";
+        case CONFIG_ID_NITR_OPENING_TIME:
+            return "NITR_OPENING_TIME";
+        case CONFIG_ID_NITR_MAX_APERTURE:
+            return "NITR_MAX_APERTURE";
         case CONFIG_ID_IGNITION_TIME:
             return "IGNITION_TIME";
         case CONFIG_ID_DEFAULT_OPENING_TIME:
diff --git a/src/RIGv2/Registry/Registry.h b/src/RIGv2/Registry/Registry.h
index b1f8e4cd2..6f26a3934 100644
--- a/src/RIGv2/Registry/Registry.h
+++ b/src/RIGv2/Registry/Registry.h
@@ -31,37 +31,37 @@ namespace RIGv2
 enum ConfigurationKeys
 {
     // N2O
-    CONFIG_ID_N2O_FILLING_OPENING_TIME,
-    CONFIG_ID_N2O_FILLING_MAX_APERTURE,
+    CONFIG_ID_OX_FIL_OPENING_TIME,
+    CONFIG_ID_OX_FIL_MAX_APERTURE,
 
-    CONFIG_ID_N2O_RELEASE_OPENING_TIME,
-    CONFIG_ID_N2O_RELEASE_MAX_APERTURE,
+    CONFIG_ID_OX_REL_OPENING_TIME,
+    CONFIG_ID_OX_REL_MAX_APERTURE,
 
-    CONFIG_ID_N2O_DETACH_OPENING_TIME,
-    CONFIG_ID_N2O_DETACH_MAX_APERTURE,
+    CONFIG_ID_OX_DET_OPENING_TIME,
+    CONFIG_ID_OX_DET_MAX_APERTURE,
 
-    CONFIG_ID_N2O_VENTING_OPENING_TIME,
-    CONFIG_ID_N2O_VENTING_MAX_APERTURE,
+    CONFIG_ID_OX_VEN_OPENING_TIME,
+    CONFIG_ID_OX_VEN_MAX_APERTURE,
 
     // N2
-    CONFIG_ID_N2_FILLING_OPENING_TIME,
-    CONFIG_ID_N2_FILLING_MAX_APERTURE,
+    CONFIG_ID_N2_FIL_OPENING_TIME,
+    CONFIG_ID_N2_FIL_MAX_APERTURE,
 
-    CONFIG_ID_N2_RELEASE_OPENING_TIME,
-    CONFIG_ID_N2_RELEASE_MAX_APERTURE,
+    CONFIG_ID_N2_REL_OPENING_TIME,
+    CONFIG_ID_N2_REL_MAX_APERTURE,
 
-    CONFIG_ID_N2_DETACH_OPENING_TIME,
-    CONFIG_ID_N2_DETACH_MAX_APERTURE,
+    CONFIG_ID_N2_DET_OPENING_TIME,
+    CONFIG_ID_N2_DET_MAX_APERTURE,
 
-    CONFIG_ID_N2_QUENCHING_OPENING_TIME,
-    CONFIG_ID_N2_QUENCHING_MAX_APERTURE,
+    CONFIG_ID_N2_QUE_OPENING_TIME,
+    CONFIG_ID_N2_QUE_MAX_APERTURE,
 
     // Main & Nitrogen
     CONFIG_ID_MAIN_OPENING_TIME,
     CONFIG_ID_MAIN_MAX_APERTURE,
 
-    CONFIG_ID_NITROGEN_OPENING_TIME,
-    CONFIG_ID_NITROGEN_MAX_APERTURE,
+    CONFIG_ID_NITR_OPENING_TIME,
+    CONFIG_ID_NITR_MAX_APERTURE,
 
     // Ignition parameters
     CONFIG_ID_IGNITION_TIME,
diff --git a/src/common/Events.h b/src/common/Events.h
index efff19c8d..6a93c35fd 100644
--- a/src/common/Events.h
+++ b/src/common/Events.h
@@ -144,16 +144,29 @@ enum Events : uint8_t
     TMTC_ARP_EXIT_TEST_MODE,
     MOTOR_START_TARS,
     MOTOR_STOP_TARS,
-    MOTOR_OPEN_VENTING_VALVE,
-    MOTOR_CLOSE_VENTING_VALVE,
-    MOTOR_OPEN_FILLING_VALVE,
-    MOTOR_CLOSE_FILLING_VALVE,
-    MOTOR_OPEN_RELEASE_VALVE,
-    MOTOR_CLOSE_RELEASE_VALVE,
-    MOTOR_DISCONNECT,
+    MOTOR_OX_FIL_OPEN,
+    MOTOR_OX_FIL_CLOSE,
+    MOTOR_OX_REL_OPEN,
+    MOTOR_OX_REL_CLOSE,
+    MOTOR_OX_DET_OPEN,
+    MOTOR_OX_DET_CLOSE,
+    MOTOR_N2_3W_OPEN,
+    MOTOR_N2_3W_CLOSE,
+    MOTOR_N2_FIL_OPEN,
+    MOTOR_N2_FIL_CLOSE,
+    MOTOR_N2_REL_OPEN,
+    MOTOR_N2_REL_CLOSE,
+    MOTOR_N2_DET_OPEN,
+    MOTOR_N2_DET_CLOSE,
+    MOTOR_NITR_OPEN,
+    MOTOR_NITR_CLOSE,
+    MOTOR_OX_VEN_OPEN,
+    MOTOR_OX_VEN_CLOSE,
+    MOTOR_N2_QUE_OPEN,
+    MOTOR_N2_QUE_CLOSE,
+    MOTOR_MAIN_OPEN,
+    MOTOR_MAIN_CLOSE,
     MOTOR_IGNITION,
-    MOTOR_OPEN_FEED_VALVE,
-    MOTOR_CLOSE_FEED_VALVE,
     MOTOR_MANUAL_ACTION,
     MOTOR_OPEN_OXIDANT,
     MOTOR_COOLING_TIMEOUT,
@@ -280,16 +293,18 @@ inline std::string getEventString(uint8_t event)
         {TMTC_ARP_EXIT_TEST_MODE, "TMTC_ARP_EXIT_TEST_MODE"},
         {MOTOR_START_TARS, "MOTOR_START_TARS"},
         {MOTOR_STOP_TARS, "MOTOR_STOP_TARS"},
-        {MOTOR_OPEN_VENTING_VALVE, "MOTOR_OPEN_VENTING_VALVE"},
-        {MOTOR_CLOSE_VENTING_VALVE, "MOTOR_CLOSE_VENTING_VALVE"},
-        {MOTOR_OPEN_FILLING_VALVE, "MOTOR_OPEN_FILLING_VALVE"},
-        {MOTOR_CLOSE_FILLING_VALVE, "MOTOR_CLOSE_FILLING_VALVE"},
-        {MOTOR_OPEN_RELEASE_VALVE, "MOTOR_OPEN_RELEASE_VALVE"},
-        {MOTOR_CLOSE_RELEASE_VALVE, "MOTOR_CLOSE_RELEASE_VALVE"},
-        {MOTOR_DISCONNECT, "MOTOR_DISCONNECT"},
+        {MOTOR_OX_FIL_OPEN, "MOTOR_OX_FIL_OPEN"},
+        {MOTOR_OX_REL_OPEN, "MOTOR_OX_REL_OPEN"},
+        {MOTOR_OX_DET_OPEN, "MOTOR_OX_DET_OPEN"},
+        {MOTOR_N2_3W_OPEN, "MOTOR_N2_3W_OPEN"},
+        {MOTOR_N2_FIL_OPEN, "MOTOR_N2_FIL_OPEN"},
+        {MOTOR_N2_REL_OPEN, "MOTOR_N2_REL_OPEN"},
+        {MOTOR_N2_DET_OPEN, "MOTOR_N2_DET_OPEN"},
+        {MOTOR_NITR_OPEN, "MOTOR_NITR_OPEN"},
+        {MOTOR_OX_VEN_OPEN, "MOTOR_OX_VEN_OPEN"},
+        {MOTOR_N2_QUE_OPEN, "MOTOR_N2_QUE_OPEN"},
+        {MOTOR_MAIN_OPEN, "MOTOR_MAIN_OPEN"},
         {MOTOR_IGNITION, "MOTOR_IGNITION"},
-        {MOTOR_OPEN_FEED_VALVE, "MOTOR_OPEN_FEED_VALVE"},
-        {MOTOR_CLOSE_FEED_VALVE, "MOTOR_CLOSE_FEED_VALVE"},
         {MOTOR_MANUAL_ACTION, "MOTOR_MANUAL_ACTION"},
         {MOTOR_OPEN_OXIDANT, "MOTOR_OPEN_OXIDANT"},
         {MOTOR_COOLING_TIMEOUT, "MOTOR_COOLING_TIMEOUT"},
-- 
GitLab