From 18ad21bdc88cd45806a3f005d8c1849df972c855 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Fri, 11 Aug 2023 22:27:49 +0400
Subject: [PATCH 01/82] refactor(Haptics): new effects interface

[skip ci]
---
 .clang-format                             |   2 +
 lib/arduino/components/serial_plotter.cpp |   2 +-
 lib/core/types.hpp                        |  17 +--
 lib/core/utility.hpp                      |   2 +-
 lib/haptics/haptic_body.cpp               |  41 ++++---
 lib/haptics/haptic_body.hpp               |  27 ++---
 lib/haptics/haptic_constants.h            |   5 -
 lib/haptics/haptic_plane.cpp              | 127 +++++++++++-----------
 lib/haptics/haptic_plane.hpp              | 127 ++++++++++++++--------
 lib/haptics/haptics_interface.hpp         |  65 +++++++++++
 lib/math/point2.hpp                       |  56 +++++-----
 platformio.ini                            |   2 +-
 test/test_haptic_body/main.cpp            |  69 ------------
 test/test_haptics_body/main.cpp           | 117 ++++++++++++++++++++
 test/test_haptics_plane/main.cpp          | 120 +++++++++++---------
 test/test_math_point2/main.cpp            |  10 ++
 16 files changed, 477 insertions(+), 312 deletions(-)
 delete mode 100644 lib/haptics/haptic_constants.h
 create mode 100644 lib/haptics/haptics_interface.hpp
 delete mode 100644 test/test_haptic_body/main.cpp
 create mode 100644 test/test_haptics_body/main.cpp

diff --git a/.clang-format b/.clang-format
index 01908c3b..8972cb76 100644
--- a/.clang-format
+++ b/.clang-format
@@ -7,6 +7,8 @@ AllowAllConstructorInitializersOnNextLine: 'true'
 AllowAllParametersOfDeclarationOnNextLine: 'true'
 AllowShortBlocksOnASingleLine: 'false'
 AllowShortCaseLabelsOnASingleLine: 'false'
+# TODO
+# AllowShortFunctionsOnASingleLine: Inline
 AllowShortFunctionsOnASingleLine: Empty
 AllowShortIfStatementsOnASingleLine: Never
 AllowShortLambdasOnASingleLine: Empty
diff --git a/lib/arduino/components/serial_plotter.cpp b/lib/arduino/components/serial_plotter.cpp
index 4920552e..c3320721 100644
--- a/lib/arduino/components/serial_plotter.cpp
+++ b/lib/arduino/components/serial_plotter.cpp
@@ -8,7 +8,7 @@ void OH::SerialPlotter_OutputStates<_Tp>::run()
             oh_output_path_t path = _c.first;
             OH::HapticPlane* component = _c.second;
 
-            for (auto& _s : *component->getOutputStates()) {
+            for (auto& _s : *component->getActuatorStates()) {
                 oh_output_point_t point = _s.first;
                 oh_output_state_t state = _s.second;
 
diff --git a/lib/core/types.hpp b/lib/core/types.hpp
index 60866742..3c51c75f 100644
--- a/lib/core/types.hpp
+++ b/lib/core/types.hpp
@@ -9,20 +9,5 @@
 #define OH_OUTPUT_COORD_MAX UINT8_MAX
 
 typedef uint8_t oh_output_path_t;
-typedef OH_OUTPUT_COORD_T oh_output_coord_t;
+typedef OH_OUTPUT_COORD_T  oh_output_coord_t;
 typedef OH::Point2b oh_output_point_t;
-
-namespace OH {
-    struct OutputData {
-        oh_output_point_t point;
-        oh_output_intensity_t intensity;
-    };
-
-    struct OutputState {
-        oh_output_intensity_t intensity;
-    };
-} // namespace OH
-
-typedef OH::OutputData oh_output_data_t;
-typedef OH::OutputState oh_output_state_t;
-typedef std::map<oh_output_point_t, OH::AbstractActuator*> oh_output_writers_map_t;
diff --git a/lib/core/utility.hpp b/lib/core/utility.hpp
index 7a60c5b1..3d40497e 100644
--- a/lib/core/utility.hpp
+++ b/lib/core/utility.hpp
@@ -30,7 +30,7 @@ namespace OH {
     };
 
     template<typename _Tp>
-    inline _Tp accurateMap(_Tp x, _Tp in_min, _Tp in_max, _Tp out_min, _Tp out_max)
+    constexpr _Tp accurateMap(_Tp x, _Tp in_min, _Tp in_max, _Tp out_min, _Tp out_max)
     {
         const _Tp run = in_max - in_min;
         if (run == 0) {
diff --git a/lib/haptics/haptic_body.cpp b/lib/haptics/haptic_body.cpp
index 6abee88d..3718ed41 100644
--- a/lib/haptics/haptic_body.cpp
+++ b/lib/haptics/haptic_body.cpp
@@ -2,24 +2,31 @@
 
 #include <logging.hpp>
 
-void OH::HapticBody::addComponent(const oh_output_path_t path, OH::HapticPlane* c)
-{
-    this->components[path] = c;
-}
+namespace SenseShift::Body::Haptics {
+    void HapticBody::setup()
+    {
+        for (auto& [target, plane] : this->vibroTargets) {
+            plane->setup();
+        }
+    }
 
-std::map<oh_output_path_t, OH::HapticPlane*>* OH::HapticBody::getComponents()
-{
-    return &this->components;
-}
+    void HapticBody::effect(const EffectRequest_t& effect)
+    {
+        if (effect.effect == Effect::Vibro && std::holds_alternative<VibroEffect_t>(effect.data)) {
+            auto it = this->vibroTargets.find(effect.target);
+            if (it == this->vibroTargets.end()) {
+                log_w("No target found for effect: %d", effect.target);
+                return;
+            }
 
-void OH::HapticBody::writeOutput(const oh_output_path_t path, const oh_output_data_t& data)
-{
-    if (this->getComponents()->count(path) == 0) {
-        // if no requested component exists, skip
-        log_w("No component found for path %d", path);
-        return;
+            it->second->effect(effect.position, std::get<VibroEffect_t>(effect.data));
+        } else {
+            log_w("Non-supported effect type: %d", effect.effect);
+        }
     }
 
-    auto componentSearch = this->getComponents()->find(path);
-    (*componentSearch).second->writeOutput(data);
-}
+    void HapticBody::addTarget(const Target_t target, VibroPlane* plane)
+    {
+        this->vibroTargets[target] = plane;
+    }
+} // namespace SenseShift::Body::Haptics
diff --git a/lib/haptics/haptic_body.hpp b/lib/haptics/haptic_body.hpp
index b57b853b..33252535 100644
--- a/lib/haptics/haptic_body.hpp
+++ b/lib/haptics/haptic_body.hpp
@@ -1,32 +1,27 @@
 #pragma once
 
-#include "haptic_constants.h"
 #include "haptic_plane.hpp"
+#include "haptics_interface.hpp"
 
 #include <types.hpp>
 #include <utility.hpp>
 
 #include <map>
 
-namespace OH {
+namespace SenseShift::Body::Haptics {
     class HapticBody {
-      private:
-        typedef std::map<oh_output_path_t, HapticPlane*> oh_output_components_map_t;
-        std::map<oh_output_path_t, HapticPlane*> components{};
-
       public:
+        typedef std::map<Target_t, VibroPlane*> VibroTargetMap_t;
+
         HapticBody(){};
 
-        void addComponent(const oh_output_path_t, HapticPlane*);
-        oh_output_components_map_t* getComponents();
+        void setup();
 
-        void writeOutput(const oh_output_path_t, const oh_output_data_t&);
+        void effect(const EffectRequest_t&);
 
-        void setup()
-        {
-            for (auto& component : this->components) {
-                component.second->setup();
-            }
-        };
+        void addTarget(const Target_t, VibroPlane* plane);
+
+      private:
+        VibroTargetMap_t vibroTargets{};
     };
-} // namespace OH
+} // namespace SenseShift::Body::Haptics
diff --git a/lib/haptics/haptic_constants.h b/lib/haptics/haptic_constants.h
deleted file mode 100644
index 8184df26..00000000
--- a/lib/haptics/haptic_constants.h
+++ /dev/null
@@ -1,5 +0,0 @@
-#pragma once
-
-#define OUTPUT_PATH_CHEST_FRONT 0x00
-#define OUTPUT_PATH_CHEST_BACK 0x01
-#define OUTPUT_PATH_ACCESSORY 0x02
diff --git a/lib/haptics/haptic_plane.cpp b/lib/haptics/haptic_plane.cpp
index 95929d94..ddbf0e7b 100644
--- a/lib/haptics/haptic_plane.cpp
+++ b/lib/haptics/haptic_plane.cpp
@@ -3,79 +3,78 @@
 #include <algorithm>
 #include <logging.hpp>
 #include <math.h>
-#include <utility.hpp>
 
-void OH::HapticPlane::setOutputs(oh_output_writers_map_t& outputs)
-{
-    this->writers.clear();
-    this->writers = outputs;
-
-    this->points.clear();
-    for (auto& _p : outputs) {
-        this->points.push_back(_p.first);
-    }
+#include "haptic_plane.hpp"
 
-    this->states.clear();
-    for (auto& _p : outputs) {
-        this->states[_p.first] = {};
+namespace SenseShift::Body::Haptics {
+    template<typename _Tp>
+    void ActuativePlane<_Tp>::setActuators(const ActuatorMap_t& actuators)
+    {
+        this->actuators.clear();
+        for (const auto& [point, actuator] : actuators) {
+            this->actuators[point] = actuator;
+        }
+
+        this->points.clear();
+        for (const auto& [point, _] : actuators) {
+            this->points.insert(point);
+        }
+
+        this->states.clear();
+        for (const auto& [point, _] : actuators) {
+            this->states[point] = 0;
+        }
     }
-}
 
-void OH::HapticPlane::setup()
-{
-    for (const auto& kv : this->writers) {
-        kv.second->setup();
+    template<typename _Tp>
+    void ActuativePlane<_Tp>::setup()
+    {
+        for (const auto& [point, actuator] : this->actuators) {
+            actuator->setup();
+        }
     }
-}
 
-void OH::HapticPlane::writeOutput(const oh_output_data_t& data)
-{
-    if (this->writers.count(data.point) == 0) {
-        log_w("No writer for point (%u, %u)", data.point.x, data.point.y);
-        return;
+    template<typename _Tp>
+    void ActuativePlane<_Tp>::effect(const Position_t& pos, const Value_t& val)
+    {
+        auto it = this->actuators.find(pos);
+        if (it == this->actuators.end()) {
+            log_w("No actuator for point (%u, %u)", pos.x, pos.y);
+            return;
+        }
+
+        it->second->writeOutput(val);
+        this->states[pos] = val;
     }
 
-    auto state = &this->states[data.point];
-    state->intensity = data.intensity;
-
-    this->writers.at(data.point)->writeOutput(state->intensity);
-}
-
-oh_output_point_t
-  OH::HapticPlane_Closest::findClosestPoints(std::list<oh_output_point_t>& pts, const oh_output_point_t& target)
-{
-    if (contains(pts, target)) {
-        return target;
+    template<typename _Tp>
+    void ActuativePlane_Closest<_Tp>::effect(const Position_t& pos, const Value_t& val)
+    {
+        auto& closest = this->findClosestPoint(*this->getAvailablePoints(), pos);
+        ActuativePlane<_Tp>::effect(closest, val);
     }
 
-    std::multimap<float, oh_output_point_t> mp = {};
-
-    for (auto& _p : pts) {
-        float dx = abs(((float) target.x / OH_OUTPUT_COORD_MAX) - ((float) _p.x / OH_OUTPUT_COORD_MAX)),
-              dy = abs(((float) target.y / OH_OUTPUT_COORD_MAX) - ((float) _p.y / OH_OUTPUT_COORD_MAX));
-
-        auto dist = (float) sqrt(pow(dx, 2) + pow(dy, 2));
-
-        mp.insert({ dist, _p });
+    template<typename _Tp>
+    const Position_t&
+      ActuativePlane_Closest<_Tp>::findClosestPoint(const PositionSet_t& pts, const Position_t& target) const
+    {
+        // check if exact point exists
+        auto it = pts.find(target);
+        if (it != pts.end()) {
+            return *it;
+        }
+
+        // find closest point by square distance
+        std::multimap<float, Position_t> mp = {};
+        for (const auto& _p : pts) {
+            mp.insert({ (target - _p), _p });
+        }
+
+        auto nearest = std::min_element(mp.begin(), mp.end());
+
+        return nearest->second;
     }
 
-    auto nearest = std::min_element(
-      mp.begin(),
-      mp.end(),
-      [](const std::pair<float, oh_output_point_t>& a, const std::pair<float, oh_output_point_t>& b) {
-          return a.first < b.first;
-      }
-    );
-
-    return nearest->second;
-}
-
-void OH::HapticPlane_Closest::writeOutput(const oh_output_data_t& data)
-{
-    auto closestPoint = this->findClosestPoints(this->points, data.point);
-
-    auto state = &this->states[closestPoint];
-    state->intensity = data.intensity;
-
-    this->writers.at(closestPoint)->writeOutput(state->intensity);
-}
+    template class ActuativePlane<VibroEffect_t>;
+    template class ActuativePlane_Closest<VibroEffect_t>;
+} // namespace SenseShift::Body::Haptics
diff --git a/lib/haptics/haptic_plane.hpp b/lib/haptics/haptic_plane.hpp
index 49084195..8f565ef6 100644
--- a/lib/haptics/haptic_plane.hpp
+++ b/lib/haptics/haptic_plane.hpp
@@ -1,71 +1,96 @@
 #pragma once
 
+#include "haptics_interface.hpp"
+
 #include <abstract_actuator.hpp>
 #include <types.hpp>
 #include <utility.hpp>
 
 #include <list>
 #include <map>
+#include <set>
 #include <vector>
 
-namespace OH {
+namespace SenseShift::Body::Haptics {
+    typedef std::set<Position_t> PositionSet_t;
+
     /**
      * Output "plane" (e.g. Chest, Palm, Finger, etc.)
+     *
+     * @tparam _Tp The type of the output value.
      */
-    class HapticPlane {
-      protected:
-        std::list<oh_output_point_t> points{};
-        oh_output_writers_map_t writers{};
-        std::map<oh_output_point_t, oh_output_state_t> states{};
-
-        void setOutputs(oh_output_writers_map_t&);
+    template<typename _Tp>
+    class ActuativePlane {
+        static_assert(std::is_same<_Tp, VibroEffect_t>());
 
       public:
-        HapticPlane(oh_output_writers_map_t& outputs)
+        typedef _Tp Value_t;
+        typedef std::map<Position_t, Value_t> PositionStateMap_t;
+
+        /**
+         * The type of the actuator.
+         * @TODO: Make this a template parameter
+         */
+        typedef OH::AbstractActuator Actuator_t;
+        typedef std::map<Position_t, Actuator_t*> ActuatorMap_t;
+
+        ActuativePlane(const ActuatorMap_t& actuators)
         {
-            this->setOutputs(outputs);
-        };
-        std::list<oh_output_point_t>* getOutputPoints(void)
+            this->setActuators(actuators);
+        }
+
+        void setup();
+        virtual void effect(const Position_t&, const Value_t&);
+
+        [[nodiscard]] const PositionSet_t* getAvailablePoints() const
         {
-            return &this->points;
-        };
-        std::map<oh_output_point_t, oh_output_state_t>* getOutputStates(void)
+            return &points;
+        }
+        [[nodiscard]] const PositionStateMap_t* getActuatorStates() const
         {
-            return &this->states;
-        };
-        virtual void writeOutput(const oh_output_data_t&);
-        void setup();
-    };
+            return &states;
+        }
 
-    class HapticPlane_Closest : public HapticPlane {
-      protected:
-        oh_output_point_t findClosestPoints(std::list<oh_output_point_t>& pts, const oh_output_point_t& target);
-        void setOutputs(oh_output_writers_map_t&);
+      private:
+        PositionSet_t points;
+        ActuatorMap_t actuators{};
+        PositionStateMap_t states{};
 
-      public:
-        HapticPlane_Closest(oh_output_writers_map_t& outputs) : HapticPlane(outputs){};
-        void writeOutput(const oh_output_data_t&) override;
+        void setActuators(const ActuatorMap_t&);
     };
 
-    class PlaneMapper_Margin {
+    typedef ActuativePlane<VibroEffect_t> VibroPlane;
+
+    /**
+     * Output plane, finds the closest actuator for the given point.
+     * @deprecated We should guarantee on the driver level, that the actuator is always exists
+     */
+    template<typename _Tp>
+    class ActuativePlane_Closest : public ActuativePlane<_Tp> {
       public:
-        /**
-         * Re-maps a point index to output coordinate.
-         * @tparam _Tp The type of the point index.
-         */
-        template<typename _Tp>
-        static inline oh_output_point_t* mapPoint(_Tp x, _Tp y, _Tp x_max, _Tp y_max)
-        {
-            const oh_output_coord_t x_coord = simpleMap<_Tp>(x + 1, x_max + 2, OH_OUTPUT_COORD_MAX);
-            const oh_output_coord_t y_coord = simpleMap<_Tp>(y + 1, y_max + 2, OH_OUTPUT_COORD_MAX);
+        typedef _Tp Value_t;
 
-            return new oh_output_point_t(x_coord, y_coord);
+        ActuativePlane_Closest(const typename ActuativePlane<_Tp>::ActuatorMap_t& actuators) :
+          ActuativePlane<_Tp>(actuators)
+        {
         }
 
+        void effect(const Position_t&, const Value_t&) override;
+
+      private:
+        [[nodiscard]] const Position_t& findClosestPoint(const PositionSet_t&, const Position_t&) const;
+    };
+
+    typedef ActuativePlane_Closest<VibroEffect_t> VibroPlane_Closest;
+
+    // TODO: configurable margin
+    class PlaneMapper_Margin {
+      public:
         template<typename _Tp>
-        static inline std::map<oh_output_point_t, _Tp*> mapMatrixCoordinates(std::vector<std::vector<_Tp*>> map2d)
+        [[nodiscard]] static constexpr inline std::map<oh_output_point_t, _Tp*>
+          mapMatrixCoordinates(std::vector<std::vector<_Tp*>> map2d)
         {
-            std::map<oh_output_point_t, _Tp*> points{};
+            std::map<Position_t, _Tp*> points{};
 
             size_t y_size = map2d.size();
             size_t y_max = y_size - 1;
@@ -76,14 +101,28 @@ namespace OH {
                 size_t x_max = x_size - 1;
 
                 for (size_t x = 0; x < x_size; ++x) {
-                    AbstractActuator* wr = row.at(x);
-                    oh_output_point_t* coord = mapPoint(x, y, x_max, y_max);
+                    auto* wr = row.at(x);
+                    Position_t coord = PlaneMapper_Margin::mapPoint<Position_t::Value_t>(x, y, x_max, y_max);
 
-                    points[*coord] = wr;
+                    points[coord] = wr;
                 }
             }
 
             return points;
         }
+
+        /**
+         * Re-maps a point index to output coordinate.
+         * @tparam _Tp The type of the point index.
+         */
+        template<typename _Tp>
+        [[nodiscard]] static constexpr inline OH::Point2<_Tp> mapPoint(_Tp x, _Tp y, _Tp x_max, _Tp y_max)
+        {
+            using Point_t = OH::Point2<_Tp>;
+            return Point_t(
+              OH::accurateMap<_Tp>(x + 1, 0, x_max + 2, Point_t::MIN, Point_t::MAX),
+              OH::accurateMap<_Tp>(y + 1, 0, y_max + 2, Point_t::MIN, Point_t::MAX)
+            );
+        }
     };
-} // namespace OH
+} // namespace SenseShift::Body::Haptics
diff --git a/lib/haptics/haptics_interface.hpp b/lib/haptics/haptics_interface.hpp
new file mode 100644
index 00000000..f6734c76
--- /dev/null
+++ b/lib/haptics/haptics_interface.hpp
@@ -0,0 +1,65 @@
+#pragma once
+
+#include <stdint.h>
+#include <variant>
+
+#include <point2.hpp>
+
+namespace SenseShift::Body::Haptics {
+    typedef uint8_t EffectIntex_t;
+    static const EffectIntex_t EFFECT_INVALID = 0xFF;
+    typedef enum class Effect : EffectIntex_t {
+        Invalid = EFFECT_INVALID,
+        Vibro = 0x00,
+        // TODO: thermal, etc.
+        // Thermal = 0x01,
+    } Effect_t;
+
+    typedef uint8_t TargetIndex_t;
+    static const TargetIndex_t TARGET_INVALID = 0xFF;
+    typedef enum class Target : TargetIndex_t {
+        Invalid = TARGET_INVALID,
+        ChestFront = 0x00,
+        ChestBack = 0x01,
+        // Legacy backword compatibility
+        Accessory [[deprecated]] = 0x02,
+
+        // TODO: arms, legs, etc.
+    } Target_t;
+
+    typedef uint8_t Coordinate_t;
+    typedef OH::Point2<Coordinate_t> Position_t;
+
+    // Vibration intensity.
+    typedef struct VibroEffect {
+        using Intensity_t = uint16_t;
+        inline static const Intensity_t INTENSITY_MIN = 0;
+        inline static const Intensity_t INTENSITY_MAX = 4095;
+
+        Intensity_t intensity = 0;
+
+        inline constexpr VibroEffect() = default;
+        inline constexpr VibroEffect(Intensity_t intensity) : intensity(intensity) {}
+        inline constexpr VibroEffect(const VibroEffect& other) = default;
+
+        inline constexpr operator uint16_t() const
+        {
+            return intensity;
+        }
+    } VibroEffect_t;
+
+    // TODO: thermal, etc.
+
+    typedef std::variant<VibroEffect_t
+                         // TODO: thermal, etc.
+                         // ThermalEffect_t
+                         >
+      EffectData_t;
+
+    typedef struct EffectRequest {
+        Effect_t effect = Effect::Invalid;
+        Target_t target = Target::Invalid;
+        Position_t position = Position_t(0, 0);
+        EffectData_t data;
+    } EffectRequest_t;
+} // namespace SenseShift::Body::Haptics
diff --git a/lib/math/point2.hpp b/lib/math/point2.hpp
index 04db618d..0cf151b8 100644
--- a/lib/math/point2.hpp
+++ b/lib/math/point2.hpp
@@ -1,19 +1,43 @@
 #pragma once
 
+#include <cmath>
 #include <tuple>
 
 namespace OH {
     template<typename _Tp>
     struct Point2 {
-        _Tp x, y;
+        static_assert(std::is_arithmetic<_Tp>::value, "OH::Point2 only can be used with arithmetic types");
+
+        typedef _Tp Value_t;
 
-        Point2() : x((_Tp) 0), y((_Tp) 0){};
-        Point2(_Tp x, _Tp y) : x(x), y(y){};
-        Point2(const Point2<_Tp>& v) : x((_Tp) v.x), y((_Tp) v.y){};
+        inline static const _Tp MIN = std::numeric_limits<_Tp>::min();
+        inline static const _Tp MAX = std::numeric_limits<_Tp>::max();
 
-        bool operator==(const Point2& rhs) const;
-        bool operator!=(const Point2& rhs) const;
-        bool operator<(const Point2& rhs) const;
+        _Tp x, y;
+
+        constexpr Point2() : x((_Tp) 0), y((_Tp) 0){};
+        constexpr Point2(_Tp x, _Tp y) : x(x), y(y){};
+        constexpr Point2(const Point2<_Tp>& v) : x((_Tp) v.x), y((_Tp) v.y){};
+
+        constexpr inline bool operator==(const Point2<_Tp>& rhs) const
+        {
+            return x == rhs.x && y == rhs.y;
+        }
+
+        constexpr inline bool operator!=(const Point2<_Tp>& rhs) const
+        {
+            return !(*this == rhs);
+        }
+
+        constexpr bool operator<(const Point2<_Tp>& rhs) const
+        {
+            return std::tie(x, y) < std::tie(rhs.x, rhs.y);
+        }
+
+        constexpr float operator-(const Point2<_Tp>& rhs) const
+        {
+            return std::sqrt(std::pow(x - rhs.x, 2) + std::pow(y - rhs.y, 2));
+        }
     };
 
     typedef Point2<unsigned char> Point2b;
@@ -22,22 +46,4 @@ namespace OH {
     typedef Point2<int> Point2i;
     typedef Point2<short> Point2s;
     typedef Point2<unsigned short> Point2w;
-
-    template<typename _Tp>
-    inline bool Point2<_Tp>::operator==(const Point2<_Tp>& rhs) const
-    {
-        return x == rhs.x && y == rhs.y;
-    }
-
-    template<typename _Tp>
-    inline bool Point2<_Tp>::operator!=(const Point2<_Tp>& rhs) const
-    {
-        return !(*this == rhs);
-    }
-
-    template<typename _Tp>
-    inline bool Point2<_Tp>::operator<(const Point2<_Tp>& rhs) const
-    {
-        return std::tie(x, y) < std::tie(rhs.x, rhs.y);
-    }
 }; // namespace OH
diff --git a/platformio.ini b/platformio.ini
index e6e18eec..f88d3a17 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -67,6 +67,6 @@ build_flags      = ${common.build_flags}
 build_src_filter = ${common.build_src_filter}
 	+<mode_configs/test.cpp>
 lib_deps         = ${common.lib_deps}
-	fabiobatsilva/ArduinoFake@^0.3.1
+	fabiobatsilva/ArduinoFake@^0.4
 
 test_ignore      = test_embedded
diff --git a/test/test_haptic_body/main.cpp b/test/test_haptic_body/main.cpp
deleted file mode 100644
index 81bd6492..00000000
--- a/test/test_haptic_body/main.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-#include <haptic_body.hpp>
-#include <unity.h>
-
-using namespace OH;
-
-class TestActuator : public AbstractActuator {
-  public:
-    bool isSetup = false;
-    oh_output_intensity_t intensity = 0;
-
-    TestActuator() : AbstractActuator() {}
-    void setup() override
-    {
-        this->isSetup = true;
-    }
-    void writeOutput(oh_output_intensity_t intensity) override
-    {
-        this->intensity = intensity;
-    }
-};
-
-void test_it_sets_up_planes(void)
-{
-    auto body = new HapticBody();
-
-    oh_output_writers_map_t outputs = {
-        { { 0, 0 }, new TestActuator() },
-        { { 0, 1 }, new TestActuator() },
-        { { 1, 0 }, new TestActuator() },
-        { { 1, 1 }, new TestActuator() },
-    };
-    auto plane = new HapticPlane(outputs);
-
-    body->addComponent(0, plane);
-    body->setup();
-
-    for (auto& kv : outputs) {
-        TEST_ASSERT_TRUE(static_cast<TestActuator*>(kv.second)->isSetup);
-    }
-}
-
-int process(void)
-{
-    UNITY_BEGIN();
-
-    RUN_TEST(test_it_sets_up_planes);
-
-    return UNITY_END();
-}
-
-#ifdef ARDUINO
-
-#include <Arduino.h>
-
-void setup(void)
-{
-    process();
-}
-
-void loop(void) {}
-
-#else
-
-int main(int argc, char** argv)
-{
-    return process();
-}
-
-#endif
diff --git a/test/test_haptics_body/main.cpp b/test/test_haptics_body/main.cpp
new file mode 100644
index 00000000..d16ed628
--- /dev/null
+++ b/test/test_haptics_body/main.cpp
@@ -0,0 +1,117 @@
+#include <haptic_body.hpp>
+#include <unity.h>
+
+using namespace SenseShift::Body::Haptics;
+
+class TestActuator : public OH::AbstractActuator {
+  public:
+    bool isSetup = false;
+    oh_output_intensity_t intensity = 0;
+
+    TestActuator() : AbstractActuator() {}
+    void setup() override
+    {
+        this->isSetup = true;
+    }
+    void writeOutput(oh_output_intensity_t intensity) override
+    {
+        this->intensity = intensity;
+    }
+};
+
+void test_it_sets_up_planes(void)
+{
+    auto body = new HapticBody();
+
+    VibroPlane::ActuatorMap_t outputs = {
+        { { 0, 0 }, new TestActuator() },
+        { { 0, 1 }, new TestActuator() },
+        { { 1, 0 }, new TestActuator() },
+        { { 1, 1 }, new TestActuator() },
+    };
+    auto plane = new VibroPlane(outputs);
+
+    body->addTarget(Target::ChestFront, plane);
+    body->setup();
+
+    for (auto& kv : outputs) {
+        TEST_ASSERT_TRUE_MESSAGE(static_cast<TestActuator*>(kv.second)->isSetup, "Actuator was not setup");
+    }
+}
+
+void test_it_handles_effect__vibro(void)
+{
+    auto actuator1 = new TestActuator(), actuator2 = new TestActuator(), actuator3 = new TestActuator(),
+         actuator4 = new TestActuator();
+
+    auto body = new HapticBody();
+
+    auto plane = new VibroPlane({
+      { { 0, 0 }, actuator1 },
+      { { 0, 1 }, actuator2 },
+      { { 1, 0 }, actuator3 },
+      { { 1, 1 }, actuator4 },
+    });
+
+    body->addTarget(Target::ChestFront, plane);
+
+    body->effect({
+      .effect = Effect::Vibro,
+      .target = Target::ChestFront,
+      .position = { 0, 0 },
+      .data = (VibroEffect_t) 64,
+    });
+    body->effect({
+      .effect = Effect::Vibro,
+      .target = Target::ChestFront,
+      .position = { 0, 1 },
+      .data = (VibroEffect_t) 128,
+    });
+    body->effect({
+      .effect = Effect::Vibro,
+      .target = Target::ChestFront,
+      .position = { 1, 0 },
+      .data = (VibroEffect_t) 192,
+    });
+    body->effect({
+      .effect = Effect::Vibro,
+      .target = Target::ChestFront,
+      .position = { 1, 1 },
+      .data = (VibroEffect_t) 255,
+    });
+
+    TEST_ASSERT_EQUAL(64, actuator1->intensity);
+    TEST_ASSERT_EQUAL(128, actuator2->intensity);
+    TEST_ASSERT_EQUAL(192, actuator3->intensity);
+    TEST_ASSERT_EQUAL(255, actuator4->intensity);
+}
+
+int process(void)
+{
+    UNITY_BEGIN();
+
+    RUN_TEST(test_it_sets_up_planes);
+    RUN_TEST(test_it_handles_effect__vibro);
+
+    return UNITY_END();
+}
+
+#ifdef ARDUINO
+
+#include <Arduino.h>
+
+void setup(void)
+{
+    process();
+}
+
+void loop(void) {}
+
+#else
+
+int main(int argc, char** argv)
+{
+    return process();
+}
+
+#endif
diff --git a/test/test_haptics_plane/main.cpp b/test/test_haptics_plane/main.cpp
index 6077cf80..ec1af818 100644
--- a/test/test_haptics_plane/main.cpp
+++ b/test/test_haptics_plane/main.cpp
@@ -1,9 +1,9 @@
 #include <haptic_plane.hpp>
 #include <unity.h>
 
-using namespace OH;
+using namespace SenseShift::Body::Haptics;
 
-class TestActuator : public AbstractActuator {
+class TestActuator : public OH::AbstractActuator {
   public:
     bool isSetup = false;
     oh_output_intensity_t intensity = 0;
@@ -21,18 +21,21 @@ class TestActuator : public AbstractActuator {
 
 void test_it_sets_up_actuators(void)
 {
-    oh_output_writers_map_t outputs = {
+    VibroPlane::ActuatorMap_t outputs = {
         { { 0, 0 }, new TestActuator() },
         { { 0, 1 }, new TestActuator() },
         { { 1, 0 }, new TestActuator() },
         { { 1, 1 }, new TestActuator() },
     };
 
-    auto plane = new HapticPlane(outputs);
+    auto plane = new VibroPlane(outputs);
     plane->setup();
 
+    TEST_ASSERT_EQUAL(outputs.size(), plane->getAvailablePoints()->size());
     for (auto& kv : outputs) {
-        TEST_ASSERT_TRUE(static_cast<TestActuator*>(kv.second)->isSetup);
+        TEST_ASSERT_TRUE_MESSAGE(plane->getAvailablePoints()->count(kv.first) > 0, "Expected point was not found");
+        TEST_ASSERT_TRUE_MESSAGE(plane->getActuatorStates()->count(kv.first) > 0, "Expected state was not found");
+        TEST_ASSERT_TRUE_MESSAGE(static_cast<TestActuator*>(kv.second)->isSetup, "Actuator was not setup");
     }
 }
 
@@ -41,20 +44,19 @@ void test_it_writes_to_correct_output(void)
     auto actuator = new TestActuator(), actuator2 = new TestActuator(), actuator3 = new TestActuator(),
          actuator4 = new TestActuator();
 
-    oh_output_writers_map_t outputs = {
+    VibroPlane::ActuatorMap_t outputs = {
         { { 0, 0 }, actuator },
         { { 0, 1 }, actuator2 },
         { { 1, 0 }, actuator3 },
         { { 1, 1 }, actuator4 },
     };
 
-    auto plane = new HapticPlane(outputs);
-    plane->setup();
+    auto plane = new VibroPlane(outputs);
 
-    plane->writeOutput({ { 0, 0 }, 64 });
-    plane->writeOutput({ { 0, 1 }, 128 });
-    plane->writeOutput({ { 1, 0 }, 192 });
-    plane->writeOutput({ { 1, 1 }, 255 });
+    plane->effect({ 0, 0 }, 64);
+    plane->effect({ 0, 1 }, 128);
+    plane->effect({ 1, 0 }, 192);
+    plane->effect({ 1, 1 }, 255);
 
     TEST_ASSERT_EQUAL_UINT8(64, actuator->intensity);
     TEST_ASSERT_EQUAL_UINT8(128, actuator2->intensity);
@@ -67,25 +69,31 @@ void test_it_updates_state(void)
     auto actuator = new TestActuator(), actuator2 = new TestActuator(), actuator3 = new TestActuator(),
          actuator4 = new TestActuator();
 
-    oh_output_writers_map_t outputs = {
+    VibroPlane::ActuatorMap_t outputs = {
         { { 0, 0 }, actuator },
         { { 0, 1 }, actuator2 },
         { { 1, 0 }, actuator3 },
         { { 1, 1 }, actuator4 },
     };
 
-    auto plane = new HapticPlane(outputs);
-    plane->setup();
+    auto plane = new VibroPlane(outputs);
+
+    plane->effect({ 0, 0 }, 64);
+    plane->effect({ 0, 1 }, 128);
+    plane->effect({ 1, 0 }, 192);
+    plane->effect({ 1, 1 }, 255);
+
+    TEST_ASSERT_TRUE(plane->getActuatorStates()->count({ 0, 0 }) > 0);
+    TEST_ASSERT_EQUAL_UINT8(64, plane->getActuatorStates()->at({ 0, 0 }));
 
-    plane->writeOutput({ { 0, 0 }, 64 });
-    plane->writeOutput({ { 0, 1 }, 128 });
-    plane->writeOutput({ { 1, 0 }, 192 });
-    plane->writeOutput({ { 1, 1 }, 255 });
+    TEST_ASSERT_TRUE(plane->getActuatorStates()->count({ 0, 1 }) > 0);
+    TEST_ASSERT_EQUAL_UINT8(128, plane->getActuatorStates()->at({ 0, 1 }));
 
-    TEST_ASSERT_EQUAL_UINT8(64, plane->getOutputStates()->at({ 0, 0 }).intensity);
-    TEST_ASSERT_EQUAL_UINT8(128, plane->getOutputStates()->at({ 0, 1 }).intensity);
-    TEST_ASSERT_EQUAL_UINT8(192, plane->getOutputStates()->at({ 1, 0 }).intensity);
-    TEST_ASSERT_EQUAL_UINT8(255, plane->getOutputStates()->at({ 1, 1 }).intensity);
+    TEST_ASSERT_TRUE(plane->getActuatorStates()->count({ 1, 0 }) > 0);
+    TEST_ASSERT_EQUAL_UINT8(192, plane->getActuatorStates()->at({ 1, 0 }));
+
+    TEST_ASSERT_TRUE(plane->getActuatorStates()->count({ 1, 1 }) > 0);
+    TEST_ASSERT_EQUAL_UINT8(255, plane->getActuatorStates()->at({ 1, 1 }));
 }
 
 void test_closest_it_writes_to_correct_if_exact(void)
@@ -93,20 +101,19 @@ void test_closest_it_writes_to_correct_if_exact(void)
     auto actuator = new TestActuator(), actuator2 = new TestActuator(), actuator3 = new TestActuator(),
          actuator4 = new TestActuator();
 
-    oh_output_writers_map_t outputs = {
+    VibroPlane_Closest::ActuatorMap_t outputs = {
         { { 0, 0 }, actuator },
         { { 0, 1 }, actuator2 },
         { { 1, 0 }, actuator3 },
         { { 1, 1 }, actuator4 },
     };
 
-    auto plane = new HapticPlane_Closest(outputs);
-    plane->setup();
+    auto plane = new VibroPlane_Closest(outputs);
 
-    plane->writeOutput({ { 0, 0 }, 1 });
-    plane->writeOutput({ { 0, 1 }, 2 });
-    plane->writeOutput({ { 1, 0 }, 3 });
-    plane->writeOutput({ { 1, 1 }, 4 });
+    plane->effect({ 0, 0 }, 1);
+    plane->effect({ 0, 1 }, 2);
+    plane->effect({ 1, 0 }, 3);
+    plane->effect({ 1, 1 }, 4);
 
     TEST_ASSERT_EQUAL(1, actuator->intensity);
     TEST_ASSERT_EQUAL(2, actuator2->intensity);
@@ -119,18 +126,17 @@ void test_closest_it_correctly_finds_closest(void)
     auto actuator = new TestActuator(), actuator2 = new TestActuator(), actuator3 = new TestActuator(),
          actuator4 = new TestActuator();
 
-    oh_output_writers_map_t outputs = {
+    VibroPlane_Closest::ActuatorMap_t outputs = {
         { { 0, 0 }, actuator },
         { { 0, 64 }, actuator2 },
         { { 64, 0 }, actuator3 },
         { { 64, 64 }, actuator4 },
     };
 
-    auto plane = new HapticPlane_Closest(outputs);
-    plane->setup();
+    auto plane = new VibroPlane_Closest(outputs);
 
-    plane->writeOutput({ { 16, 16 }, 16 });
-    plane->writeOutput({ { 65, 65 }, 65 });
+    plane->effect({ 16, 16 }, 16);
+    plane->effect({ 65, 65 }, 65);
 
     TEST_ASSERT_EQUAL(16, actuator->intensity);
     TEST_ASSERT_EQUAL(0, actuator2->intensity);
@@ -143,41 +149,47 @@ void test_closest_it_updates_state(void)
     auto actuator = new TestActuator(), actuator2 = new TestActuator(), actuator3 = new TestActuator(),
          actuator4 = new TestActuator();
 
-    oh_output_writers_map_t outputs = {
+    VibroPlane_Closest::ActuatorMap_t outputs = {
         { { 0, 0 }, actuator },
         { { 0, 64 }, actuator2 },
         { { 64, 0 }, actuator3 },
         { { 64, 64 }, actuator4 },
     };
 
-    auto plane = new HapticPlane_Closest(outputs);
-    plane->setup();
+    auto plane = new VibroPlane_Closest(outputs);
+
+    plane->effect({ 16, 16 }, 16);
+    plane->effect({ 65, 65 }, 65);
+
+    TEST_ASSERT_TRUE(plane->getActuatorStates()->count({ 0, 0 }) > 0);
+    TEST_ASSERT_EQUAL(16, plane->getActuatorStates()->at({ 0, 0 }));
 
-    plane->writeOutput({ { 16, 16 }, 16 });
-    plane->writeOutput({ { 65, 65 }, 65 });
+    TEST_ASSERT_TRUE(plane->getActuatorStates()->count({ 0, 64 }) > 0);
+    TEST_ASSERT_EQUAL(0, plane->getActuatorStates()->at({ 0, 64 }));
 
-    TEST_ASSERT_EQUAL(16, plane->getOutputStates()->at({ 0, 0 }).intensity);
-    TEST_ASSERT_EQUAL(0, plane->getOutputStates()->at({ 0, 64 }).intensity);
-    TEST_ASSERT_EQUAL(0, plane->getOutputStates()->at({ 64, 0 }).intensity);
-    TEST_ASSERT_EQUAL(65, plane->getOutputStates()->at({ 64, 64 }).intensity);
+    TEST_ASSERT_TRUE(plane->getActuatorStates()->count({ 64, 0 }) > 0);
+    TEST_ASSERT_EQUAL(0, plane->getActuatorStates()->at({ 64, 0 }));
+
+    TEST_ASSERT_TRUE(plane->getActuatorStates()->count({ 64, 64 }) > 0);
+    TEST_ASSERT_EQUAL(65, plane->getActuatorStates()->at({ 64, 64 }));
 }
 
 void test_plain_mapper_margin_map_points(void)
 {
-    auto point = PlaneMapper_Margin::mapPoint(0, 0, 0, 0);
+    auto point = PlaneMapper_Margin::mapPoint<uint8_t>(0, 0, 0, 0);
 
-    TEST_ASSERT_EQUAL(127, point->x);
-    TEST_ASSERT_EQUAL(127, point->y);
+    TEST_ASSERT_EQUAL(127, point.x);
+    TEST_ASSERT_EQUAL(127, point.y);
 
-    point = PlaneMapper_Margin::mapPoint(0, 0, 1, 1);
+    point = PlaneMapper_Margin::mapPoint<uint8_t>(0, 0, 1, 1);
 
-    TEST_ASSERT_EQUAL(85, point->x);
-    TEST_ASSERT_EQUAL(85, point->y);
+    TEST_ASSERT_EQUAL(85, point.x);
+    TEST_ASSERT_EQUAL(85, point.y);
 
-    point = PlaneMapper_Margin::mapPoint(1, 1, 1, 1);
+    point = PlaneMapper_Margin::mapPoint<uint8_t>(1, 1, 1, 1);
 
-    TEST_ASSERT_EQUAL(170, point->x);
-    TEST_ASSERT_EQUAL(170, point->y);
+    TEST_ASSERT_EQUAL(170, point.x);
+    TEST_ASSERT_EQUAL(170, point.y);
 }
 
 int process(void)
@@ -187,9 +199,11 @@ int process(void)
     RUN_TEST(test_it_sets_up_actuators);
     RUN_TEST(test_it_writes_to_correct_output);
     RUN_TEST(test_it_updates_state);
+
     RUN_TEST(test_closest_it_writes_to_correct_if_exact);
     RUN_TEST(test_closest_it_correctly_finds_closest);
     RUN_TEST(test_closest_it_updates_state);
+
     RUN_TEST(test_plain_mapper_margin_map_points);
 
     return UNITY_END();
diff --git a/test/test_math_point2/main.cpp b/test/test_math_point2/main.cpp
index 48378b18..8da8b3dd 100644
--- a/test/test_math_point2/main.cpp
+++ b/test/test_math_point2/main.cpp
@@ -33,6 +33,15 @@ void test_operator_less_than(void)
     TEST_ASSERT_TRUE(p1 < p3);
 }
 
+void test_operator_minus(void)
+{
+    Point2 p1 = { 32, 32 };
+    Point2 p2 = { 16, 16 };
+
+    TEST_ASSERT_EQUAL_FLOAT(p1 - p2, p2 - p1);
+    TEST_ASSERT_EQUAL_FLOAT(22.6274, p1 - p2);
+}
+
 int process(void)
 {
     UNITY_BEGIN();
@@ -40,6 +49,7 @@ int process(void)
     RUN_TEST(test_operator_equal);
     RUN_TEST(test_operator_not_equal);
     RUN_TEST(test_operator_less_than);
+    RUN_TEST(test_operator_minus);
 
     return UNITY_END();
 }

From 2394497e1041e4f0e7ea2d4ed2803a0db5cec098 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Sat, 12 Aug 2023 02:22:17 +0400
Subject: [PATCH 02/82] refactor(bHaptics): use new Haptics interfafce

---
 lib/bhaptics/bh_constants.hpp |  86 ++++++++--------
 lib/bhaptics/bh_utils.cpp     | 114 ++++++++++-----------
 lib/bhaptics/bh_utils.hpp     |  40 ++++++--
 lib/core/types.hpp            |   2 +-
 test/test_bhaptics/main.cpp   | 188 ++++++----------------------------
 5 files changed, 164 insertions(+), 266 deletions(-)

diff --git a/lib/bhaptics/bh_constants.hpp b/lib/bhaptics/bh_constants.hpp
index 92e8ec60..b28f53e3 100644
--- a/lib/bhaptics/bh_constants.hpp
+++ b/lib/bhaptics/bh_constants.hpp
@@ -1,5 +1,7 @@
 #pragma once
 
+#include <haptic_plane.hpp>
+
 #if defined(ESP32)
 #include <BLEUUID.h>
 #endif
@@ -17,12 +19,12 @@
 
 #define BH_LAYOUT_TACTSUITX16_SIZE_X 4
 #define BH_LAYOUT_TACTSUITX16_SIZE_Y 2
-#define BH_LAYOUT_TACTSUITX16_MAKE_POINT(x, y)                \
-    OH::PlaneMapper_Margin::mapPoint<oh_output_coord_t>(      \
-      x,                                                      \
-      y,                                                      \
-      (oh_output_coord_t) (BH_LAYOUT_TACTSUITX16_SIZE_X - 1), \
-      (oh_output_coord_t) (BH_LAYOUT_TACTSUITX16_SIZE_Y - 1)  \
+#define BH_LAYOUT_TACTSUITX16_MAKE_POINT(x, y)                                  \
+    SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
+      x,                                                                        \
+      y,                                                                        \
+      (oh_output_coord_t) (BH_LAYOUT_TACTSUITX16_SIZE_X - 1),                   \
+      (oh_output_coord_t) (BH_LAYOUT_TACTSUITX16_SIZE_Y - 1)                    \
     )
 
 // X16 suit uses the same packets structure as x40 suit and performs motor grouping in firmware
@@ -95,12 +97,12 @@
 
 #define BH_LAYOUT_TACTSUITX40_SIZE_X 4
 #define BH_LAYOUT_TACTSUITX40_SIZE_Y 5
-#define BH_LAYOUT_TACTSUITX40_MAKE_POINT(x, y)                \
-    OH::PlaneMapper_Margin::mapPoint<oh_output_coord_t>(      \
-      x,                                                      \
-      y,                                                      \
-      (oh_output_coord_t) (BH_LAYOUT_TACTSUITX40_SIZE_X - 1), \
-      (oh_output_coord_t) (BH_LAYOUT_TACTSUITX40_SIZE_Y - 1)  \
+#define BH_LAYOUT_TACTSUITX40_MAKE_POINT(x, y)                                  \
+    SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
+      x,                                                                        \
+      y,                                                                        \
+      (oh_output_coord_t) (BH_LAYOUT_TACTSUITX40_SIZE_X - 1),                   \
+      (oh_output_coord_t) (BH_LAYOUT_TACTSUITX40_SIZE_Y - 1)                    \
     )
 
 // X * Y for front and back
@@ -162,12 +164,12 @@
 
 #define BH_LAYOUT_TACTAL_SIZE_X 6
 #define BH_LAYOUT_TACTAL_SIZE_Y 1
-#define BH_LAYOUT_TACTAL_MAKE_POINT(x, y)                \
-    OH::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
-      x,                                                 \
-      y,                                                 \
-      (oh_output_coord_t) (BH_LAYOUT_TACTAL_SIZE_X - 1), \
-      (oh_output_coord_t) (BH_LAYOUT_TACTAL_SIZE_Y - 1)  \
+#define BH_LAYOUT_TACTAL_MAKE_POINT(x, y)                                       \
+    SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
+      x,                                                                        \
+      y,                                                                        \
+      (oh_output_coord_t) (BH_LAYOUT_TACTAL_SIZE_X - 1),                        \
+      (oh_output_coord_t) (BH_LAYOUT_TACTAL_SIZE_Y - 1)                         \
     )
 
 #define BH_LAYOUT_TACTAL_SIZE (BH_LAYOUT_TACTAL_SIZE_X * BH_LAYOUT_TACTAL_SIZE_Y)
@@ -188,12 +190,12 @@
 
 #define BH_LAYOUT_TACTVISOR_SIZE_X 4
 #define BH_LAYOUT_TACTVISOR_SIZE_Y 1
-#define BH_LAYOUT_TACTVISOR_MAKE_POINT(x, y)                \
-    OH::PlaneMapper_Margin::mapPoint<oh_output_coord_t>(    \
-      x,                                                    \
-      y,                                                    \
-      (oh_output_coord_t) (BH_LAYOUT_TACTVISOR_SIZE_X - 1), \
-      (oh_output_coord_t) (BH_LAYOUT_TACTVISOR_SIZE_Y - 1)  \
+#define BH_LAYOUT_TACTVISOR_MAKE_POINT(x, y)                                    \
+    SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
+      x,                                                                        \
+      y,                                                                        \
+      (oh_output_coord_t) (BH_LAYOUT_TACTVISOR_SIZE_X - 1),                     \
+      (oh_output_coord_t) (BH_LAYOUT_TACTVISOR_SIZE_Y - 1)                      \
     )
 
 #define BH_LAYOUT_TACTVISOR_SIZE (BH_LAYOUT_TACTVISOR_SIZE_X * BH_LAYOUT_TACTVISOR_SIZE_Y)
@@ -212,12 +214,12 @@
 
 #define BH_LAYOUT_TACTOSY2_SIZE_X 3
 #define BH_LAYOUT_TACTOSY2_SIZE_Y 2
-#define BH_LAYOUT_TACTOSY2_MAKE_POINT(x, y)                \
-    OH::PlaneMapper_Margin::mapPoint<oh_output_coord_t>(   \
-      x,                                                   \
-      y,                                                   \
-      (oh_output_coord_t) (BH_LAYOUT_TACTOSY2_SIZE_X - 1), \
-      (oh_output_coord_t) (BH_LAYOUT_TACTOSY2_SIZE_Y - 1)  \
+#define BH_LAYOUT_TACTOSY2_MAKE_POINT(x, y)                                     \
+    SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
+      x,                                                                        \
+      y,                                                                        \
+      (oh_output_coord_t) (BH_LAYOUT_TACTOSY2_SIZE_X - 1),                      \
+      (oh_output_coord_t) (BH_LAYOUT_TACTOSY2_SIZE_Y - 1)                       \
     )
 
 #define BH_LAYOUT_TACTOSY2_SIZE (BH_LAYOUT_TACTOSY2_SIZE_X * BH_LAYOUT_TACTOSY2_SIZE_Y)
@@ -234,12 +236,12 @@
 
 #define BH_LAYOUT_TACTOSYH_SIZE_X 1
 #define BH_LAYOUT_TACTOSYH_SIZE_Y 3
-#define BH_LAYOUT_TACTOSYH_MAKE_POINT(x, y)                \
-    OH::PlaneMapper_Margin::mapPoint<oh_output_coord_t>(   \
-      x,                                                   \
-      y,                                                   \
-      (oh_output_coord_t) (BH_LAYOUT_TACTOSYH_SIZE_X - 1), \
-      (oh_output_coord_t) (BH_LAYOUT_TACTOSYH_SIZE_Y - 1)  \
+#define BH_LAYOUT_TACTOSYH_MAKE_POINT(x, y)                                     \
+    SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
+      x,                                                                        \
+      y,                                                                        \
+      (oh_output_coord_t) (BH_LAYOUT_TACTOSYH_SIZE_X - 1),                      \
+      (oh_output_coord_t) (BH_LAYOUT_TACTOSYH_SIZE_Y - 1)                       \
     )
 
 #define BH_LAYOUT_TACTOSYH_SIZE (BH_LAYOUT_TACTOSYH_SIZE_X * BH_LAYOUT_TACTOSYH_SIZE_Y)
@@ -253,12 +255,12 @@
 
 #define BH_LAYOUT_TACTOSYF_SIZE_X 1
 #define BH_LAYOUT_TACTOSYF_SIZE_Y 3
-#define BH_LAYOUT_TACTOSYF_MAKE_POINT(x, y)                \
-    OH::PlaneMapper_Margin::mapPoint<oh_output_coord_t>(   \
-      x,                                                   \
-      y,                                                   \
-      (oh_output_coord_t) (BH_LAYOUT_TACTOSYF_SIZE_X - 1), \
-      (oh_output_coord_t) (BH_LAYOUT_TACTOSYF_SIZE_Y - 1)  \
+#define BH_LAYOUT_TACTOSYF_MAKE_POINT(x, y)                                     \
+    SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
+      x,                                                                        \
+      y,                                                                        \
+      (oh_output_coord_t) (BH_LAYOUT_TACTOSYF_SIZE_X - 1),                      \
+      (oh_output_coord_t) (BH_LAYOUT_TACTOSYF_SIZE_Y - 1)                       \
     )
 
 #define BH_LAYOUT_TACTOSYF_SIZE (BH_LAYOUT_TACTOSYF_SIZE_X * BH_LAYOUT_TACTOSYF_SIZE_Y)
diff --git a/lib/bhaptics/bh_utils.cpp b/lib/bhaptics/bh_utils.cpp
index 710e53a5..d1a4654a 100644
--- a/lib/bhaptics/bh_utils.cpp
+++ b/lib/bhaptics/bh_utils.cpp
@@ -2,60 +2,54 @@
 
 #include <utility.hpp>
 
-void BH::plainOutputTransformer(
-  OH::HapticBody* output,
-  std::string& value,
-  const oh_output_point_t* layout[],
-  const size_t layoutSize,
-  const oh_output_path_t path
-)
-{
-    for (size_t i = 0; i < layoutSize; i++) {
-        uint8_t byte = value[i];
-
-        oh_output_data_t outputData{
-            .point = *layout[i],
-            // TODO: optimize generic type
-            .intensity =
-              static_cast<oh_output_intensity_t>(OH::accurateMap<long>(byte, 0, 100, 0, OH_OUTPUT_INTENSITY_MAX)),
-        };
-
-        output->writeOutput(path, outputData);
+namespace BH {
+    template<size_t N = 20>
+    void vestOutputTransformer(
+      SenseShift::Body::Haptics::HapticBody* output, const char (&value)[N], const oh_output_point_t (&layout)[N * 2]
+    )
+    {
+        for (size_t i = 0; i < N; i++) {
+            uint8_t byte = value[i];
+            uint actIndex = i * 2;
+            const auto target = (actIndex < 10 || actIndex >= 30) ? SenseShift::Body::Haptics::Target::ChestFront
+                                                                  : SenseShift::Body::Haptics::Target::ChestBack;
+
+            output->effect({
+              .effect = SenseShift::Body::Haptics::Effect::Vibro,
+              .target = target,
+              .position = layout[actIndex],
+              .data = SenseShift::Body::Haptics::VibroEffect(
+                OH::simpleMap<SenseShift::Body::Haptics::VibroEffect::Intensity_t>(
+                  ((byte >> 4) & 0xf),
+                  15,
+                  SenseShift::Body::Haptics::VibroEffect::INTENSITY_MAX
+                )
+              ),
+            });
+            output->effect({
+              .effect = SenseShift::Body::Haptics::Effect::Vibro,
+              .target = target,
+              .position = layout[actIndex + 1],
+              .data = SenseShift::Body::Haptics::VibroEffect(
+                OH::simpleMap<SenseShift::Body::Haptics::VibroEffect::Intensity_t>(
+                  (byte & 0xf),
+                  15,
+                  SenseShift::Body::Haptics::VibroEffect::INTENSITY_MAX
+                )
+              ),
+            });
+        }
     }
-}
-
-void BH::vestOutputTransformer(
-  OH::HapticBody* output, std::string& value, const oh_output_point_t* layout[], const size_t layoutSize
-)
-{
-    for (size_t i = 0; i < layoutSize / 2; i++) {
-        uint8_t byte = value[i];
-        uint actIndex = i * 2;
-        const auto path = (actIndex < 10 || actIndex >= 30) ? OUTPUT_PATH_CHEST_FRONT : OUTPUT_PATH_CHEST_BACK;
-
-        const oh_output_data_t outputData0{
-            .point = *layout[actIndex],
-            // TODO: optimize generic type
-            .intensity =
-              static_cast<oh_output_intensity_t>(OH::simpleMap<long>(((byte >> 4) & 0xf), 15, OH_OUTPUT_INTENSITY_MAX)),
-        };
 
-        const oh_output_data_t outputData1{
-            .point = *layout[actIndex + 1],
-            // TODO: optimize generic type
-            .intensity =
-              static_cast<oh_output_intensity_t>(OH::simpleMap<long>((byte & 0xf), 15, OH_OUTPUT_INTENSITY_MAX)),
-        };
-
-        output->writeOutput(path, outputData0);
-        output->writeOutput(path, outputData1);
-    }
-}
+    template void vestOutputTransformer<20>(
+      SenseShift::Body::Haptics::HapticBody* output, const char (&value)[20], const oh_output_point_t (&layout)[40]
+    );
+} // namespace BH
 
 void BH::vestX16OutputTransformer(
-  OH::HapticBody* output,
-  std::string& value,
-  const oh_output_point_t* layout[],
+  SenseShift::Body::Haptics::HapticBody* output,
+  const std::string& value,
+  const oh_output_point_t* layout,
   const size_t layoutSize,
   const uint8_t layoutGroups[],
   const size_t layoutGroupsSize
@@ -98,14 +92,16 @@ void BH::vestX16OutputTransformer(
             continue;
         }
 
-        const auto path = (i < 10 || i >= 30) ? OUTPUT_PATH_CHEST_FRONT : OUTPUT_PATH_CHEST_BACK;
-        const oh_output_data_t outputData{
-            .point = *layout[i],
-            // TODO: optimize generic type
-            .intensity =
-              static_cast<oh_output_intensity_t>(OH::accurateMap<long>(result[i], 0, 15, 0, OH_OUTPUT_INTENSITY_MAX)),
-        };
-
-        output->writeOutput(path, outputData);
+        const auto target = (i < 10 || i >= 30) ? SenseShift::Body::Haptics::Target::ChestFront
+                                                : SenseShift::Body::Haptics::Target::ChestBack;
+
+        output->effect({
+          .effect = SenseShift::Body::Haptics::Effect::Vibro,
+          .target = target,
+          .position = layout[i],
+          .data = SenseShift::Body::Haptics::VibroEffect(
+            static_cast<oh_output_intensity_t>(OH::accurateMap<long>(result[i], 0, 15, 0, OH_OUTPUT_INTENSITY_MAX))
+          ),
+        });
     }
 }
diff --git a/lib/bhaptics/bh_utils.hpp b/lib/bhaptics/bh_utils.hpp
index b1b42fd6..2825aea7 100644
--- a/lib/bhaptics/bh_utils.hpp
+++ b/lib/bhaptics/bh_utils.hpp
@@ -4,20 +4,40 @@
 #include <string>
 
 namespace BH {
+    template<size_t N>
     void plainOutputTransformer(
-      OH::HapticBody* output,
-      std::string& value,
-      const oh_output_point_t* layout[],
-      const size_t layoutSize,
-      const oh_output_path_t path
-    );
+      SenseShift::Body::Haptics::HapticBody* output,
+      const char (&value)[N],
+      const oh_output_point_t (&layout)[N],
+      const SenseShift::Body::Haptics::Target_t target
+    )
+    {
+        for (size_t i = 0; i < N; i++) {
+            uint8_t byte = value[i];
+
+            output->effect({
+              .effect = SenseShift::Body::Haptics::Effect::Vibro,
+              .target = target,
+              .position = layout[i],
+              .data = SenseShift::Body::Haptics::VibroEffect(
+                OH::simpleMap<SenseShift::Body::Haptics::VibroEffect::Intensity_t>(
+                  byte,
+                  100,
+                  SenseShift::Body::Haptics::VibroEffect::INTENSITY_MAX
+                )
+              ),
+            });
+        }
+    }
+
+    template<size_t N = 20>
     void vestOutputTransformer(
-      OH::HapticBody* output, std::string& value, const oh_output_point_t* layout[], const size_t layoutSize
+      SenseShift::Body::Haptics::HapticBody* output, const char (&value)[N], const oh_output_point_t (&layout)[N * 2]
     );
     void vestX16OutputTransformer(
-      OH::HapticBody* output,
-      std::string& value,
-      const oh_output_point_t* layout[],
+      SenseShift::Body::Haptics::HapticBody* output,
+      const std::string& value,
+      const oh_output_point_t* layout,
       const size_t layoutSize,
       const uint8_t layoutGroups[],
       const size_t layoutGroupsSize
diff --git a/lib/core/types.hpp b/lib/core/types.hpp
index 3c51c75f..ed2c7a20 100644
--- a/lib/core/types.hpp
+++ b/lib/core/types.hpp
@@ -9,5 +9,5 @@
 #define OH_OUTPUT_COORD_MAX UINT8_MAX
 
 typedef uint8_t oh_output_path_t;
-typedef OH_OUTPUT_COORD_T  oh_output_coord_t;
+typedef OH_OUTPUT_COORD_T oh_output_coord_t;
 typedef OH::Point2b oh_output_point_t;
diff --git a/test/test_bhaptics/main.cpp b/test/test_bhaptics/main.cpp
index 1725cca1..1cc22a18 100644
--- a/test/test_bhaptics/main.cpp
+++ b/test/test_bhaptics/main.cpp
@@ -1,12 +1,13 @@
 #include <bh_types.hpp>
 #include <bh_utils.hpp>
-#include <haptic_plane.hpp>
+
 #include <unity.h>
 
+using namespace SenseShift::Body::Haptics;
 using namespace OH;
 using namespace BH;
 
-class TestActuator : public AbstractActuator {
+class TestActuator : public OH::AbstractActuator {
   public:
     bool isSetup = false;
     oh_output_intensity_t intensity = 0;
@@ -25,7 +26,7 @@ class TestActuator : public AbstractActuator {
 void test_layout_tactsuitx16(void)
 {
     static const size_t bhLayoutSize = BH_LAYOUT_TACTSUITX16_SIZE;
-    static const oh_output_point_t* bhLayout[bhLayoutSize] = BH_LAYOUT_TACTSUITX16;
+    static const oh_output_point_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTSUITX16;
 
     static const size_t layoutGroupsSize = BH_LAYOUT_TACTSUITX16_GROUPS_SIZE;
     static const uint8_t layoutGroups[layoutGroupsSize] = BH_LAYOUT_TACTSUITX16_GROUPS;
@@ -49,7 +50,7 @@ void test_layout_tactsuitx16(void)
     TestActuator* actuator14 = new TestActuator();
     TestActuator* actuator15 = new TestActuator();
 
-    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
+    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<OH::AbstractActuator>({
       { actuator0, actuator1, actuator2, actuator3 },
       { actuator4, actuator5, actuator6, actuator7 },
     });
@@ -58,12 +59,12 @@ void test_layout_tactsuitx16(void)
       { actuator12, actuator13, actuator14, actuator15 },
     });
 
-    auto frontPlane = new HapticPlane(frontOutputs);
-    auto backPlane = new HapticPlane(backOutputs);
+    auto frontPlane = new VibroPlane(frontOutputs);
+    auto backPlane = new VibroPlane(backOutputs);
 
-    body->addComponent(OUTPUT_PATH_CHEST_FRONT, frontPlane);
-    body->addComponent(OUTPUT_PATH_CHEST_BACK, backPlane);
-    body->setup();
+    body->addTarget(Target::ChestFront, frontPlane);
+    body->addTarget(Target::ChestBack, backPlane);
+    // body->setup();
 
     uint8_t values[] = {
         0x01, 0x00, 0x23, 0x00, 0x00, 0x45, 0x00, 0x67, 0x00, 0x00,
@@ -94,7 +95,7 @@ void test_layout_tactsuitx16(void)
 void test_layout_tactsuitx40(void)
 {
     static const size_t bhLayoutSize = BH_LAYOUT_TACTSUITX40_SIZE;
-    static const oh_output_point_t* bhLayout[bhLayoutSize] = BH_LAYOUT_TACTSUITX40;
+    static const oh_output_point_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTSUITX40;
 
     auto body = new HapticBody();
 
@@ -116,20 +117,21 @@ void test_layout_tactsuitx40(void)
     auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>(frontMatrix);
     auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>(backMatrix);
 
-    auto frontPlane = new HapticPlane(frontOutputs);
-    auto backPlane = new HapticPlane(backOutputs);
-
-    body->addComponent(OUTPUT_PATH_CHEST_FRONT, frontPlane);
-    body->addComponent(OUTPUT_PATH_CHEST_BACK, backPlane);
-    body->setup();
-
-    uint8_t values[] = {
-        0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x00, 0x00,
-        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    };
-    std::string value = std::string((char*) values, sizeof(values));
-
-    vestOutputTransformer(body, value, bhLayout, bhLayoutSize);
+    auto frontPlane = new VibroPlane(frontOutputs);
+    auto backPlane = new VibroPlane(backOutputs);
+
+    body->addTarget(Target::ChestFront, frontPlane);
+    body->addTarget(Target::ChestBack, backPlane);
+    // body->setup();
+
+    vestOutputTransformer(
+      body,
+      {
+        0x01, 0x23, 0x45, 0x67, (char) 0x89, (char) 0xab, (char) 0xcd, (char) 0xef, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00,        0x00,        0x00,        0x00,        0x00, 0x00,
+      },
+      bhLayout
+    );
     TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(frontMatrix[0][0])->intensity);
     TEST_ASSERT_EQUAL_INT(273, static_cast<TestActuator*>(frontMatrix[0][1])->intensity);
     TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(frontMatrix[0][2])->intensity);
@@ -176,7 +178,7 @@ void test_layout_tactsuitx40(void)
 void test_layout_tactal(void)
 {
     static const size_t bhLayoutSize = BH_LAYOUT_TACTAL_SIZE;
-    static const oh_output_point_t* bhLayout[bhLayoutSize] = BH_LAYOUT_TACTAL;
+    static const oh_output_point_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTAL;
 
     auto body = new HapticBody();
 
@@ -190,60 +192,11 @@ void test_layout_tactal(void)
     auto outputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
       { actuator0, actuator1, actuator2, actuator3, actuator4, actuator5 },
     });
-    auto plane = new HapticPlane(outputs);
-
-    body->addComponent(OUTPUT_PATH_ACCESSORY, plane);
-    body->setup();
-
-    uint8_t values[] = { 0x64, 0x00, 0x00, 0x00, 0x00, 0x00 };
-    std::string value = std::string((char*) values, sizeof(values));
-
-    plainOutputTransformer(body, value, bhLayout, bhLayoutSize, OUTPUT_PATH_ACCESSORY);
-    TEST_ASSERT_EQUAL_INT(4095, actuator0->intensity);
-    TEST_ASSERT_EQUAL_INT(0, actuator1->intensity);
-    TEST_ASSERT_EQUAL_INT(0, actuator2->intensity);
-    TEST_ASSERT_EQUAL_INT(0, actuator3->intensity);
-    TEST_ASSERT_EQUAL_INT(0, actuator4->intensity);
-    TEST_ASSERT_EQUAL_INT(0, actuator5->intensity);
-
-    value = "\x10\x20\x30\x40\x50\x60";
-
-    plainOutputTransformer(body, value, bhLayout, bhLayoutSize, OUTPUT_PATH_ACCESSORY);
-    TEST_ASSERT_EQUAL_INT(655, actuator0->intensity);
-    TEST_ASSERT_EQUAL_INT(1310, actuator1->intensity);
-    TEST_ASSERT_EQUAL_INT(1965, actuator2->intensity);
-    TEST_ASSERT_EQUAL_INT(2620, actuator3->intensity);
-    TEST_ASSERT_EQUAL_INT(3276, actuator4->intensity);
-    TEST_ASSERT_EQUAL_INT(3931, actuator5->intensity);
-}
-
-void test_layout_tactosy2(void)
-{
-    static const size_t bhLayoutSize = BH_LAYOUT_TACTOSY2_SIZE;
-    static const oh_output_point_t* bhLayout[bhLayoutSize] = BH_LAYOUT_TACTOSY2;
-
-    auto body = new HapticBody();
-
-    TestActuator* actuator0 = new TestActuator();
-    TestActuator* actuator1 = new TestActuator();
-    TestActuator* actuator2 = new TestActuator();
-    TestActuator* actuator3 = new TestActuator();
-    TestActuator* actuator4 = new TestActuator();
-    TestActuator* actuator5 = new TestActuator();
-
-    auto outputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
-      { actuator0, actuator1, actuator2 },
-      { actuator3, actuator4, actuator5 },
-    });
-    auto plane = new HapticPlane(outputs);
-
-    body->addComponent(OUTPUT_PATH_ACCESSORY, plane);
-    body->setup();
+    auto plane = new VibroPlane(outputs);
 
-    uint8_t values[] = { 0x64, 0x00, 0x00, 0x00, 0x00, 0x00 };
-    std::string value = std::string((char*) values, sizeof(values));
+    body->addTarget(Target::Accessory, plane);
 
-    plainOutputTransformer(body, value, bhLayout, bhLayoutSize, OUTPUT_PATH_ACCESSORY);
+    plainOutputTransformer(body, { 0x64, 0x00, 0x00, 0x00, 0x00, 0x00 }, bhLayout, Target::Accessory);
     TEST_ASSERT_EQUAL_INT(4095, actuator0->intensity);
     TEST_ASSERT_EQUAL_INT(0, actuator1->intensity);
     TEST_ASSERT_EQUAL_INT(0, actuator2->intensity);
@@ -251,8 +204,7 @@ void test_layout_tactosy2(void)
     TEST_ASSERT_EQUAL_INT(0, actuator4->intensity);
     TEST_ASSERT_EQUAL_INT(0, actuator5->intensity);
 
-    value = "\x10\x20\x30\x40\x50\x60";
-    plainOutputTransformer(body, value, bhLayout, bhLayoutSize, OUTPUT_PATH_ACCESSORY);
+    plainOutputTransformer(body, { 0x10, 0x20, 0x30, 0x40, 0x50, 0x60 }, bhLayout, Target::Accessory);
     TEST_ASSERT_EQUAL_INT(655, actuator0->intensity);
     TEST_ASSERT_EQUAL_INT(1310, actuator1->intensity);
     TEST_ASSERT_EQUAL_INT(1965, actuator2->intensity);
@@ -261,78 +213,6 @@ void test_layout_tactosy2(void)
     TEST_ASSERT_EQUAL_INT(3931, actuator5->intensity);
 }
 
-void test_layout_tactosyh(void)
-{
-    static const size_t bhLayoutSize = BH_LAYOUT_TACTOSYH_SIZE;
-    static const oh_output_point_t* bhLayout[bhLayoutSize] = BH_LAYOUT_TACTOSYH;
-
-    auto body = new HapticBody();
-
-    TestActuator* actuator0 = new TestActuator();
-    TestActuator* actuator1 = new TestActuator();
-    TestActuator* actuator2 = new TestActuator();
-
-    auto outputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
-      { actuator0 },
-      { actuator1 },
-      { actuator2 },
-    });
-    auto plane = new HapticPlane(outputs);
-
-    body->addComponent(OUTPUT_PATH_ACCESSORY, plane);
-    body->setup();
-
-    uint8_t values[] = { 0x64, 0x00, 0x00 };
-    std::string value = std::string((char*) values, sizeof(values));
-
-    plainOutputTransformer(body, value, bhLayout, bhLayoutSize, OUTPUT_PATH_ACCESSORY);
-    TEST_ASSERT_EQUAL_INT(4095, actuator0->intensity);
-    TEST_ASSERT_EQUAL_INT(0, actuator1->intensity);
-    TEST_ASSERT_EQUAL_INT(0, actuator2->intensity);
-
-    value = "\x10\x20\x30";
-    plainOutputTransformer(body, value, bhLayout, bhLayoutSize, OUTPUT_PATH_ACCESSORY);
-    TEST_ASSERT_EQUAL_INT(655, actuator0->intensity);
-    TEST_ASSERT_EQUAL_INT(1310, actuator1->intensity);
-    TEST_ASSERT_EQUAL_INT(1965, actuator2->intensity);
-}
-
-void test_layout_tactosyf(void)
-{
-    static const size_t bhLayoutSize = BH_LAYOUT_TACTOSYF_SIZE;
-    static const oh_output_point_t* bhLayout[bhLayoutSize] = BH_LAYOUT_TACTOSYF;
-
-    auto body = new HapticBody();
-
-    TestActuator* actuator0 = new TestActuator();
-    TestActuator* actuator1 = new TestActuator();
-    TestActuator* actuator2 = new TestActuator();
-
-    auto outputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
-      { actuator0 },
-      { actuator1 },
-      { actuator2 },
-    });
-    auto plane = new HapticPlane(outputs);
-
-    body->addComponent(OUTPUT_PATH_ACCESSORY, plane);
-    body->setup();
-
-    uint8_t values[] = { 0x64, 0x00, 0x00 };
-    std::string value = std::string((char*) values, sizeof(values));
-
-    plainOutputTransformer(body, value, bhLayout, bhLayoutSize, OUTPUT_PATH_ACCESSORY);
-    TEST_ASSERT_EQUAL_INT(4095, actuator0->intensity);
-    TEST_ASSERT_EQUAL_INT(0, actuator1->intensity);
-    TEST_ASSERT_EQUAL_INT(0, actuator2->intensity);
-
-    value = "\x10\x20\x30";
-    plainOutputTransformer(body, value, bhLayout, bhLayoutSize, OUTPUT_PATH_ACCESSORY);
-    TEST_ASSERT_EQUAL_INT(655, actuator0->intensity);
-    TEST_ASSERT_EQUAL_INT(1310, actuator1->intensity);
-    TEST_ASSERT_EQUAL_INT(1965, actuator2->intensity);
-}
-
 int process(void)
 {
     UNITY_BEGIN();
@@ -340,9 +220,9 @@ int process(void)
     RUN_TEST(test_layout_tactsuitx16);
     RUN_TEST(test_layout_tactsuitx40);
     RUN_TEST(test_layout_tactal);
-    RUN_TEST(test_layout_tactosy2);
-    RUN_TEST(test_layout_tactosyh);
-    RUN_TEST(test_layout_tactosyf);
+    // RUN_TEST(test_layout_tactosy2);
+    // RUN_TEST(test_layout_tactosyh);
+    // RUN_TEST(test_layout_tactosyf);
 
     return UNITY_END();
 }

From 164f0edd071189f1acbab8b472ea4774894efa0f Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Sat, 12 Aug 2023 13:54:05 +0400
Subject: [PATCH 03/82] refactor(bHaptics): update firmwares after refactoring

---
 firmware/firmware.cpp                     |  2 +-
 firmware/mode_configs/bhaptics/tactal.cpp | 16 +++++++-----
 firmware/senseshift.cpp                   | 32 ++++++++++++-----------
 include/senseshift.h                      | 30 +++++++++++----------
 lib/arduino/components/serial_plotter.cpp | 24 ++++++++++-------
 lib/arduino/components/serial_plotter.hpp |  6 ++---
 lib/haptics/haptic_body.cpp               |  1 +
 lib/haptics/haptic_body.hpp               |  6 +++++
 8 files changed, 68 insertions(+), 49 deletions(-)

diff --git a/firmware/firmware.cpp b/firmware/firmware.cpp
index 28aeed08..ae0f0668 100644
--- a/firmware/firmware.cpp
+++ b/firmware/firmware.cpp
@@ -19,7 +19,7 @@ extern void loopMode();
 
 #if defined(ARDUINO)
 
-SenseShift App;
+SenseShift::SenseShift App;
 
 void setup()
 {
diff --git a/firmware/mode_configs/bhaptics/tactal.cpp b/firmware/mode_configs/bhaptics/tactal.cpp
index 25913016..f4430848 100644
--- a/firmware/mode_configs/bhaptics/tactal.cpp
+++ b/firmware/mode_configs/bhaptics/tactal.cpp
@@ -15,13 +15,15 @@
 #endif
 
 using namespace OH;
+using namespace SenseShift;
+using namespace SenseShift::Body::Haptics;
 using namespace BH;
 
-extern SenseShift App;
-SenseShift* app = &App;
+extern SenseShift::SenseShift App;
+SenseShift::SenseShift* app = &App;
 
 static const size_t bhLayoutSize = BH_LAYOUT_TACTAL_SIZE;
-static const oh_output_point_t* bhLayout[bhLayoutSize] = BH_LAYOUT_TACTAL;
+static const oh_output_point_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTAL;
 
 void setupMode()
 {
@@ -32,8 +34,8 @@ void setupMode()
       // clang-format on
     });
 
-    auto* face = new HapticPlane_Closest(faceOutputs);
-    app->getHapticBody()->addComponent(OUTPUT_PATH_ACCESSORY, face);
+    auto* face = new VibroPlane_Closest(faceOutputs);
+    app->getHapticBody()->addTarget(Target::Accessory, face);
 
     app->getHapticBody()->setup();
 
@@ -46,7 +48,9 @@ void setupMode()
     auto* bhBleConnection = new ConnectionBHBLE(
       config,
       [](std::string& value) -> void {
-          plainOutputTransformer(app->getHapticBody(), value, bhLayout, bhLayoutSize, OUTPUT_PATH_ACCESSORY);
+        char buf[6];
+        strncpy(buf, value.c_str(), 6);
+        plainOutputTransformer<6>(app->getHapticBody(), buf, bhLayout, Target::Accessory);
       },
       app
     );
diff --git a/firmware/senseshift.cpp b/firmware/senseshift.cpp
index 683ac376..f54fda9a 100644
--- a/firmware/senseshift.cpp
+++ b/firmware/senseshift.cpp
@@ -8,23 +8,25 @@
 
 #include <logging.hpp>
 
-SenseShift::SenseShift()
-{
-    this->pHapticBody = new OH::HapticBody();
-}
+namespace SenseShift {
+    SenseShift::SenseShift()
+    {
+        this->pHapticBody = new Body::Haptics::HapticBody();
+    }
 
-void SenseShift::postEvent(const OH::IEvent* event)
-{
-    log_i("Event dispatched at %u: %s (%p)", millis(), event->eventName.c_str(), event);
+    void SenseShift::postEvent(const OH::IEvent* event)
+    {
+        log_i("Event dispatched at %u: %s (%p)", millis(), event->eventName.c_str(), event);
 
-    for (auto* listener : this->eventListeners) {
-        listener->handleEvent(event);
-    }
+        for (auto* listener : this->eventListeners) {
+            listener->handleEvent(event);
+        }
 
-    delete event;
-}
+        delete event;
+    }
 
-void SenseShift::addEventListener(const OH::IEventListener* listener)
-{
-    this->eventListeners.push_back(listener);
+    void SenseShift::addEventListener(const OH::IEventListener* listener)
+    {
+        this->eventListeners.push_back(listener);
+    }
 }
diff --git a/include/senseshift.h b/include/senseshift.h
index 1251a9fd..f4346148 100644
--- a/include/senseshift.h
+++ b/include/senseshift.h
@@ -12,23 +12,25 @@
 
 #include <vector>
 
-class SenseShift final : public OH::IEventDispatcher {
-  private:
-    std::vector<const OH::IEventListener*> eventListeners{};
-    OH::HapticBody* pHapticBody;
+namespace SenseShift {
+    class SenseShift final : public OH::IEventDispatcher {
+    private:
+        std::vector<const OH::IEventListener*> eventListeners{};
+        Body::Haptics::HapticBody* pHapticBody;
 
 #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
-    OH::BatterySensor* battery;
+        OH::BatterySensor* battery;
 #endif
 
-  public:
-    SenseShift();
+    public:
+        SenseShift();
 
-    OH::HapticBody* getHapticBody()
-    {
-        return this->pHapticBody;
-    };
+        Body::Haptics::HapticBody* getHapticBody()
+        {
+            return this->pHapticBody;
+        };
 
-    void postEvent(const OH::IEvent* event) override;
-    void addEventListener(const OH::IEventListener* listener) override;
-};
+        void postEvent(const OH::IEvent* event) override;
+        void addEventListener(const OH::IEventListener* listener) override;
+    };
+}
diff --git a/lib/arduino/components/serial_plotter.cpp b/lib/arduino/components/serial_plotter.cpp
index c3320721..21a1ffb1 100644
--- a/lib/arduino/components/serial_plotter.cpp
+++ b/lib/arduino/components/serial_plotter.cpp
@@ -1,19 +1,23 @@
 #include "components/serial_plotter.hpp"
 
+struct PlaneVisitor {
+    const SenseShift::Body::Haptics::Target_t target;
+    HardwareSerial* serial;
+
+    void operator()(const SenseShift::Body::Haptics::VibroPlane* plane) const
+    {
+        for (const auto& [position, state] : *(plane->getActuatorStates())) {
+            this->serial->printf("Output[%u][%ux%u]:%u, ", this->target, position.x, position.y, state.intensity);
+        }
+    }
+};
+
 template<typename _Tp>
 void OH::SerialPlotter_OutputStates<_Tp>::run()
 {
     while (true) {
-        for (auto& _c : *output->getComponents()) {
-            oh_output_path_t path = _c.first;
-            OH::HapticPlane* component = _c.second;
-
-            for (auto& _s : *component->getActuatorStates()) {
-                oh_output_point_t point = _s.first;
-                oh_output_state_t state = _s.second;
-
-                this->serial->printf("Output[%u][%ux%u]:%u, ", path, point.x, point.y, state.intensity);
-            }
+        for (const auto& [target, plane] : *output->getTargets()) {
+            std::visit(PlaneVisitor{target, this->serial}, plane);
         }
         this->serial->println();
 
diff --git a/lib/arduino/components/serial_plotter.hpp b/lib/arduino/components/serial_plotter.hpp
index 70ab445e..e0a9be8a 100644
--- a/lib/arduino/components/serial_plotter.hpp
+++ b/lib/arduino/components/serial_plotter.hpp
@@ -21,7 +21,7 @@ namespace OH {
 
       private:
         _Tp* serial;
-        HapticBody* output;
+        SenseShift::Body::Haptics::HapticBody* output;
         uint32_t sampleRate;
 
         void setup(void){};
@@ -30,12 +30,12 @@ namespace OH {
       public:
         SerialPlotter_OutputStates(
           _Tp& serial,
-          HapticBody* output,
+          SenseShift::Body::Haptics::HapticBody* output,
           uint32_t sampleRate,
           TaskConfig taskConfig = { "Serial Plotter", 2048, 1, tskNO_AFFINITY }
         ) :
           Task<SerialPlotter_OutputStates<_Tp>>(taskConfig), serial(&serial), output(output), sampleRate(sampleRate){};
-        SerialPlotter_OutputStates(_Tp& serial, HapticBody* output) : SerialPlotter_OutputStates(serial, output, 100){};
+        SerialPlotter_OutputStates(_Tp& serial, SenseShift::Body::Haptics::HapticBody* output) : SerialPlotter_OutputStates(serial, output, 100){};
 
         void begin() override
         {
diff --git a/lib/haptics/haptic_body.cpp b/lib/haptics/haptic_body.cpp
index 3718ed41..c90ce2f1 100644
--- a/lib/haptics/haptic_body.cpp
+++ b/lib/haptics/haptic_body.cpp
@@ -28,5 +28,6 @@ namespace SenseShift::Body::Haptics {
     void HapticBody::addTarget(const Target_t target, VibroPlane* plane)
     {
         this->vibroTargets[target] = plane;
+        this->allTargets.insert({ target, plane });
     }
 } // namespace SenseShift::Body::Haptics
diff --git a/lib/haptics/haptic_body.hpp b/lib/haptics/haptic_body.hpp
index 33252535..088bad0e 100644
--- a/lib/haptics/haptic_body.hpp
+++ b/lib/haptics/haptic_body.hpp
@@ -11,6 +11,8 @@
 namespace SenseShift::Body::Haptics {
     class HapticBody {
       public:
+        typedef std::variant<VibroPlane*> AuctiativePlane_t;
+        typedef std::multimap<Target_t, AuctiativePlane_t> PlaneTargetMap_t;
         typedef std::map<Target_t, VibroPlane*> VibroTargetMap_t;
 
         HapticBody(){};
@@ -21,7 +23,11 @@ namespace SenseShift::Body::Haptics {
 
         void addTarget(const Target_t, VibroPlane* plane);
 
+        const PlaneTargetMap_t* getTargets() const { return &allTargets; }
+
       private:
+        PlaneTargetMap_t allTargets{};
         VibroTargetMap_t vibroTargets{};
+
     };
 } // namespace SenseShift::Body::Haptics

From 6f3b63f124a43dbf2bd3d9a478f38a58251593a2 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Wed, 16 Aug 2023 21:08:22 +0400
Subject: [PATCH 04/82] refactor(bHaptics): add Decoder class

---
 firmware/mode_configs/bhaptics/tactal.cpp     |   8 +-
 lib/bhaptics/bh_encoding.hpp                  | 202 ++++++++++++++++++
 lib/bhaptics/bh_utils.cpp                     | 107 ----------
 lib/bhaptics/bh_utils.hpp                     |  45 ----
 lib/haptics/haptic_body.cpp                   |   4 +-
 lib/haptics/haptic_plane.cpp                  |   4 +-
 lib/haptics/haptic_plane.hpp                  |   6 +-
 lib/haptics/haptics_interface.hpp             |  16 +-
 .../main.cpp                                  |  19 +-
 test/test_haptics_body/main.cpp               |   8 +-
 10 files changed, 234 insertions(+), 185 deletions(-)
 create mode 100644 lib/bhaptics/bh_encoding.hpp
 delete mode 100644 lib/bhaptics/bh_utils.cpp
 delete mode 100644 lib/bhaptics/bh_utils.hpp
 rename test/{test_bhaptics => test_bhaptics_encoding}/main.cpp (93%)

diff --git a/firmware/mode_configs/bhaptics/tactal.cpp b/firmware/mode_configs/bhaptics/tactal.cpp
index f4430848..12bb9063 100644
--- a/firmware/mode_configs/bhaptics/tactal.cpp
+++ b/firmware/mode_configs/bhaptics/tactal.cpp
@@ -6,7 +6,7 @@
 
 #include "senseshift.h"
 
-#include <bh_utils.hpp>
+#include <bh_encoding.hpp>
 #include <connection_bhble.hpp>
 #include <output_writers/pwm.hpp>
 
@@ -35,7 +35,7 @@ void setupMode()
     });
 
     auto* face = new VibroPlane_Closest(faceOutputs);
-    app->getHapticBody()->addTarget(Target::Accessory, face);
+    app->getHapticBody()->addTarget(Target::FaceFront, face);
 
     app->getHapticBody()->setup();
 
@@ -48,9 +48,7 @@ void setupMode()
     auto* bhBleConnection = new ConnectionBHBLE(
       config,
       [](std::string& value) -> void {
-        char buf[6];
-        strncpy(buf, value.c_str(), 6);
-        plainOutputTransformer<6>(app->getHapticBody(), buf, bhLayout, Target::Accessory);
+        Decoder::applyPlain<6>(app->getHapticBody(), value, bhLayout, Effect::Vibro, Target::FaceFront);
       },
       app
     );
diff --git a/lib/bhaptics/bh_encoding.hpp b/lib/bhaptics/bh_encoding.hpp
new file mode 100644
index 00000000..4a4c7508
--- /dev/null
+++ b/lib/bhaptics/bh_encoding.hpp
@@ -0,0 +1,202 @@
+#pragma once
+
+#include <haptic_body.hpp>
+#include <cstring>
+
+namespace BH
+{
+    class Decoder
+    {
+      public:
+        typedef SenseShift::Body::Haptics::VibroEffectData_t VibroEffectData_t;
+        typedef SenseShift::Body::Haptics::EffectData_t EffectData_t;
+        typedef SenseShift::Body::Haptics::Effect_t Effect_t;
+        typedef SenseShift::Body::Haptics::Target_t Target_t;
+        typedef SenseShift::Body::Haptics::Position_t Position_t;
+        typedef std::tuple<Effect_t, Target_t, Position_t> OutputLayout_t;
+
+        static const size_t VEST_LAYOUT_SIZE = 40;
+        static const size_t VEST_PAYLOAD_SIZE = 20;
+
+        /**
+         * Apply plain-encoded data to the output.
+         */
+        template<size_t N>
+        static void applyPlain(
+            SenseShift::Body::Haptics::HapticBody* output,
+            const uint8_t (&value)[N],
+            const Position_t (&layout)[N],
+            const Effect_t effect,
+            const Target_t target
+        ) {
+            for (size_t i = 0; i < N; i++) {
+                auto position = layout[i];
+                uint8_t byte = value[i];
+
+                output->effect({
+                    .effect = effect,
+                    .target = target,
+                    .position = position,
+                    .data = effectDataFromByte(effect, byte),
+                });
+            }
+        }
+
+        template<size_t N>
+        static void applyPlain(
+            SenseShift::Body::Haptics::HapticBody* output,
+            std::string& value,
+            const Position_t (&layout)[N],
+            const Effect_t effect,
+            const Target_t target
+        ) {
+            std::uint8_t buf[6];
+            std::size_t copyLength = std::min(value.size(), sizeof(buf));
+            std::memcpy(buf, value.c_str(), copyLength);
+
+            applyPlain(output, buf, layout, effect, target);
+        }
+
+        /**
+         * Apply vest-encoded data to the output.
+         */
+        static void applyVest(
+            SenseShift::Body::Haptics::HapticBody* output,
+            const uint8_t (&value)[VEST_PAYLOAD_SIZE],
+            const Position_t (&layout)[VEST_LAYOUT_SIZE]
+        ) {
+            for (size_t i = 0; i < VEST_PAYLOAD_SIZE; i++) {
+                uint8_t byte = value[i];
+                uint actIndex = i * 2;
+                const auto target = (actIndex < 10 || actIndex >= 30) ? Target_t::ChestFront : Target_t::ChestBack;
+
+                output->effect({
+                    .effect = Effect_t::Vibro,
+                    .target = target,
+                    .position = layout[actIndex],
+                    .data = effectDataFromByte(
+                        Effect_t::Vibro,
+                        ((byte >> 4) & 0xf),
+                        15
+                    ),
+                });
+                output->effect({
+                    .effect = Effect_t::Vibro,
+                    .target = target,
+                    .position = layout[actIndex + 1],
+                    .data = effectDataFromByte(
+                        Effect_t::Vibro,
+                        (byte & 0xf),
+                        15
+                    ),
+                });
+            }
+        }
+
+        static void applyVest(
+            SenseShift::Body::Haptics::HapticBody* output,
+            std::string& value,
+            const Position_t (&layout)[VEST_LAYOUT_SIZE]
+        ) {
+            std::uint8_t buf[VEST_PAYLOAD_SIZE];
+            std::size_t copyLength = std::min(value.size(), sizeof(buf));
+            std::memcpy(buf, value.c_str(), copyLength);
+
+            applyVest(output, buf, layout);
+        }
+
+        /**
+         * Apply grouped vest-encoded data to the output.
+         */
+        template <size_t N>
+        static void applyVestGrouped(
+            SenseShift::Body::Haptics::HapticBody* output,
+            const uint8_t (&value)[VEST_PAYLOAD_SIZE],
+            const Position_t (&layout)[VEST_LAYOUT_SIZE],
+            const uint8_t (&layoutGroups)[N]
+        ) {
+            uint8_t result[VEST_LAYOUT_SIZE];
+
+            // Unpack values
+            for (auto i = 0; i < VEST_PAYLOAD_SIZE; i++) {
+                uint8_t byte = value[i];
+                uint actIndex = i * 2;
+
+                result[actIndex] = (byte >> 4) & 0xf;
+                result[actIndex + 1] = (byte & 0xf);
+            }
+
+            // Assign max value into each group
+            for (auto i = 0; i < N; i++) {
+                auto groupIndex = layoutGroups[i];
+
+                if (groupIndex % 10 >= 4) {
+                    // Top 3 rows of x40
+                    auto maxValue = std::max({ result[groupIndex], result[groupIndex + 2], result[groupIndex + 4] });
+
+                    result[groupIndex] = maxValue;
+                    result[groupIndex + 2] = maxValue;
+                    result[groupIndex + 4] = maxValue;
+                } else {
+                    // Bottom 2 rows of x40
+                    auto maxValue = std::max({ result[groupIndex], result[groupIndex + 2] });
+
+                    result[groupIndex] = maxValue;
+                    result[groupIndex + 2] = maxValue;
+                }
+            }
+
+            for (uint8_t i = 0; i < VEST_LAYOUT_SIZE; i++) {
+                // take only meaningful values
+                if (!OH::contains(layoutGroups, VEST_LAYOUT_SIZE, i)) {
+                    continue;
+                }
+
+                const auto target = (i < 10 || i >= 30) ? Target_t::ChestFront : Target_t::ChestBack;
+
+                output->effect({
+                    .effect = Effect_t::Vibro,
+                    .target = target,
+                    .position = layout[i],
+                    .data = effectDataFromByte(
+                        Effect_t::Vibro,
+                        result[i],
+                        15
+                    ),
+                });
+            }
+        }
+
+        template <size_t N>
+        static void applyVestGrouped(
+            SenseShift::Body::Haptics::HapticBody* output,
+            std::string& value,
+            const Position_t (&layout)[VEST_LAYOUT_SIZE],
+            const uint8_t (&layoutGroups)[N]
+        ) {
+            std::uint8_t buf[VEST_PAYLOAD_SIZE];
+            std::size_t copyLength = std::min(value.size(), sizeof(buf));
+            std::memcpy(buf, value.c_str(), copyLength);
+
+            applyVestGrouped(output, buf, layout, layoutGroups);
+        }
+
+      private:
+        static const EffectData_t effectDataFromByte(
+            const Effect_t& effect,
+            const uint8_t byte,
+            const uint8_t maxValue = 100
+        ) {
+            switch (effect) {
+                case Effect_t::Vibro:
+                    return VibroEffectData_t(
+                        OH::simpleMap<VibroEffectData_t::Intensity_t>(
+                            byte, maxValue, VibroEffectData_t::INTENSITY_MAX
+                        )
+                    );
+                default:
+                    throw std::runtime_error("Unknown effect");
+            }
+        }
+    };
+} // namespace BH
diff --git a/lib/bhaptics/bh_utils.cpp b/lib/bhaptics/bh_utils.cpp
deleted file mode 100644
index d1a4654a..00000000
--- a/lib/bhaptics/bh_utils.cpp
+++ /dev/null
@@ -1,107 +0,0 @@
-#include "bh_utils.hpp"
-
-#include <utility.hpp>
-
-namespace BH {
-    template<size_t N = 20>
-    void vestOutputTransformer(
-      SenseShift::Body::Haptics::HapticBody* output, const char (&value)[N], const oh_output_point_t (&layout)[N * 2]
-    )
-    {
-        for (size_t i = 0; i < N; i++) {
-            uint8_t byte = value[i];
-            uint actIndex = i * 2;
-            const auto target = (actIndex < 10 || actIndex >= 30) ? SenseShift::Body::Haptics::Target::ChestFront
-                                                                  : SenseShift::Body::Haptics::Target::ChestBack;
-
-            output->effect({
-              .effect = SenseShift::Body::Haptics::Effect::Vibro,
-              .target = target,
-              .position = layout[actIndex],
-              .data = SenseShift::Body::Haptics::VibroEffect(
-                OH::simpleMap<SenseShift::Body::Haptics::VibroEffect::Intensity_t>(
-                  ((byte >> 4) & 0xf),
-                  15,
-                  SenseShift::Body::Haptics::VibroEffect::INTENSITY_MAX
-                )
-              ),
-            });
-            output->effect({
-              .effect = SenseShift::Body::Haptics::Effect::Vibro,
-              .target = target,
-              .position = layout[actIndex + 1],
-              .data = SenseShift::Body::Haptics::VibroEffect(
-                OH::simpleMap<SenseShift::Body::Haptics::VibroEffect::Intensity_t>(
-                  (byte & 0xf),
-                  15,
-                  SenseShift::Body::Haptics::VibroEffect::INTENSITY_MAX
-                )
-              ),
-            });
-        }
-    }
-
-    template void vestOutputTransformer<20>(
-      SenseShift::Body::Haptics::HapticBody* output, const char (&value)[20], const oh_output_point_t (&layout)[40]
-    );
-} // namespace BH
-
-void BH::vestX16OutputTransformer(
-  SenseShift::Body::Haptics::HapticBody* output,
-  const std::string& value,
-  const oh_output_point_t* layout,
-  const size_t layoutSize,
-  const uint8_t layoutGroups[],
-  const size_t layoutGroupsSize
-)
-{
-    uint8_t result[layoutSize];
-
-    // Unpack values
-    for (auto i = 0; i < layoutSize / 2; i++) {
-        uint8_t byte = value[i];
-        uint actIndex = i * 2;
-
-        result[actIndex] = (byte >> 4) & 0xf;
-        result[actIndex + 1] = (byte & 0xf);
-    }
-
-    // Assign max value into each group
-    for (auto i = 0; i < layoutGroupsSize; i++) {
-        auto groupIndex = layoutGroups[i];
-
-        if (groupIndex % 10 >= 4) {
-            // Top 3 rows of x40
-            auto maxValue = std::max({ result[groupIndex], result[groupIndex + 2], result[groupIndex + 4] });
-
-            result[groupIndex] = maxValue;
-            result[groupIndex + 2] = maxValue;
-            result[groupIndex + 4] = maxValue;
-        } else {
-            // Bottom 2 rows of x40
-            auto maxValue = std::max({ result[groupIndex], result[groupIndex + 2] });
-
-            result[groupIndex] = maxValue;
-            result[groupIndex + 2] = maxValue;
-        }
-    }
-
-    for (uint8_t i = 0; i < static_cast<uint8_t>(layoutSize); i++) {
-        // take only meaningful values
-        if (!OH::contains(layoutGroups, layoutSize, i)) {
-            continue;
-        }
-
-        const auto target = (i < 10 || i >= 30) ? SenseShift::Body::Haptics::Target::ChestFront
-                                                : SenseShift::Body::Haptics::Target::ChestBack;
-
-        output->effect({
-          .effect = SenseShift::Body::Haptics::Effect::Vibro,
-          .target = target,
-          .position = layout[i],
-          .data = SenseShift::Body::Haptics::VibroEffect(
-            static_cast<oh_output_intensity_t>(OH::accurateMap<long>(result[i], 0, 15, 0, OH_OUTPUT_INTENSITY_MAX))
-          ),
-        });
-    }
-}
diff --git a/lib/bhaptics/bh_utils.hpp b/lib/bhaptics/bh_utils.hpp
deleted file mode 100644
index 2825aea7..00000000
--- a/lib/bhaptics/bh_utils.hpp
+++ /dev/null
@@ -1,45 +0,0 @@
-#pragma once
-
-#include <haptic_body.hpp>
-#include <string>
-
-namespace BH {
-    template<size_t N>
-    void plainOutputTransformer(
-      SenseShift::Body::Haptics::HapticBody* output,
-      const char (&value)[N],
-      const oh_output_point_t (&layout)[N],
-      const SenseShift::Body::Haptics::Target_t target
-    )
-    {
-        for (size_t i = 0; i < N; i++) {
-            uint8_t byte = value[i];
-
-            output->effect({
-              .effect = SenseShift::Body::Haptics::Effect::Vibro,
-              .target = target,
-              .position = layout[i],
-              .data = SenseShift::Body::Haptics::VibroEffect(
-                OH::simpleMap<SenseShift::Body::Haptics::VibroEffect::Intensity_t>(
-                  byte,
-                  100,
-                  SenseShift::Body::Haptics::VibroEffect::INTENSITY_MAX
-                )
-              ),
-            });
-        }
-    }
-
-    template<size_t N = 20>
-    void vestOutputTransformer(
-      SenseShift::Body::Haptics::HapticBody* output, const char (&value)[N], const oh_output_point_t (&layout)[N * 2]
-    );
-    void vestX16OutputTransformer(
-      SenseShift::Body::Haptics::HapticBody* output,
-      const std::string& value,
-      const oh_output_point_t* layout,
-      const size_t layoutSize,
-      const uint8_t layoutGroups[],
-      const size_t layoutGroupsSize
-    );
-} // namespace BH
diff --git a/lib/haptics/haptic_body.cpp b/lib/haptics/haptic_body.cpp
index c90ce2f1..fd4cf2cb 100644
--- a/lib/haptics/haptic_body.cpp
+++ b/lib/haptics/haptic_body.cpp
@@ -12,14 +12,14 @@ namespace SenseShift::Body::Haptics {
 
     void HapticBody::effect(const EffectRequest_t& effect)
     {
-        if (effect.effect == Effect::Vibro && std::holds_alternative<VibroEffect_t>(effect.data)) {
+        if (effect.effect == Effect::Vibro && std::holds_alternative<VibroEffectData_t>(effect.data)) {
             auto it = this->vibroTargets.find(effect.target);
             if (it == this->vibroTargets.end()) {
                 log_w("No target found for effect: %d", effect.target);
                 return;
             }
 
-            it->second->effect(effect.position, std::get<VibroEffect_t>(effect.data));
+            it->second->effect(effect.position, std::get<VibroEffectData_t>(effect.data));
         } else {
             log_w("Non-supported effect type: %d", effect.effect);
         }
diff --git a/lib/haptics/haptic_plane.cpp b/lib/haptics/haptic_plane.cpp
index ddbf0e7b..6c10861f 100644
--- a/lib/haptics/haptic_plane.cpp
+++ b/lib/haptics/haptic_plane.cpp
@@ -75,6 +75,6 @@ namespace SenseShift::Body::Haptics {
         return nearest->second;
     }
 
-    template class ActuativePlane<VibroEffect_t>;
-    template class ActuativePlane_Closest<VibroEffect_t>;
+    template class ActuativePlane<VibroEffectData_t>;
+    template class ActuativePlane_Closest<VibroEffectData_t>;
 } // namespace SenseShift::Body::Haptics
diff --git a/lib/haptics/haptic_plane.hpp b/lib/haptics/haptic_plane.hpp
index 8f565ef6..66c47818 100644
--- a/lib/haptics/haptic_plane.hpp
+++ b/lib/haptics/haptic_plane.hpp
@@ -21,7 +21,7 @@ namespace SenseShift::Body::Haptics {
      */
     template<typename _Tp>
     class ActuativePlane {
-        static_assert(std::is_same<_Tp, VibroEffect_t>());
+        static_assert(std::is_same<_Tp, VibroEffectData_t>());
 
       public:
         typedef _Tp Value_t;
@@ -59,7 +59,7 @@ namespace SenseShift::Body::Haptics {
         void setActuators(const ActuatorMap_t&);
     };
 
-    typedef ActuativePlane<VibroEffect_t> VibroPlane;
+    typedef ActuativePlane<VibroEffectData_t> VibroPlane;
 
     /**
      * Output plane, finds the closest actuator for the given point.
@@ -81,7 +81,7 @@ namespace SenseShift::Body::Haptics {
         [[nodiscard]] const Position_t& findClosestPoint(const PositionSet_t&, const Position_t&) const;
     };
 
-    typedef ActuativePlane_Closest<VibroEffect_t> VibroPlane_Closest;
+    typedef ActuativePlane_Closest<VibroEffectData_t> VibroPlane_Closest;
 
     // TODO: configurable margin
     class PlaneMapper_Margin {
diff --git a/lib/haptics/haptics_interface.hpp b/lib/haptics/haptics_interface.hpp
index f6734c76..26bd2b86 100644
--- a/lib/haptics/haptics_interface.hpp
+++ b/lib/haptics/haptics_interface.hpp
@@ -24,6 +24,8 @@ namespace SenseShift::Body::Haptics {
         // Legacy backword compatibility
         Accessory [[deprecated]] = 0x02,
 
+        FaceFront = 0x03,
+
         // TODO: arms, legs, etc.
     } Target_t;
 
@@ -31,28 +33,28 @@ namespace SenseShift::Body::Haptics {
     typedef OH::Point2<Coordinate_t> Position_t;
 
     // Vibration intensity.
-    typedef struct VibroEffect {
+    typedef struct VibroEffectData {
         using Intensity_t = uint16_t;
         inline static const Intensity_t INTENSITY_MIN = 0;
         inline static const Intensity_t INTENSITY_MAX = 4095;
 
         Intensity_t intensity = 0;
 
-        inline constexpr VibroEffect() = default;
-        inline constexpr VibroEffect(Intensity_t intensity) : intensity(intensity) {}
-        inline constexpr VibroEffect(const VibroEffect& other) = default;
+        inline constexpr VibroEffectData() = default;
+        inline constexpr VibroEffectData(const Intensity_t intensity) : intensity(intensity) {}
+        inline constexpr VibroEffectData(const VibroEffectData& other) = default;
 
         inline constexpr operator uint16_t() const
         {
             return intensity;
         }
-    } VibroEffect_t;
+    } VibroEffectData_t;
 
     // TODO: thermal, etc.
 
-    typedef std::variant<VibroEffect_t
+    typedef std::variant<VibroEffectData_t
                          // TODO: thermal, etc.
-                         // ThermalEffect_t
+                         // ThermalEffectData_t
                          >
       EffectData_t;
 
diff --git a/test/test_bhaptics/main.cpp b/test/test_bhaptics_encoding/main.cpp
similarity index 93%
rename from test/test_bhaptics/main.cpp
rename to test/test_bhaptics_encoding/main.cpp
index 1cc22a18..e73993c4 100644
--- a/test/test_bhaptics/main.cpp
+++ b/test/test_bhaptics_encoding/main.cpp
@@ -1,5 +1,5 @@
 #include <bh_types.hpp>
-#include <bh_utils.hpp>
+#include <bh_encoding.hpp>
 
 #include <unity.h>
 
@@ -66,13 +66,12 @@ void test_layout_tactsuitx16(void)
     body->addTarget(Target::ChestBack, backPlane);
     // body->setup();
 
-    uint8_t values[] = {
+    const uint8_t values[] = {
         0x01, 0x00, 0x23, 0x00, 0x00, 0x45, 0x00, 0x67, 0x00, 0x00,
         0x89, 0x00, 0xab, 0x00, 0x00, 0xcd, 0x00, 0xef, 0x00, 0x00,
     };
-    std::string value = std::string((char*) values, sizeof(values));
 
-    vestX16OutputTransformer(body, value, bhLayout, bhLayoutSize, layoutGroups, layoutGroupsSize);
+    Decoder::applyVestGrouped(body, values, bhLayout, layoutGroups);
     TEST_ASSERT_EQUAL_INT(0, actuator0->intensity);
     TEST_ASSERT_EQUAL_INT(273, actuator1->intensity);
     TEST_ASSERT_EQUAL_INT(3276, actuator2->intensity);
@@ -124,11 +123,11 @@ void test_layout_tactsuitx40(void)
     body->addTarget(Target::ChestBack, backPlane);
     // body->setup();
 
-    vestOutputTransformer(
+    Decoder::applyVest(
       body,
       {
-        0x01, 0x23, 0x45, 0x67, (char) 0x89, (char) 0xab, (char) 0xcd, (char) 0xef, 0x00, 0x00,
-        0x00, 0x00, 0x00, 0x00, 0x00,        0x00,        0x00,        0x00,        0x00, 0x00,
+        0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
       },
       bhLayout
     );
@@ -194,9 +193,9 @@ void test_layout_tactal(void)
     });
     auto plane = new VibroPlane(outputs);
 
-    body->addTarget(Target::Accessory, plane);
+    body->addTarget(Target::FaceFront, plane);
 
-    plainOutputTransformer(body, { 0x64, 0x00, 0x00, 0x00, 0x00, 0x00 }, bhLayout, Target::Accessory);
+    Decoder::applyPlain(body, { 0x64, 0x00, 0x00, 0x00, 0x00, 0x00 }, bhLayout, Effect::Vibro, Target::FaceFront);
     TEST_ASSERT_EQUAL_INT(4095, actuator0->intensity);
     TEST_ASSERT_EQUAL_INT(0, actuator1->intensity);
     TEST_ASSERT_EQUAL_INT(0, actuator2->intensity);
@@ -204,7 +203,7 @@ void test_layout_tactal(void)
     TEST_ASSERT_EQUAL_INT(0, actuator4->intensity);
     TEST_ASSERT_EQUAL_INT(0, actuator5->intensity);
 
-    plainOutputTransformer(body, { 0x10, 0x20, 0x30, 0x40, 0x50, 0x60 }, bhLayout, Target::Accessory);
+    Decoder::applyPlain(body, { 0x10, 0x20, 0x30, 0x40, 0x50, 0x60 }, bhLayout, Effect::Vibro, Target::FaceFront);
     TEST_ASSERT_EQUAL_INT(655, actuator0->intensity);
     TEST_ASSERT_EQUAL_INT(1310, actuator1->intensity);
     TEST_ASSERT_EQUAL_INT(1965, actuator2->intensity);
diff --git a/test/test_haptics_body/main.cpp b/test/test_haptics_body/main.cpp
index d16ed628..f5ceb76f 100644
--- a/test/test_haptics_body/main.cpp
+++ b/test/test_haptics_body/main.cpp
@@ -59,25 +59,25 @@ void test_it_handles_effect__vibro(void)
       .effect = Effect::Vibro,
       .target = Target::ChestFront,
       .position = { 0, 0 },
-      .data = (VibroEffect_t) 64,
+      .data = (VibroEffectData_t) 64,
     });
     body->effect({
       .effect = Effect::Vibro,
       .target = Target::ChestFront,
       .position = { 0, 1 },
-      .data = (VibroEffect_t) 128,
+      .data = (VibroEffectData_t) 128,
     });
     body->effect({
       .effect = Effect::Vibro,
       .target = Target::ChestFront,
       .position = { 1, 0 },
-      .data = (VibroEffect_t) 192,
+      .data = (VibroEffectData_t) 192,
     });
     body->effect({
       .effect = Effect::Vibro,
       .target = Target::ChestFront,
       .position = { 1, 1 },
-      .data = (VibroEffect_t) 255,
+      .data = (VibroEffectData_t) 255,
     });
 
     TEST_ASSERT_EQUAL(64, actuator1->intensity);

From 4a6c2a91f30f546a081f16227d59c0e1074131e3 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Wed, 16 Aug 2023 23:41:12 +0400
Subject: [PATCH 05/82] refactor(bHaptics): advanced TactGlove layout

---
 firmware/mode_configs/bhaptics/tactal.cpp    |   2 +-
 firmware/mode_configs/bhaptics/tactglove.cpp |  72 +++-----
 include/config/all.h                         |   1 -
 include/config/bluetooth.h                   |   2 +-
 include/config/output.h                      |   5 -
 ini/bhaptics.ini                             |   2 +
 lib/bhaptics/bh_constants.hpp                |  79 ++-------
 lib/bhaptics/bh_devices.hpp                  |  95 ++++++++++
 lib/bhaptics/bh_encoding.hpp                 | 176 +++++++++++--------
 lib/hands/hand_interface.hpp                 |  19 ++
 lib/haptics/haptics_interface.hpp            |  19 +-
 platformio.ini                               |   2 +-
 test/test_bhaptics_encoding/main.cpp         |  48 ++++-
 13 files changed, 317 insertions(+), 205 deletions(-)
 delete mode 100644 include/config/output.h
 create mode 100644 lib/bhaptics/bh_devices.hpp
 create mode 100644 lib/hands/hand_interface.hpp

diff --git a/firmware/mode_configs/bhaptics/tactal.cpp b/firmware/mode_configs/bhaptics/tactal.cpp
index 12bb9063..17982748 100644
--- a/firmware/mode_configs/bhaptics/tactal.cpp
+++ b/firmware/mode_configs/bhaptics/tactal.cpp
@@ -34,7 +34,7 @@ void setupMode()
       // clang-format on
     });
 
-    auto* face = new VibroPlane_Closest(faceOutputs);
+    auto* face = new VibroPlane(faceOutputs);
     app->getHapticBody()->addTarget(Target::FaceFront, face);
 
     app->getHapticBody()->setup();
diff --git a/firmware/mode_configs/bhaptics/tactglove.cpp b/firmware/mode_configs/bhaptics/tactglove.cpp
index 8466c328..cb4cbea8 100644
--- a/firmware/mode_configs/bhaptics/tactglove.cpp
+++ b/firmware/mode_configs/bhaptics/tactglove.cpp
@@ -8,7 +8,8 @@
 
 #include "senseshift.h"
 
-#include <bh_utils.hpp>
+#include <bh_encoding.hpp>
+#include <bh_devices.hpp>
 #include <connection_bhble.hpp>
 #include <output_writers/pwm.hpp>
 
@@ -16,59 +17,36 @@
 #include <battery/adc_naive.hpp>
 #endif
 
+#ifndef BH_LAYOUT
+#define BH_LAYOUT BH::TactGloveLeftLayout
+#endif
+
 using namespace OH;
+using namespace SenseShift;
+using namespace SenseShift::Body::Haptics;
 using namespace BH;
 
-extern SenseShift App;
-SenseShift* app = &App;
-
-#pragma region bHaptics_trash
-
-// TODO: all of this will need to be re-written to use the new output paths system, when time comes
-
-static const uint16_t _bh_size_x = 6;
-static const uint16_t _bh_size_y = 1;
-
-inline oh_output_point_t* make_point(oh_output_coord_t x, oh_output_coord_t y)
-{
-    return PlaneMapper_Margin::mapPoint(
-      x,
-      y,
-      (oh_output_coord_t) (_bh_size_x - 1),
-      (oh_output_coord_t) (_bh_size_y - 1)
-    );
-}
-
-static const uint16_t bhLayoutSize = _bh_size_x * _bh_size_y;
-static const oh_output_point_t* bhLayout[bhLayoutSize] = {
-    // clang-format off
-
-    // Thumb, Index, Middle, Ring, Pinky
-    make_point(0, 0), make_point(1, 0), make_point(2, 0), make_point(3, 0), make_point(4, 0),
-    // Wrist
-    make_point(5, 0)
+extern SenseShift::SenseShift App;
+SenseShift::SenseShift* app = &App;
 
-    // clang-format on
-};
-
-#pragma endregion bHaptics_trash
+const auto& bhLayout = BH_LAYOUT;
 
 void setupMode()
 {
     // Configure PWM pins to their positions on the glove
-    auto gloveOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
-      // clang-format off
-      {
-        // Thumb, Index, Middle, Ring, Pinky
-        new PWMOutputWriter(32), new PWMOutputWriter(33), new PWMOutputWriter(25), new PWMOutputWriter(26), new PWMOutputWriter(27),
-        // Wrist
-        new PWMOutputWriter(14)
-      },
-      // clang-format on
-    });
-
-    auto* glove = new HapticPlane_Closest(gloveOutputs);
-    app->getHapticBody()->addComponent(OUTPUT_PATH_ACCESSORY, glove);
+    auto* motorThumb = new PWMOutputWriter(33); // Thumb
+    auto* motorIndex = new PWMOutputWriter(32); // Index
+    auto* motorMiddle = new PWMOutputWriter(25); // Middle
+    auto* motorRing = new PWMOutputWriter(26); // Ring
+    auto* motorLittle = new PWMOutputWriter(27); // Little
+    auto* motorWrist = new PWMOutputWriter(14); // Wrist
+
+    app->getHapticBody()->addTarget(std::get<0>(bhLayout[0]), new VibroPlane({{ std::get<1>(bhLayout[0]), motorThumb }}));
+    app->getHapticBody()->addTarget(std::get<0>(bhLayout[1]), new VibroPlane({{ std::get<1>(bhLayout[1]), motorIndex }}));
+    app->getHapticBody()->addTarget(std::get<0>(bhLayout[2]), new VibroPlane({{ std::get<1>(bhLayout[2]), motorMiddle }}));
+    app->getHapticBody()->addTarget(std::get<0>(bhLayout[3]), new VibroPlane({{ std::get<1>(bhLayout[3]), motorRing }}));
+    app->getHapticBody()->addTarget(std::get<0>(bhLayout[4]), new VibroPlane({{ std::get<1>(bhLayout[4]), motorLittle }}));
+    app->getHapticBody()->addTarget(std::get<0>(bhLayout[5]), new VibroPlane({{ std::get<1>(bhLayout[5]), motorWrist }}));
 
     app->getHapticBody()->setup();
 
@@ -81,7 +59,7 @@ void setupMode()
     auto* bhBleConnection = new ConnectionBHBLE(
       config,
       [](std::string& value) -> void {
-          plainOutputTransformer(app->getHapticBody(), value, bhLayout, bhLayoutSize, OUTPUT_PATH_ACCESSORY);
+        Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect_t::Vibro);
       },
       app
     );
diff --git a/include/config/all.h b/include/config/all.h
index 1c74f18c..46c9d0b0 100644
--- a/include/config/all.h
+++ b/include/config/all.h
@@ -3,7 +3,6 @@
 #pragma once
 
 #include "config/battery.h"
-#include "config/output.h"
 #include "config/pwm.h"
 
 #include "config/bluetooth.h"
diff --git a/include/config/bluetooth.h b/include/config/bluetooth.h
index 46f31eb6..b12cff34 100644
--- a/include/config/bluetooth.h
+++ b/include/config/bluetooth.h
@@ -12,5 +12,5 @@
 #endif
 
 #ifndef BLUETOOTH_USE_NIMBLE
-#define BLUETOOTH_USE_NIMBLE false
+#define BLUETOOTH_USE_NIMBLE true
 #endif
diff --git a/include/config/output.h b/include/config/output.h
deleted file mode 100644
index 8184df26..00000000
--- a/include/config/output.h
+++ /dev/null
@@ -1,5 +0,0 @@
-#pragma once
-
-#define OUTPUT_PATH_CHEST_FRONT 0x00
-#define OUTPUT_PATH_CHEST_BACK 0x01
-#define OUTPUT_PATH_ACCESSORY 0x02
diff --git a/ini/bhaptics.ini b/ini/bhaptics.ini
index ee45ae1e..54c5aa1e 100644
--- a/ini/bhaptics.ini
+++ b/ini/bhaptics.ini
@@ -239,6 +239,7 @@ monitor_speed		= ${bhaptics.monitor_speed}
 
 build_flags 		= ${bhaptics.build_flags}
 	-D BH_DEVICE_TACTGLOVE
+	-D BH_LAYOUT=BH::TactGloveLeftLayout
 	-D BH_BLE_APPEARANCE=508
 	'-D BLUETOOTH_NAME="TactGlove (L"'
 	'-D BH_SERIAL_NUMBER={ 0xcd, 0x0b, 0x81, 0x45, 0x85, 0xf9, 0x2b, 0x6c, 0xed, 0x5b }'
@@ -257,6 +258,7 @@ monitor_speed		= ${bhaptics.monitor_speed}
 
 build_flags 		= ${bhaptics.build_flags}
 	-D BH_DEVICE_TACTGLOVE
+	-D BH_LAYOUT=BH::TactGloveRightLayout
 	-D BH_BLE_APPEARANCE=508
 	'-D BLUETOOTH_NAME="TactGlove (R"'
 	'-D BH_SERIAL_NUMBER={ 0x12, 0x0b, 0xae, 0xbf, 0xbc, 0x90, 0x3b, 0x0d, 0x84, 0xdd }'
diff --git a/lib/bhaptics/bh_constants.hpp b/lib/bhaptics/bh_constants.hpp
index b28f53e3..c430911a 100644
--- a/lib/bhaptics/bh_constants.hpp
+++ b/lib/bhaptics/bh_constants.hpp
@@ -8,8 +8,16 @@
 
 #define BH_SERIAL_NUMBER_LENGTH 10
 
+#ifndef BH_FIRMWARE_VERSION_MAJOR
+#define BH_FIRMWARE_VERSION_MAJOR ((uint8_t) UINT8_MAX)
+#endif
+
+#ifndef BH_FIRMWARE_VERSION_MINOR
+#define BH_FIRMWARE_VERSION_MINOR ((uint8_t) UINT8_MAX)
+#endif
+
 #ifndef BH_FIRMWARE_VERSION
-#define BH_FIRMWARE_VERSION (uint16_t) UINT16_MAX
+#define BH_FIRMWARE_VERSION (uint16_t)((BH_FIRMWARE_VERSION_MAJOR << 8) | BH_FIRMWARE_VERSION_MINOR)
 #endif
 
 #define NO_AUDIO_CABLE 0
@@ -93,73 +101,6 @@
 
 #pragma endregion BH_DEVICE_TACTSUITX16
 
-#pragma region BH_DEVICE_TACTSUITX40
-
-#define BH_LAYOUT_TACTSUITX40_SIZE_X 4
-#define BH_LAYOUT_TACTSUITX40_SIZE_Y 5
-#define BH_LAYOUT_TACTSUITX40_MAKE_POINT(x, y)                                  \
-    SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
-      x,                                                                        \
-      y,                                                                        \
-      (oh_output_coord_t) (BH_LAYOUT_TACTSUITX40_SIZE_X - 1),                   \
-      (oh_output_coord_t) (BH_LAYOUT_TACTSUITX40_SIZE_Y - 1)                    \
-    )
-
-// X * Y for front and back
-#define BH_LAYOUT_TACTSUITX40_SIZE 40
-// clang-format off
-#define BH_LAYOUT_TACTSUITX40 {                       \
-    /* Front, left part */                            \
-    /*  0 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 0),  \
-    /*  1 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 0),  \
-    /*  2 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 1),  \
-    /*  3 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 1),  \
-    /*  4 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 2),  \
-    /*  5 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 2),  \
-    /*  6 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 3),  \
-    /*  7 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 3),  \
-    /*  8 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 4),  \
-    /*  9 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 4),  \
-                                                      \
-    /* Back */                                        \
-    /* 11 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 0),  \
-    /* 11 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 0),  \
-    /* 12 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 1),  \
-    /* 13 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 1),  \
-    /* 14 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 2),  \
-    /* 15 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 2),  \
-    /* 16 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 3),  \
-    /* 17 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 3),  \
-    /* 18 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 4),  \
-    /* 19 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 4),  \
-                                                      \
-    /* 20 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 0),  \
-    /* 21 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 0),  \
-    /* 22 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 1),  \
-    /* 23 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 1),  \
-    /* 24 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 2),  \
-    /* 25 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 2),  \
-    /* 26 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 3),  \
-    /* 27 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 3),  \
-    /* 28 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 4),  \
-    /* 29 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 4),  \
-                                                      \
-    /* Front, again... Now right part */              \
-    /* 30 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 0),  \
-    /* 31 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 0),  \
-    /* 32 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 1),  \
-    /* 33 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 1),  \
-    /* 34 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 2),  \
-    /* 35 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 2),  \
-    /* 36 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 3),  \
-    /* 37 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 3),  \
-    /* 38 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 4),  \
-    /* 39 */ BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 4)   \
-}
-// clang-format on
-
-#pragma endregion BH_DEVICE_TACTSUITX40
-
 #pragma region BH_DEVICE_TACTAL
 
 #define BH_LAYOUT_TACTAL_SIZE_X 6
@@ -272,7 +213,7 @@
 
 // All below are weird choices of bHaptics engineers...
 // Why to use unconventional UUIDs, that are reserved for other purposes?
-// You have  an unlimited amount of other UUIDs
+// You have an unlimited amount of other UUIDs
 
 // Main service for communication
 #define BH_BLE_SERVICE_MOTOR_UUID BLEUUID("6e400001-b5a3-f393-e0a9-e50e24dcca9e") // Nordic UART Service
diff --git a/lib/bhaptics/bh_devices.hpp b/lib/bhaptics/bh_devices.hpp
new file mode 100644
index 00000000..dd131e20
--- /dev/null
+++ b/lib/bhaptics/bh_devices.hpp
@@ -0,0 +1,95 @@
+#pragma once
+
+#include <hand_interface.hpp>
+
+#pragma region TaxtSuit X16
+
+#define BH_LAYOUT_TACTSUITX40_SIZE_X 4
+#define BH_LAYOUT_TACTSUITX40_SIZE_Y 5
+#define BH_LAYOUT_TACTSUITX40_MAKE_POINT(x, y)                                  \
+    SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
+      x,                                                                        \
+      y,                                                                        \
+      (oh_output_coord_t) (BH_LAYOUT_TACTSUITX40_SIZE_X - 1),                   \
+      (oh_output_coord_t) (BH_LAYOUT_TACTSUITX40_SIZE_Y - 1)                    \
+    )
+
+// X * Y for front and back
+#define BH_LAYOUT_TACTSUITX40_SIZE 40
+// clang-format off
+#define BH_LAYOUT_TACTSUITX40 {                       \
+    /* Front, left part */                            \
+    /*  0 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 0) },  \
+    /*  1 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 0) },  \
+    /*  2 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 1) },  \
+    /*  3 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 1) },  \
+    /*  4 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 2) },  \
+    /*  5 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 2) },  \
+    /*  6 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 3) },  \
+    /*  7 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 3) },  \
+    /*  8 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 4) },  \
+    /*  9 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 4) },  \
+                                                      \
+    /* Back */                                        \
+    /* 11 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 0) },  \
+    /* 11 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 0) },  \
+    /* 12 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 1) },  \
+    /* 13 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 1) },  \
+    /* 14 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 2) },  \
+    /* 15 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 2) },  \
+    /* 16 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 3) },  \
+    /* 17 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 3) },  \
+    /* 18 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 4) },  \
+    /* 19 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 4) },  \
+                                                      \
+    /* 20 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 0) },  \
+    /* 21 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 0) },  \
+    /* 22 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 1) },  \
+    /* 23 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 1) },  \
+    /* 24 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 2) },  \
+    /* 25 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 2) },  \
+    /* 26 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 3) },  \
+    /* 27 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 3) },  \
+    /* 28 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 4) },  \
+    /* 29 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 4) },  \
+                                                      \
+    /* Front, again... Now right part */              \
+    /* 30 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 0) },  \
+    /* 31 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 0) },  \
+    /* 32 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 1) },  \
+    /* 33 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 1) },  \
+    /* 34 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 2) },  \
+    /* 35 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 2) },  \
+    /* 36 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 3) },  \
+    /* 37 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 3) },  \
+    /* 38 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 4) },  \
+    /* 39 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 4) }   \
+}
+// clang-format on
+
+#pragma endregion BH_DEVICE_TACTSUITX40
+
+namespace BH {
+    using namespace SenseShift::Body::Hands::Haptics;
+
+    typedef SenseShift::Body::Haptics::Effect_t Effect_t;
+    typedef SenseShift::Body::Haptics::Target_t Target_t;
+    typedef SenseShift::Body::Haptics::Position_t Position_t;
+    typedef std::tuple<Target_t, Position_t> OutputLayout_t;
+
+    // TactSuit X40 motor positions
+    static constexpr const OutputLayout_t TactSuitX40Layout[] = BH_LAYOUT_TACTSUITX40;
+
+    // TactGlove Wrist motor position
+    static constexpr const Position_t WRIST_MOTOR_POSITION(127, 191);
+    static constexpr const OutputLayout_t TactGloveLeftLayout[] = {
+        { Target_t::HandLeftThumb, FINGERTIP_POSITION },  { Target_t::HandLeftIndex, FINGERTIP_POSITION },
+        { Target_t::HandLeftMiddle, FINGERTIP_POSITION }, { Target_t::HandLeftRing, FINGERTIP_POSITION },
+        { Target_t::HandLeftLittle, FINGERTIP_POSITION }, { Target_t::HandLeftDorsal, WRIST_MOTOR_POSITION },
+    };
+    static constexpr const OutputLayout_t TactGloveRightLayout[] = {
+        { Target_t::HandRightThumb, FINGERTIP_POSITION },  { Target_t::HandRightIndex, FINGERTIP_POSITION },
+        { Target_t::HandRightMiddle, FINGERTIP_POSITION }, { Target_t::HandRightRing, FINGERTIP_POSITION },
+        { Target_t::HandRightLittle, FINGERTIP_POSITION }, { Target_t::HandRightDorsal, WRIST_MOTOR_POSITION },
+    };
+} // namespace BH
diff --git a/lib/bhaptics/bh_encoding.hpp b/lib/bhaptics/bh_encoding.hpp
index 4a4c7508..6a7c5490 100644
--- a/lib/bhaptics/bh_encoding.hpp
+++ b/lib/bhaptics/bh_encoding.hpp
@@ -1,55 +1,91 @@
 #pragma once
 
-#include <haptic_body.hpp>
 #include <cstring>
+#include <haptic_body.hpp>
 
-namespace BH
-{
-    class Decoder
-    {
+namespace BH {
+    class Decoder {
       public:
         typedef SenseShift::Body::Haptics::VibroEffectData_t VibroEffectData_t;
         typedef SenseShift::Body::Haptics::EffectData_t EffectData_t;
         typedef SenseShift::Body::Haptics::Effect_t Effect_t;
         typedef SenseShift::Body::Haptics::Target_t Target_t;
         typedef SenseShift::Body::Haptics::Position_t Position_t;
-        typedef std::tuple<Effect_t, Target_t, Position_t> OutputLayout_t;
+        typedef std::tuple<Target_t, Position_t> OutputLayout_t;
 
         static const size_t VEST_LAYOUT_SIZE = 40;
         static const size_t VEST_PAYLOAD_SIZE = 20;
 
+        template<size_t N>
+        static void applyPlain(
+          SenseShift::Body::Haptics::HapticBody* output,
+          const uint8_t (&value)[N],
+          const OutputLayout_t (&layout)[N],
+          const Effect_t effect
+        )
+        {
+            for (size_t i = 0; i < N; i++) {
+                const auto [target, position] = layout[i];
+                const uint8_t byte = value[i];
+
+                output->effect({
+                  .effect = effect,
+                  .target = target,
+                  .position = position,
+                  .data = effectDataFromByte(effect, byte),
+                });
+            }
+        }
+
+        template<size_t N>
+        static void applyPlain(
+          SenseShift::Body::Haptics::HapticBody* output,
+          std::string& value,
+          const OutputLayout_t (&layout)[N],
+          const Effect_t effect
+        )
+        {
+            std::uint8_t buf[N];
+            std::size_t copyLength = std::min(value.size(), sizeof(buf));
+            std::memcpy(buf, value.c_str(), copyLength);
+
+            applyPlain(output, buf, layout, effect);
+        }
+
         /**
          * Apply plain-encoded data to the output.
          */
         template<size_t N>
         static void applyPlain(
-            SenseShift::Body::Haptics::HapticBody* output,
-            const uint8_t (&value)[N],
-            const Position_t (&layout)[N],
-            const Effect_t effect,
-            const Target_t target
-        ) {
+          SenseShift::Body::Haptics::HapticBody* output,
+          const uint8_t (&value)[N],
+          const Position_t (&layout)[N],
+          const Effect_t effect,
+          const Target_t target
+        )
+        {
             for (size_t i = 0; i < N; i++) {
-                auto position = layout[i];
-                uint8_t byte = value[i];
+                const auto position = layout[i];
+                const uint8_t byte = value[i];
 
                 output->effect({
-                    .effect = effect,
-                    .target = target,
-                    .position = position,
-                    .data = effectDataFromByte(effect, byte),
+                  .effect = effect,
+                  .target = target,
+                  .position = position,
+                  .data = effectDataFromByte(effect, byte),
                 });
             }
         }
 
         template<size_t N>
         static void applyPlain(
-            SenseShift::Body::Haptics::HapticBody* output,
-            std::string& value,
-            const Position_t (&layout)[N],
-            const Effect_t effect,
-            const Target_t target
-        ) {
+          SenseShift::Body::Haptics::HapticBody* output,
+          std::string& value,
+          const Position_t (&layout)[N],
+          const Effect_t effect,
+          const Target_t target
+        )
+        {
             std::uint8_t buf[6];
             std::size_t copyLength = std::min(value.size(), sizeof(buf));
             std::memcpy(buf, value.c_str(), copyLength);
@@ -61,43 +97,37 @@ namespace BH
          * Apply vest-encoded data to the output.
          */
         static void applyVest(
-            SenseShift::Body::Haptics::HapticBody* output,
-            const uint8_t (&value)[VEST_PAYLOAD_SIZE],
-            const Position_t (&layout)[VEST_LAYOUT_SIZE]
-        ) {
+          SenseShift::Body::Haptics::HapticBody* output,
+          const uint8_t (&value)[VEST_PAYLOAD_SIZE],
+          const Position_t (&layout)[VEST_LAYOUT_SIZE]
+        )
+        {
             for (size_t i = 0; i < VEST_PAYLOAD_SIZE; i++) {
                 uint8_t byte = value[i];
                 uint actIndex = i * 2;
                 const auto target = (actIndex < 10 || actIndex >= 30) ? Target_t::ChestFront : Target_t::ChestBack;
 
                 output->effect({
-                    .effect = Effect_t::Vibro,
-                    .target = target,
-                    .position = layout[actIndex],
-                    .data = effectDataFromByte(
-                        Effect_t::Vibro,
-                        ((byte >> 4) & 0xf),
-                        15
-                    ),
+                  .effect = Effect_t::Vibro,
+                  .target = target,
+                  .position = layout[actIndex],
+                  .data = effectDataFromByte(Effect_t::Vibro, ((byte >> 4) & 0xf), 15),
                 });
                 output->effect({
-                    .effect = Effect_t::Vibro,
-                    .target = target,
-                    .position = layout[actIndex + 1],
-                    .data = effectDataFromByte(
-                        Effect_t::Vibro,
-                        (byte & 0xf),
-                        15
-                    ),
+                  .effect = Effect_t::Vibro,
+                  .target = target,
+                  .position = layout[actIndex + 1],
+                  .data = effectDataFromByte(Effect_t::Vibro, (byte & 0xf), 15),
                 });
             }
         }
 
         static void applyVest(
-            SenseShift::Body::Haptics::HapticBody* output,
-            std::string& value,
-            const Position_t (&layout)[VEST_LAYOUT_SIZE]
-        ) {
+          SenseShift::Body::Haptics::HapticBody* output,
+          std::string& value,
+          const Position_t (&layout)[VEST_LAYOUT_SIZE]
+        )
+        {
             std::uint8_t buf[VEST_PAYLOAD_SIZE];
             std::size_t copyLength = std::min(value.size(), sizeof(buf));
             std::memcpy(buf, value.c_str(), copyLength);
@@ -108,13 +138,14 @@ namespace BH
         /**
          * Apply grouped vest-encoded data to the output.
          */
-        template <size_t N>
+        template<size_t N>
         static void applyVestGrouped(
-            SenseShift::Body::Haptics::HapticBody* output,
-            const uint8_t (&value)[VEST_PAYLOAD_SIZE],
-            const Position_t (&layout)[VEST_LAYOUT_SIZE],
-            const uint8_t (&layoutGroups)[N]
-        ) {
+          SenseShift::Body::Haptics::HapticBody* output,
+          const uint8_t (&value)[VEST_PAYLOAD_SIZE],
+          const Position_t (&layout)[VEST_LAYOUT_SIZE],
+          const uint8_t (&layoutGroups)[N]
+        )
+        {
             uint8_t result[VEST_LAYOUT_SIZE];
 
             // Unpack values
@@ -155,25 +186,22 @@ namespace BH
                 const auto target = (i < 10 || i >= 30) ? Target_t::ChestFront : Target_t::ChestBack;
 
                 output->effect({
-                    .effect = Effect_t::Vibro,
-                    .target = target,
-                    .position = layout[i],
-                    .data = effectDataFromByte(
-                        Effect_t::Vibro,
-                        result[i],
-                        15
-                    ),
+                  .effect = Effect_t::Vibro,
+                  .target = target,
+                  .position = layout[i],
+                  .data = effectDataFromByte(Effect_t::Vibro, result[i], 15),
                 });
             }
         }
 
-        template <size_t N>
+        template<size_t N>
         static void applyVestGrouped(
-            SenseShift::Body::Haptics::HapticBody* output,
-            std::string& value,
-            const Position_t (&layout)[VEST_LAYOUT_SIZE],
-            const uint8_t (&layoutGroups)[N]
-        ) {
+          SenseShift::Body::Haptics::HapticBody* output,
+          std::string& value,
+          const Position_t (&layout)[VEST_LAYOUT_SIZE],
+          const uint8_t (&layoutGroups)[N]
+        )
+        {
             std::uint8_t buf[VEST_PAYLOAD_SIZE];
             std::size_t copyLength = std::min(value.size(), sizeof(buf));
             std::memcpy(buf, value.c_str(), copyLength);
@@ -182,17 +210,13 @@ namespace BH
         }
 
       private:
-        static const EffectData_t effectDataFromByte(
-            const Effect_t& effect,
-            const uint8_t byte,
-            const uint8_t maxValue = 100
-        ) {
+        static const EffectData_t
+          effectDataFromByte(const Effect_t effect, const uint8_t byte, const uint8_t maxValue = 100)
+        {
             switch (effect) {
                 case Effect_t::Vibro:
                     return VibroEffectData_t(
-                        OH::simpleMap<VibroEffectData_t::Intensity_t>(
-                            byte, maxValue, VibroEffectData_t::INTENSITY_MAX
-                        )
+                      OH::simpleMap<VibroEffectData_t::Intensity_t>(byte, maxValue, VibroEffectData_t::INTENSITY_MAX)
                     );
                 default:
                     throw std::runtime_error("Unknown effect");
diff --git a/lib/hands/hand_interface.hpp b/lib/hands/hand_interface.hpp
new file mode 100644
index 00000000..34dca343
--- /dev/null
+++ b/lib/hands/hand_interface.hpp
@@ -0,0 +1,19 @@
+#pragma once
+
+#include <cstdint>
+#include <haptics_interface.hpp>
+
+namespace SenseShift::Body {
+    namespace Hands {
+        typedef std::uint8_t HandPositionIndex_t;
+        typedef enum class HandPosition : HandPositionIndex_t { Left, Right } HandPosition_t;
+
+        namespace Haptics {
+            /**
+             * @brief Helper with position of the haptic device on the fingertip.
+             * Distal phalanx of the volar surface of the any finger.
+             */
+            static constexpr const ::SenseShift::Body::Haptics::Position_t FINGERTIP_POSITION(127, 16);
+        } // namespace Haptics
+    }     // namespace Hands
+} // namespace SenseShift::Body
diff --git a/lib/haptics/haptics_interface.hpp b/lib/haptics/haptics_interface.hpp
index 26bd2b86..ca926f21 100644
--- a/lib/haptics/haptics_interface.hpp
+++ b/lib/haptics/haptics_interface.hpp
@@ -21,10 +21,27 @@ namespace SenseShift::Body::Haptics {
         Invalid = TARGET_INVALID,
         ChestFront = 0x00,
         ChestBack = 0x01,
+
         // Legacy backword compatibility
         Accessory [[deprecated]] = 0x02,
 
-        FaceFront = 0x03,
+        FaceFront,
+
+        HandLeftThumb,
+        HandLeftIndex,
+        HandLeftMiddle,
+        HandLeftRing,
+        HandLeftLittle,
+        HandLeftVolar, // Palm
+        HandLeftDorsal, // Back
+
+        HandRightThumb,
+        HandRightIndex,
+        HandRightMiddle,
+        HandRightRing,
+        HandRightLittle,
+        HandRightVolar, // Palm
+        HandRightDorsal, // Back
 
         // TODO: arms, legs, etc.
     } Target_t;
diff --git a/platformio.ini b/platformio.ini
index f88d3a17..c11d81a2 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -27,7 +27,7 @@ build_flags       =
 ;	-D DEBUG_ESP_PORT=Serial
 ;	-D SERIAL_PLOTTER=true
 ;	-D BATTERY_ENABLED=true
-;	-D BLUETOOTH_USE_NIMBLE=false
+	-D BLUETOOTH_USE_NIMBLE=true
 
 build_src_filter  =
 	+<*>
diff --git a/test/test_bhaptics_encoding/main.cpp b/test/test_bhaptics_encoding/main.cpp
index e73993c4..7f153779 100644
--- a/test/test_bhaptics_encoding/main.cpp
+++ b/test/test_bhaptics_encoding/main.cpp
@@ -1,5 +1,6 @@
 #include <bh_types.hpp>
 #include <bh_encoding.hpp>
+#include <bh_devices.hpp>
 
 #include <unity.h>
 
@@ -212,6 +213,50 @@ void test_layout_tactal(void)
     TEST_ASSERT_EQUAL_INT(3931, actuator5->intensity);
 }
 
+void test_layout_tactglove(void)
+{
+    TestActuator* actuatorThumb = new TestActuator();
+    TestActuator* actuatorIndex = new TestActuator();
+    TestActuator* actuatorMiddle = new TestActuator();
+    TestActuator* actuatorRing = new TestActuator();
+    TestActuator* actuatorLittle = new TestActuator();
+    TestActuator* actuatorWrist = new TestActuator();
+
+    const auto& bhLayout = BH::TactGloveLeftLayout;
+
+    auto thumb = new VibroPlane({{ std::get<2>(bhLayout[0]), actuatorThumb }});
+    auto index = new VibroPlane({{ std::get<2>(bhLayout[1]), actuatorIndex }});
+    auto middle = new VibroPlane({{ std::get<2>(bhLayout[2]), actuatorMiddle }});
+    auto ring = new VibroPlane({{ std::get<2>(bhLayout[3]), actuatorRing }});
+    auto little = new VibroPlane({{ std::get<2>(bhLayout[4]), actuatorLittle }});
+    auto wrist = new VibroPlane({{ std::get<2>(bhLayout[5]), actuatorWrist }});
+
+    auto body = new HapticBody();
+
+    body->addTarget(Target::HandLeftThumb, thumb);
+    body->addTarget(Target::HandLeftIndex, index);
+    body->addTarget(Target::HandLeftMiddle, middle);
+    body->addTarget(Target::HandLeftRing, ring);
+    body->addTarget(Target::HandLeftLittle, little);
+    body->addTarget(Target::HandLeftDorsal, wrist);
+
+    Decoder::applyPlain(body, { 0x64, 0x00, 0x00, 0x00, 0x00, 0x00 }, bhLayout);
+    TEST_ASSERT_EQUAL_INT(4095, actuatorThumb->intensity);
+    TEST_ASSERT_EQUAL_INT(0, actuatorIndex->intensity);
+    TEST_ASSERT_EQUAL_INT(0, actuatorMiddle->intensity);
+    TEST_ASSERT_EQUAL_INT(0, actuatorRing->intensity);
+    TEST_ASSERT_EQUAL_INT(0, actuatorLittle->intensity);
+    TEST_ASSERT_EQUAL_INT(0, actuatorWrist->intensity);
+
+    Decoder::applyPlain(body, { 0x10, 0x20, 0x30, 0x40, 0x50, 0x60 }, bhLayout);
+    TEST_ASSERT_EQUAL_INT(655, actuatorThumb->intensity);
+    TEST_ASSERT_EQUAL_INT(1310, actuatorIndex->intensity);
+    TEST_ASSERT_EQUAL_INT(1965, actuatorMiddle->intensity);
+    TEST_ASSERT_EQUAL_INT(2620, actuatorRing->intensity);
+    TEST_ASSERT_EQUAL_INT(3276, actuatorLittle->intensity);
+    TEST_ASSERT_EQUAL_INT(3931, actuatorWrist->intensity);
+}
+
 int process(void)
 {
     UNITY_BEGIN();
@@ -219,9 +264,6 @@ int process(void)
     RUN_TEST(test_layout_tactsuitx16);
     RUN_TEST(test_layout_tactsuitx40);
     RUN_TEST(test_layout_tactal);
-    // RUN_TEST(test_layout_tactosy2);
-    // RUN_TEST(test_layout_tactosyh);
-    // RUN_TEST(test_layout_tactosyf);
 
     return UNITY_END();
 }

From 27c6650b53c888588a899c6b480a5432fe589069 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Mon, 21 Aug 2023 14:45:24 +0400
Subject: [PATCH 06/82] refactor(bHaptics): restructure lib

---
 firmware/mode_configs/bhaptics/tactal.cpp     |  18 +-
 lib/bhaptics/bh_devices.hpp                   |  95 -------
 lib/bhaptics/bh_types.hpp                     |   5 -
 lib/bhaptics/bh_types_c.h                     |   3 -
 lib/bhaptics/senseshift/bh/constants.hpp      |  17 ++
 .../bh/devices.hpp}                           | 145 ++++++----
 .../bh/encoding.hpp}                          |  36 +--
 lib/bhaptics_ble/connection_bhble.cpp         | 256 -----------------
 .../senseshift/bh/ble/connection.cpp          | 260 ++++++++++++++++++
 .../bh/ble/connection.hpp}                    |  68 ++---
 .../senseshift/bh/ble/constants.hpp           |  59 ++++
 test/test_bhaptics_encoding/main.cpp          |  16 +-
 12 files changed, 501 insertions(+), 477 deletions(-)
 delete mode 100644 lib/bhaptics/bh_devices.hpp
 delete mode 100644 lib/bhaptics/bh_types.hpp
 delete mode 100644 lib/bhaptics/bh_types_c.h
 create mode 100644 lib/bhaptics/senseshift/bh/constants.hpp
 rename lib/bhaptics/{bh_constants.hpp => senseshift/bh/devices.hpp} (58%)
 rename lib/bhaptics/{bh_encoding.hpp => senseshift/bh/encoding.hpp} (88%)
 delete mode 100644 lib/bhaptics_ble/connection_bhble.cpp
 create mode 100644 lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
 rename lib/bhaptics_ble/{connection_bhble.hpp => senseshift/bh/ble/connection.hpp} (67%)
 create mode 100644 lib/bhaptics_ble/senseshift/bh/ble/constants.hpp

diff --git a/firmware/mode_configs/bhaptics/tactal.cpp b/firmware/mode_configs/bhaptics/tactal.cpp
index 17982748..89b3a620 100644
--- a/firmware/mode_configs/bhaptics/tactal.cpp
+++ b/firmware/mode_configs/bhaptics/tactal.cpp
@@ -6,8 +6,9 @@
 
 #include "senseshift.h"
 
-#include <bh_encoding.hpp>
-#include <connection_bhble.hpp>
+#include <senseshift/bh/devices.hpp>
+#include <senseshift/bh/encoding.hpp>
+#include <senseshift/bh/ble/connection.hpp>
 #include <output_writers/pwm.hpp>
 
 #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
@@ -17,7 +18,6 @@
 using namespace OH;
 using namespace SenseShift;
 using namespace SenseShift::Body::Haptics;
-using namespace BH;
 
 extern SenseShift::SenseShift App;
 SenseShift::SenseShift* app = &App;
@@ -39,16 +39,14 @@ void setupMode()
 
     app->getHapticBody()->setup();
 
-    uint8_t serialNumber[BH_SERIAL_NUMBER_LENGTH] = BH_SERIAL_NUMBER;
-    ConnectionBHBLE_Config config = {
+    auto* bhBleConnection = new BH::BLE::Connection(
+      {
         .deviceName = BLUETOOTH_NAME,
         .appearance = BH_BLE_APPEARANCE,
-        .serialNumber = serialNumber,
-    };
-    auto* bhBleConnection = new ConnectionBHBLE(
-      config,
+        .serialNumber = BH_SERIAL_NUMBER,
+        },
       [](std::string& value) -> void {
-        Decoder::applyPlain<6>(app->getHapticBody(), value, bhLayout, Effect::Vibro, Target::FaceFront);
+        BH::Decoder::applyPlain<6>(app->getHapticBody(), value, bhLayout, Effect::Vibro, Target::FaceFront);
       },
       app
     );
diff --git a/lib/bhaptics/bh_devices.hpp b/lib/bhaptics/bh_devices.hpp
deleted file mode 100644
index dd131e20..00000000
--- a/lib/bhaptics/bh_devices.hpp
+++ /dev/null
@@ -1,95 +0,0 @@
-#pragma once
-
-#include <hand_interface.hpp>
-
-#pragma region TaxtSuit X16
-
-#define BH_LAYOUT_TACTSUITX40_SIZE_X 4
-#define BH_LAYOUT_TACTSUITX40_SIZE_Y 5
-#define BH_LAYOUT_TACTSUITX40_MAKE_POINT(x, y)                                  \
-    SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
-      x,                                                                        \
-      y,                                                                        \
-      (oh_output_coord_t) (BH_LAYOUT_TACTSUITX40_SIZE_X - 1),                   \
-      (oh_output_coord_t) (BH_LAYOUT_TACTSUITX40_SIZE_Y - 1)                    \
-    )
-
-// X * Y for front and back
-#define BH_LAYOUT_TACTSUITX40_SIZE 40
-// clang-format off
-#define BH_LAYOUT_TACTSUITX40 {                       \
-    /* Front, left part */                            \
-    /*  0 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 0) },  \
-    /*  1 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 0) },  \
-    /*  2 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 1) },  \
-    /*  3 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 1) },  \
-    /*  4 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 2) },  \
-    /*  5 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 2) },  \
-    /*  6 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 3) },  \
-    /*  7 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 3) },  \
-    /*  8 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 4) },  \
-    /*  9 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 4) },  \
-                                                      \
-    /* Back */                                        \
-    /* 11 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 0) },  \
-    /* 11 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 0) },  \
-    /* 12 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 1) },  \
-    /* 13 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 1) },  \
-    /* 14 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 2) },  \
-    /* 15 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 2) },  \
-    /* 16 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 3) },  \
-    /* 17 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 3) },  \
-    /* 18 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 4) },  \
-    /* 19 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 4) },  \
-                                                      \
-    /* 20 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 0) },  \
-    /* 21 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 0) },  \
-    /* 22 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 1) },  \
-    /* 23 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 1) },  \
-    /* 24 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 2) },  \
-    /* 25 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 2) },  \
-    /* 26 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 3) },  \
-    /* 27 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 3) },  \
-    /* 28 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 4) },  \
-    /* 29 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 4) },  \
-                                                      \
-    /* Front, again... Now right part */              \
-    /* 30 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 0) },  \
-    /* 31 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 0) },  \
-    /* 32 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 1) },  \
-    /* 33 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 1) },  \
-    /* 34 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 2) },  \
-    /* 35 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 2) },  \
-    /* 36 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 3) },  \
-    /* 37 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 3) },  \
-    /* 38 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 4) },  \
-    /* 39 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 4) }   \
-}
-// clang-format on
-
-#pragma endregion BH_DEVICE_TACTSUITX40
-
-namespace BH {
-    using namespace SenseShift::Body::Hands::Haptics;
-
-    typedef SenseShift::Body::Haptics::Effect_t Effect_t;
-    typedef SenseShift::Body::Haptics::Target_t Target_t;
-    typedef SenseShift::Body::Haptics::Position_t Position_t;
-    typedef std::tuple<Target_t, Position_t> OutputLayout_t;
-
-    // TactSuit X40 motor positions
-    static constexpr const OutputLayout_t TactSuitX40Layout[] = BH_LAYOUT_TACTSUITX40;
-
-    // TactGlove Wrist motor position
-    static constexpr const Position_t WRIST_MOTOR_POSITION(127, 191);
-    static constexpr const OutputLayout_t TactGloveLeftLayout[] = {
-        { Target_t::HandLeftThumb, FINGERTIP_POSITION },  { Target_t::HandLeftIndex, FINGERTIP_POSITION },
-        { Target_t::HandLeftMiddle, FINGERTIP_POSITION }, { Target_t::HandLeftRing, FINGERTIP_POSITION },
-        { Target_t::HandLeftLittle, FINGERTIP_POSITION }, { Target_t::HandLeftDorsal, WRIST_MOTOR_POSITION },
-    };
-    static constexpr const OutputLayout_t TactGloveRightLayout[] = {
-        { Target_t::HandRightThumb, FINGERTIP_POSITION },  { Target_t::HandRightIndex, FINGERTIP_POSITION },
-        { Target_t::HandRightMiddle, FINGERTIP_POSITION }, { Target_t::HandRightRing, FINGERTIP_POSITION },
-        { Target_t::HandRightLittle, FINGERTIP_POSITION }, { Target_t::HandRightDorsal, WRIST_MOTOR_POSITION },
-    };
-} // namespace BH
diff --git a/lib/bhaptics/bh_types.hpp b/lib/bhaptics/bh_types.hpp
deleted file mode 100644
index 3cba875f..00000000
--- a/lib/bhaptics/bh_types.hpp
+++ /dev/null
@@ -1,5 +0,0 @@
-#pragma once
-
-#include "bh_types_c.h"
-
-#include "bh_constants.hpp"
diff --git a/lib/bhaptics/bh_types_c.h b/lib/bhaptics/bh_types_c.h
deleted file mode 100644
index 20bf1928..00000000
--- a/lib/bhaptics/bh_types_c.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-#include <stdint.h>
diff --git a/lib/bhaptics/senseshift/bh/constants.hpp b/lib/bhaptics/senseshift/bh/constants.hpp
new file mode 100644
index 00000000..930a7e3e
--- /dev/null
+++ b/lib/bhaptics/senseshift/bh/constants.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+#ifndef BH_FIRMWARE_VERSION_MAJOR
+#define BH_FIRMWARE_VERSION_MAJOR ((uint8_t) UINT8_MAX)
+#endif
+
+#ifndef BH_FIRMWARE_VERSION_MINOR
+#define BH_FIRMWARE_VERSION_MINOR ((uint8_t) UINT8_MAX)
+#endif
+
+#ifndef BH_FIRMWARE_VERSION
+#define BH_FIRMWARE_VERSION (uint16_t)((BH_FIRMWARE_VERSION_MAJOR << 8) | BH_FIRMWARE_VERSION_MINOR)
+#endif
+
+// TODO: use enum
+#define NO_AUDIO_CABLE 0
+#define AUDIO_CABLE 1
diff --git a/lib/bhaptics/bh_constants.hpp b/lib/bhaptics/senseshift/bh/devices.hpp
similarity index 58%
rename from lib/bhaptics/bh_constants.hpp
rename to lib/bhaptics/senseshift/bh/devices.hpp
index c430911a..e500c09f 100644
--- a/lib/bhaptics/bh_constants.hpp
+++ b/lib/bhaptics/senseshift/bh/devices.hpp
@@ -1,34 +1,81 @@
 #pragma once
 
+#include <hand_interface.hpp>
 #include <haptic_plane.hpp>
 
-#if defined(ESP32)
-#include <BLEUUID.h>
-#endif
+#pragma region BH_DEVICE_TACTSUITX40
 
-#define BH_SERIAL_NUMBER_LENGTH 10
-
-#ifndef BH_FIRMWARE_VERSION_MAJOR
-#define BH_FIRMWARE_VERSION_MAJOR ((uint8_t) UINT8_MAX)
-#endif
-
-#ifndef BH_FIRMWARE_VERSION_MINOR
-#define BH_FIRMWARE_VERSION_MINOR ((uint8_t) UINT8_MAX)
-#endif
+#define BH_LAYOUT_TACTSUITX40_SIZE_X 4
+#define BH_LAYOUT_TACTSUITX40_SIZE_Y 5
+#define BH_LAYOUT_TACTSUITX40_MAKE_POINT(x, y)                                  \
+    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
+      x,                                                                        \
+      y,                                                                        \
+      (oh_output_coord_t) (BH_LAYOUT_TACTSUITX40_SIZE_X - 1),                   \
+      (oh_output_coord_t) (BH_LAYOUT_TACTSUITX40_SIZE_Y - 1)                    \
+    )
 
-#ifndef BH_FIRMWARE_VERSION
-#define BH_FIRMWARE_VERSION (uint16_t)((BH_FIRMWARE_VERSION_MAJOR << 8) | BH_FIRMWARE_VERSION_MINOR)
-#endif
+// X * Y for front and back
+#define BH_LAYOUT_TACTSUITX40_SIZE 40
+// clang-format off
+#define BH_LAYOUT_TACTSUITX40 {                       \
+    /* Front, left part */                            \
+    /*  0 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 0) },  \
+    /*  1 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 0) },  \
+    /*  2 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 1) },  \
+    /*  3 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 1) },  \
+    /*  4 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 2) },  \
+    /*  5 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 2) },  \
+    /*  6 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 3) },  \
+    /*  7 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 3) },  \
+    /*  8 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 4) },  \
+    /*  9 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 4) },  \
+                                                      \
+    /* Back */                                        \
+    /* 11 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 0) },  \
+    /* 11 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 0) },  \
+    /* 12 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 1) },  \
+    /* 13 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 1) },  \
+    /* 14 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 2) },  \
+    /* 15 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 2) },  \
+    /* 16 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 3) },  \
+    /* 17 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 3) },  \
+    /* 18 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 4) },  \
+    /* 19 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 4) },  \
+                                                      \
+    /* 20 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 0) },  \
+    /* 21 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 0) },  \
+    /* 22 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 1) },  \
+    /* 23 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 1) },  \
+    /* 24 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 2) },  \
+    /* 25 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 2) },  \
+    /* 26 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 3) },  \
+    /* 27 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 3) },  \
+    /* 28 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 4) },  \
+    /* 29 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 4) },  \
+                                                      \
+    /* Front, again... Now right part */              \
+    /* 30 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 0) },  \
+    /* 31 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 0) },  \
+    /* 32 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 1) },  \
+    /* 33 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 1) },  \
+    /* 34 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 2) },  \
+    /* 35 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 2) },  \
+    /* 36 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 3) },  \
+    /* 37 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 3) },  \
+    /* 38 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 4) },  \
+    /* 39 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 4) }   \
+}
+// clang-format on
 
-#define NO_AUDIO_CABLE 0
-#define AUDIO_CABLE 1
+#pragma endregion BH_DEVICE_TACTSUITX40
 
 #pragma region BH_DEVICE_TACTSUITX16
 
 #define BH_LAYOUT_TACTSUITX16_SIZE_X 4
 #define BH_LAYOUT_TACTSUITX16_SIZE_Y 2
 #define BH_LAYOUT_TACTSUITX16_MAKE_POINT(x, y)                                  \
-    SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
+    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
       x,                                                                        \
       y,                                                                        \
       (oh_output_coord_t) (BH_LAYOUT_TACTSUITX16_SIZE_X - 1),                   \
@@ -106,7 +153,7 @@
 #define BH_LAYOUT_TACTAL_SIZE_X 6
 #define BH_LAYOUT_TACTAL_SIZE_Y 1
 #define BH_LAYOUT_TACTAL_MAKE_POINT(x, y)                                       \
-    SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
+    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
       x,                                                                        \
       y,                                                                        \
       (oh_output_coord_t) (BH_LAYOUT_TACTAL_SIZE_X - 1),                        \
@@ -132,7 +179,7 @@
 #define BH_LAYOUT_TACTVISOR_SIZE_X 4
 #define BH_LAYOUT_TACTVISOR_SIZE_Y 1
 #define BH_LAYOUT_TACTVISOR_MAKE_POINT(x, y)                                    \
-    SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
+    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
       x,                                                                        \
       y,                                                                        \
       (oh_output_coord_t) (BH_LAYOUT_TACTVISOR_SIZE_X - 1),                     \
@@ -156,7 +203,7 @@
 #define BH_LAYOUT_TACTOSY2_SIZE_X 3
 #define BH_LAYOUT_TACTOSY2_SIZE_Y 2
 #define BH_LAYOUT_TACTOSY2_MAKE_POINT(x, y)                                     \
-    SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
+    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
       x,                                                                        \
       y,                                                                        \
       (oh_output_coord_t) (BH_LAYOUT_TACTOSY2_SIZE_X - 1),                      \
@@ -178,7 +225,7 @@
 #define BH_LAYOUT_TACTOSYH_SIZE_X 1
 #define BH_LAYOUT_TACTOSYH_SIZE_Y 3
 #define BH_LAYOUT_TACTOSYH_MAKE_POINT(x, y)                                     \
-    SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
+    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
       x,                                                                        \
       y,                                                                        \
       (oh_output_coord_t) (BH_LAYOUT_TACTOSYH_SIZE_X - 1),                      \
@@ -197,7 +244,7 @@
 #define BH_LAYOUT_TACTOSYF_SIZE_X 1
 #define BH_LAYOUT_TACTOSYF_SIZE_Y 3
 #define BH_LAYOUT_TACTOSYF_MAKE_POINT(x, y)                                     \
-    SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
+    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
       x,                                                                        \
       y,                                                                        \
       (oh_output_coord_t) (BH_LAYOUT_TACTOSYF_SIZE_X - 1),                      \
@@ -211,30 +258,28 @@
 
 #pragma endregion BH_DEVICE_TACTOSYF
 
-// All below are weird choices of bHaptics engineers...
-// Why to use unconventional UUIDs, that are reserved for other purposes?
-// You have an unlimited amount of other UUIDs
-
-// Main service for communication
-#define BH_BLE_SERVICE_MOTOR_UUID BLEUUID("6e400001-b5a3-f393-e0a9-e50e24dcca9e") // Nordic UART Service
-
-// Legacy Characteristic to create haptic feedback
-#define BH_BLE_SERVICE_MOTOR_CHAR_MOTOR_UUID BLEUUID("6e400002-b5a3-f393-e0a9-e50e24dcca9e") // Nordic UART RX
-
-// Characteristic for Device S/N
-#define BH_BLE_SERVICE_MOTOR_CHAR_SERIAL_KEY_UUID BLEUUID("6e400003-b5a3-f393-e0a9-e50e24dcca9e") // Nordic UART TX
-
-// Glow Color
-#define BH_BLE_SERVICE_MOTOR_CHAR_CONFIG_UUID BLEUUID("6e400005-b5a3-f393-e0a9-e50e24dcca9e")
-#define BH_BLE_SERVICE_MOTOR_CHAR_VERSION_UUID BLEUUID("6e400007-b5a3-f393-e0a9-e50e24dcca9e")
-#define BH_BLE_SERVICE_MOTOR_CHAR_BATTERY_UUID BLEUUID("6e400008-b5a3-f393-e0a9-e50e24dcca9e")
-#define BH_BLE_SERVICE_MOTOR_CHAR_MOTOR_STABLE_UUID BLEUUID("6e40000a-b5a3-f393-e0a9-e50e24dcca9e")
-#define BH_BLE_SERVICE_MOTOR_CHAR_TACTSUIT_MONITOR_UUID BLEUUID("6e40000b-b5a3-f393-e0a9-e50e24dcca9e")
-#define BH_BLE_SERVICE_MOTOR_CHAR_ATH_GLOBAL_CONF_UUID \
-    BLEUUID("6e40000c-b5a3-f393-e0a9-e50e24dcca9e")                                              // Audio-to-Haptic
-#define BH_BLE_SERVICE_MOTOR_CHAR_ATH_THEME_UUID BLEUUID("6e40000d-b5a3-f393-e0a9-e50e24dcca9e") // Audio-to-Haptic
-#define BH_BLE_SERVICE_MOTOR_CHAR_MOTTOR_MAPPING_UUID BLEUUID("6e40000e-b5a3-f393-e0a9-e50e24dcca9e")
-#define BH_BLE_SERVICE_MOTOR_CHAR_SIGNATURE_PATTERN_UUID BLEUUID("6e40000f-b5a3-f393-e0a9-e50e24dcca9e")
-
-#define BH_BLE_SERVICE_DFU_UUID BLEUUID("0000fe59-0000-1000-8000-00805f9b34fb")
-#define BH_BLE_SERVICE_DFU_CHAR_CONTROL_UUID BLEUUID("8ec90003-f315-4f60-9fb8-838830daea50")
+namespace SenseShift::BH {
+    using namespace ::SenseShift::Body::Hands::Haptics;
+
+    using Effect_t = ::SenseShift::Body::Haptics::Effect_t;
+    using Target_t = ::SenseShift::Body::Haptics::Target_t;
+    using Position_t = ::SenseShift::Body::Haptics::Position_t;
+
+    typedef std::tuple<Target_t, Position_t> OutputLayout_t;
+
+    // TactSuit X40 motor positions
+    static constexpr const OutputLayout_t TactSuitX40Layout[] = BH_LAYOUT_TACTSUITX40;
+
+    // TactGlove Wrist motor position
+    static constexpr const Position_t WRIST_MOTOR_POSITION(127, 191);
+    static constexpr const OutputLayout_t TactGloveLeftLayout[] = {
+        { Target_t::HandLeftThumb, FINGERTIP_POSITION },  { Target_t::HandLeftIndex, FINGERTIP_POSITION },
+        { Target_t::HandLeftMiddle, FINGERTIP_POSITION }, { Target_t::HandLeftRing, FINGERTIP_POSITION },
+        { Target_t::HandLeftLittle, FINGERTIP_POSITION }, { Target_t::HandLeftDorsal, WRIST_MOTOR_POSITION },
+    };
+    static constexpr const OutputLayout_t TactGloveRightLayout[] = {
+        { Target_t::HandRightThumb, FINGERTIP_POSITION },  { Target_t::HandRightIndex, FINGERTIP_POSITION },
+        { Target_t::HandRightMiddle, FINGERTIP_POSITION }, { Target_t::HandRightRing, FINGERTIP_POSITION },
+        { Target_t::HandRightLittle, FINGERTIP_POSITION }, { Target_t::HandRightDorsal, WRIST_MOTOR_POSITION },
+    };
+} // namespace SenseShift::BH
diff --git a/lib/bhaptics/bh_encoding.hpp b/lib/bhaptics/senseshift/bh/encoding.hpp
similarity index 88%
rename from lib/bhaptics/bh_encoding.hpp
rename to lib/bhaptics/senseshift/bh/encoding.hpp
index 6a7c5490..c6b876e1 100644
--- a/lib/bhaptics/bh_encoding.hpp
+++ b/lib/bhaptics/senseshift/bh/encoding.hpp
@@ -3,14 +3,18 @@
 #include <cstring>
 #include <haptic_body.hpp>
 
-namespace BH {
+namespace SenseShift::BH {
     class Decoder {
       public:
-        typedef SenseShift::Body::Haptics::VibroEffectData_t VibroEffectData_t;
-        typedef SenseShift::Body::Haptics::EffectData_t EffectData_t;
-        typedef SenseShift::Body::Haptics::Effect_t Effect_t;
-        typedef SenseShift::Body::Haptics::Target_t Target_t;
-        typedef SenseShift::Body::Haptics::Position_t Position_t;
+        using VibroEffectData_t = ::SenseShift::Body::Haptics::VibroEffectData_t;
+        using EffectData_t = ::SenseShift::Body::Haptics::EffectData_t;
+
+        using Effect_t = ::SenseShift::Body::Haptics::Effect_t;
+        using Target_t = ::SenseShift::Body::Haptics::Target_t;
+        using Position_t = ::SenseShift::Body::Haptics::Position_t;
+
+        using HapticBody_t = ::SenseShift::Body::Haptics::HapticBody;
+
         typedef std::tuple<Target_t, Position_t> OutputLayout_t;
 
         static const size_t VEST_LAYOUT_SIZE = 40;
@@ -18,7 +22,7 @@ namespace BH {
 
         template<size_t N>
         static void applyPlain(
-          SenseShift::Body::Haptics::HapticBody* output,
+          HapticBody_t* output,
           const uint8_t (&value)[N],
           const OutputLayout_t (&layout)[N],
           const Effect_t effect
@@ -39,7 +43,7 @@ namespace BH {
 
         template<size_t N>
         static void applyPlain(
-          SenseShift::Body::Haptics::HapticBody* output,
+          HapticBody_t* output,
           std::string& value,
           const OutputLayout_t (&layout)[N],
           const Effect_t effect
@@ -57,7 +61,7 @@ namespace BH {
          */
         template<size_t N>
         static void applyPlain(
-          SenseShift::Body::Haptics::HapticBody* output,
+          HapticBody_t* output,
           const uint8_t (&value)[N],
           const Position_t (&layout)[N],
           const Effect_t effect,
@@ -79,14 +83,14 @@ namespace BH {
 
         template<size_t N>
         static void applyPlain(
-          SenseShift::Body::Haptics::HapticBody* output,
+          HapticBody_t* output,
           std::string& value,
           const Position_t (&layout)[N],
           const Effect_t effect,
           const Target_t target
         )
         {
-            std::uint8_t buf[6];
+            std::uint8_t buf[N];
             std::size_t copyLength = std::min(value.size(), sizeof(buf));
             std::memcpy(buf, value.c_str(), copyLength);
 
@@ -97,7 +101,7 @@ namespace BH {
          * Apply vest-encoded data to the output.
          */
         static void applyVest(
-          SenseShift::Body::Haptics::HapticBody* output,
+          HapticBody_t* output,
           const uint8_t (&value)[VEST_PAYLOAD_SIZE],
           const Position_t (&layout)[VEST_LAYOUT_SIZE]
         )
@@ -123,7 +127,7 @@ namespace BH {
         }
 
         static void applyVest(
-          SenseShift::Body::Haptics::HapticBody* output,
+          HapticBody_t* output,
           std::string& value,
           const Position_t (&layout)[VEST_LAYOUT_SIZE]
         )
@@ -140,7 +144,7 @@ namespace BH {
          */
         template<size_t N>
         static void applyVestGrouped(
-          SenseShift::Body::Haptics::HapticBody* output,
+          HapticBody_t* output,
           const uint8_t (&value)[VEST_PAYLOAD_SIZE],
           const Position_t (&layout)[VEST_LAYOUT_SIZE],
           const uint8_t (&layoutGroups)[N]
@@ -196,7 +200,7 @@ namespace BH {
 
         template<size_t N>
         static void applyVestGrouped(
-          SenseShift::Body::Haptics::HapticBody* output,
+          HapticBody_t* output,
           std::string& value,
           const Position_t (&layout)[VEST_LAYOUT_SIZE],
           const uint8_t (&layoutGroups)[N]
@@ -223,4 +227,4 @@ namespace BH {
             }
         }
     };
-} // namespace BH
+} // namespace SenseShift::BH
diff --git a/lib/bhaptics_ble/connection_bhble.cpp b/lib/bhaptics_ble/connection_bhble.cpp
deleted file mode 100644
index 6367b64d..00000000
--- a/lib/bhaptics_ble/connection_bhble.cpp
+++ /dev/null
@@ -1,256 +0,0 @@
-#include "connection_bhble.hpp"
-
-#include <events.hpp>
-#include <haptic_body.hpp>
-
-#include <Arduino.h>
-
-#if defined(BLUETOOTH_USE_NIMBLE) && BLUETOOTH_USE_NIMBLE == true
-// BLE2902 not needed: https://github.com/h2zero/NimBLE-Arduino/blob/release/1.4/docs/Migration_guide.md#descriptors
-
-#define PROPERTY_READ NIMBLE_PROPERTY::READ
-#define PROPERTY_WRITE NIMBLE_PROPERTY::WRITE
-#define PROPERTY_WRITE_NR NIMBLE_PROPERTY::WRITE_NR
-#define PROPERTY_BROADCAST NIMBLE_PROPERTY::BROADCAST
-#define PROPERTY_NOTIFY NIMBLE_PROPERTY::NOTIFY
-#define PROPERTY_INDICATE NIMBLE_PROPERTY::INDICATE
-#else
-#include <BLE2902.h>
-
-#define PROPERTY_READ BLECharacteristic::PROPERTY_READ
-#define PROPERTY_WRITE BLECharacteristic::PROPERTY_WRITE
-#define PROPERTY_WRITE_NR BLECharacteristic::PROPERTY_WRITE_NR
-#define PROPERTY_BROADCAST BLECharacteristic::PROPERTY_BROADCAST
-#define PROPERTY_NOTIFY BLECharacteristic::PROPERTY_NOTIFY
-#define PROPERTY_INDICATE BLECharacteristic::PROPERTY_INDICATE
-#endif
-
-class BHServerCallbacks final : public BLEServerCallbacks {
-  private:
-    OH::IEventDispatcher* dispatcher;
-
-  public:
-    BHServerCallbacks(OH::IEventDispatcher* eventDispatcher) : dispatcher(eventDispatcher) {}
-
-    void onConnect(BLEServer* pServer)
-    {
-        this->dispatcher->postEvent(new OH::IEvent(OH_EVENT_CONNECTED));
-    }
-
-    void onDisconnect(BLEServer* pServer)
-    {
-        this->dispatcher->postEvent(new OH::IEvent(OH_EVENT_DISCONNECTED));
-        pServer->startAdvertising();
-    }
-};
-
-class SerialOutputCharCallbacks : public BLECharacteristicCallbacks {
-    void onWrite(BLECharacteristic* pCharacteristic) override
-    {
-        log_d(
-          ">>\tonWrite (UUID: %s)\n\tvalue: `%s`, len: %u",
-          pCharacteristic->getUUID().toString().c_str(),
-          pCharacteristic->getValue().c_str(),
-          pCharacteristic->getValue().length()
-        );
-    };
-
-    void onRead(BLECharacteristic* pCharacteristic) override
-    {
-        log_d(
-          ">>\tonRead (UUID: %s)\n\tvalue: `%s`, len: %u",
-          pCharacteristic->getUUID().toString().c_str(),
-          pCharacteristic->getValue().c_str(),
-          pCharacteristic->getValue().length()
-        );
-    };
-
-    void onNotify(BLECharacteristic* pCharacteristic) override
-    {
-        log_d(
-          ">>\tonNotify (UUID: %s)\n\tvalue: `%s`, len: %u",
-          pCharacteristic->getUUID().toString().c_str(),
-          pCharacteristic->getValue().c_str(),
-          pCharacteristic->getValue().length()
-        );
-    };
-
-#if defined(BLUETOOTH_USE_NIMBLE) && BLUETOOTH_USE_NIMBLE == true
-    void onStatus(BLECharacteristic* pCharacteristic, Status s, int code) override
-#else
-    void onStatus(BLECharacteristic* pCharacteristic, Status s, uint32_t code) override
-#endif
-    {
-        log_d(
-          ">>\tonNotify (UUID: %s)\n\tstatus: %d, code: %u \n\tvalue: `%s`, len: %u",
-          pCharacteristic->getUUID().toString().c_str(),
-          s,
-          code,
-          pCharacteristic->getValue().c_str(),
-          pCharacteristic->getValue().length()
-        );
-    };
-};
-
-class MotorCharCallbacks : public BLECharacteristicCallbacks {
-  private:
-    bh_motor_handler_t motorTransformer;
-
-  public:
-    MotorCharCallbacks(bh_motor_handler_t motorTransformer) : motorTransformer(motorTransformer) {}
-
-    void onWrite(BLECharacteristic* pCharacteristic) override
-    {
-        std::string value = pCharacteristic->getValue();
-
-        this->motorTransformer(value);
-    };
-};
-
-class ConfigCharCallbacks : public BLECharacteristicCallbacks {
-    void onWrite(BLECharacteristic* pCharacteristic) override
-    {
-        auto value = pCharacteristic->getValue();
-
-        if (value.length() != 3) {
-            return;
-        }
-
-        auto byte_0 = value[0], byte_1 = value[1], byte_2 = value[2];
-
-        log_d(">>\tonWrite (Config Char): %3hhu %2hhu %2hhu", byte_0, byte_1, byte_2);
-    };
-};
-
-void BH::ConnectionBHBLE::begin()
-{
-    BLEDevice::init(this->config.deviceName);
-
-    this->callbacks->postInit();
-
-    this->bleServer = BLEDevice::createServer();
-
-    this->bleServer->setCallbacks(new BHServerCallbacks(this->eventDispatcher));
-
-    auto scanResponseData = new BLEAdvertisementData();
-    scanResponseData->setAppearance(this->config.appearance);
-    scanResponseData->setName(this->config.deviceName);
-
-    this->bleServer->getAdvertising()->setAppearance(this->config.appearance);
-    this->bleServer->getAdvertising()->setScanResponseData(*scanResponseData);
-
-    // Each characteristic needs 2 handles and descriptor 1 handle.
-    this->motorService = this->bleServer->createService(BH_BLE_SERVICE_MOTOR_UUID);
-
-    {
-        MotorCharCallbacks* motorCallbacks = new MotorCharCallbacks(this->motorHandler);
-
-        auto* motorChar =
-          this->motorService->createCharacteristic(BH_BLE_SERVICE_MOTOR_CHAR_MOTOR_UUID, PROPERTY_WRITE_NR);
-        motorChar->setCallbacks(motorCallbacks);
-
-        auto* motorCharStable =
-          this->motorService->createCharacteristic(BH_BLE_SERVICE_MOTOR_CHAR_MOTOR_STABLE_UUID, PROPERTY_WRITE);
-        motorCharStable->setCallbacks(motorCallbacks);
-    }
-
-    {
-        auto* configChar = this->motorService->createCharacteristic(
-          BH_BLE_SERVICE_MOTOR_CHAR_CONFIG_UUID,
-          PROPERTY_READ | PROPERTY_WRITE
-        );
-        configChar->setCallbacks(new ConfigCharCallbacks());
-    }
-
-    {
-        auto* serialNumberChar = this->motorService->createCharacteristic(
-          BH_BLE_SERVICE_MOTOR_CHAR_SERIAL_KEY_UUID,
-          PROPERTY_READ | PROPERTY_WRITE
-        );
-        serialNumberChar->setValue(this->config.serialNumber, BH_SERIAL_NUMBER_LENGTH);
-        serialNumberChar->setCallbacks(new SerialOutputCharCallbacks());
-    }
-
-    {
-        this->batteryChar = this->motorService->createCharacteristic(
-          BH_BLE_SERVICE_MOTOR_CHAR_BATTERY_UUID,
-          PROPERTY_READ | PROPERTY_WRITE_NR
-            | PROPERTY_NOTIFY // for whatever reason, it have to be writable, otherwise Desktop app crashes
-        );
-
-#if !defined(BLUETOOTH_USE_NIMBLE) || BLUETOOTH_USE_NIMBLE != true
-        batteryChar->addDescriptor(new BLE2902());
-#endif
-
-        // original bHaptics Player require non-null value for battery level, otherwise it crashes
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
-        uint16_t defaultLevel = 0;
-#else
-        uint16_t defaultLevel = 100;
-#endif
-
-        this->batteryChar->setValue(defaultLevel);
-        // this->batteryChar->notify();
-    }
-
-    {
-        auto* versionChar =
-          this->motorService->createCharacteristic(BH_BLE_SERVICE_MOTOR_CHAR_VERSION_UUID, PROPERTY_READ);
-        versionChar->setCallbacks(new SerialOutputCharCallbacks());
-        uint16_t firmwareVersion = BH_FIRMWARE_VERSION;
-        versionChar->setValue(firmwareVersion);
-    }
-
-    {
-        auto* monitorChar = this->motorService->createCharacteristic(
-          BH_BLE_SERVICE_MOTOR_CHAR_TACTSUIT_MONITOR_UUID,
-          PROPERTY_READ | PROPERTY_WRITE | PROPERTY_NOTIFY | PROPERTY_BROADCAST | PROPERTY_INDICATE | PROPERTY_WRITE_NR
-        );
-        monitorChar->setCallbacks(new SerialOutputCharCallbacks());
-
-#if !defined(BLUETOOTH_USE_NIMBLE) || BLUETOOTH_USE_NIMBLE != true
-        monitorChar->addDescriptor(new BLE2902());
-#endif
-
-        uint16_t audioCableState = NO_AUDIO_CABLE;
-        monitorChar->setValue(audioCableState);
-    }
-
-    // auto* athGlobalChar = this->motorService->createCharacteristic(
-    //     BH_BLE_SERVICE_MOTOR_CHAR_ATH_GLOBAL_CONF_UUID,
-    //     PROPERTY_READ | PROPERTY_WRITE | PROPERTY_NOTIFY | PROPERTY_BROADCAST | PROPERTY_INDICATE | PROPERTY_WRITE_NR
-    // );
-    // athGlobalChar->setCallbacks(new SerialOutputCharCallbacks());
-
-    // auto* athThemeChar = this->motorService->createCharacteristic(
-    //     BH_BLE_SERVICE_MOTOR_CHAR_ATH_THEME_UUID,
-    //     PROPERTY_READ | PROPERTY_WRITE | PROPERTY_NOTIFY | PROPERTY_BROADCAST | PROPERTY_INDICATE | PROPERTY_WRITE_NR
-    // );
-    // athThemeChar->setCallbacks(new SerialOutputCharCallbacks());
-
-    // auto* motorMappingChar = this->motorService->createCharacteristic(
-    //     BH_BLE_SERVICE_MOTOR_CHAR_MOTTOR_MAPPING_UUID,
-    //     PROPERTY_READ | PROPERTY_WRITE | PROPERTY_NOTIFY | PROPERTY_BROADCAST | PROPERTY_INDICATE | PROPERTY_WRITE_NR
-    // );
-    // motorMappingChar->setCallbacks(new SerialOutputCharCallbacks());
-
-    // auto* signatureMappingChar = this->motorService->createCharacteristic(
-    //     BH_BLE_SERVICE_MOTOR_CHAR_SIGNATURE_PATTERN_UUID,
-    //     PROPERTY_READ | PROPERTY_WRITE | PROPERTY_NOTIFY | PROPERTY_BROADCAST | PROPERTY_INDICATE | PROPERTY_WRITE_NR
-    // );
-    // signatureMappingChar->setCallbacks(new SerialOutputCharCallbacks());
-
-    this->motorService->start();
-
-    {
-        auto dfuService = this->bleServer->createService(BH_BLE_SERVICE_DFU_UUID);
-
-        auto* dfuControlChar = dfuService->createCharacteristic(
-          BH_BLE_SERVICE_MOTOR_CHAR_SIGNATURE_PATTERN_UUID,
-          PROPERTY_READ | PROPERTY_WRITE
-        );
-        dfuService->start();
-    }
-
-    this->bleServer->getAdvertising()->start();
-}
diff --git a/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp b/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
new file mode 100644
index 00000000..a4f60bbc
--- /dev/null
+++ b/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
@@ -0,0 +1,260 @@
+#include "senseshift/bh/ble/connection.hpp"
+
+#include <senseshift/bh/constants.hpp>
+
+#include <events.hpp>
+#include <haptic_body.hpp>
+
+#include <Arduino.h>
+
+#if defined(BLUETOOTH_USE_NIMBLE) && BLUETOOTH_USE_NIMBLE == true
+// BLE2902 not needed: https://github.com/h2zero/NimBLE-Arduino/blob/release/1.4/docs/Migration_guide.md#descriptors
+
+#define PROPERTY_READ NIMBLE_PROPERTY::READ
+#define PROPERTY_WRITE NIMBLE_PROPERTY::WRITE
+#define PROPERTY_WRITE_NR NIMBLE_PROPERTY::WRITE_NR
+#define PROPERTY_BROADCAST NIMBLE_PROPERTY::BROADCAST
+#define PROPERTY_NOTIFY NIMBLE_PROPERTY::NOTIFY
+#define PROPERTY_INDICATE NIMBLE_PROPERTY::INDICATE
+#else
+#include <BLE2902.h>
+
+#define PROPERTY_READ BLECharacteristic::PROPERTY_READ
+#define PROPERTY_WRITE BLECharacteristic::PROPERTY_WRITE
+#define PROPERTY_WRITE_NR BLECharacteristic::PROPERTY_WRITE_NR
+#define PROPERTY_BROADCAST BLECharacteristic::PROPERTY_BROADCAST
+#define PROPERTY_NOTIFY BLECharacteristic::PROPERTY_NOTIFY
+#define PROPERTY_INDICATE BLECharacteristic::PROPERTY_INDICATE
+#endif
+
+namespace SenseShift::BH::BLE {
+    class BHServerCallbacks final : public BLEServerCallbacks {
+    private:
+        OH::IEventDispatcher* dispatcher;
+
+    public:
+        BHServerCallbacks(OH::IEventDispatcher* eventDispatcher) : dispatcher(eventDispatcher) {}
+
+        void onConnect(BLEServer* pServer)
+        {
+            this->dispatcher->postEvent(new OH::IEvent(OH_EVENT_CONNECTED));
+        }
+
+        void onDisconnect(BLEServer* pServer)
+        {
+            this->dispatcher->postEvent(new OH::IEvent(OH_EVENT_DISCONNECTED));
+            pServer->startAdvertising();
+        }
+    };
+
+    class LogOutputCharCallbacks : public BLECharacteristicCallbacks {
+        void onWrite(BLECharacteristic* pCharacteristic) override
+        {
+            log_d(
+            ">>\tonWrite (UUID: %s)\n\tvalue: `%s`, len: %u",
+            pCharacteristic->getUUID().toString().c_str(),
+            pCharacteristic->getValue().c_str(),
+            pCharacteristic->getValue().length()
+            );
+        };
+
+        void onRead(BLECharacteristic* pCharacteristic) override
+        {
+            log_d(
+            ">>\tonRead (UUID: %s)\n\tvalue: `%s`, len: %u",
+            pCharacteristic->getUUID().toString().c_str(),
+            pCharacteristic->getValue().c_str(),
+            pCharacteristic->getValue().length()
+            );
+        };
+
+        void onNotify(BLECharacteristic* pCharacteristic) override
+        {
+            log_d(
+            ">>\tonNotify (UUID: %s)\n\tvalue: `%s`, len: %u",
+            pCharacteristic->getUUID().toString().c_str(),
+            pCharacteristic->getValue().c_str(),
+            pCharacteristic->getValue().length()
+            );
+        };
+
+    #if defined(BLUETOOTH_USE_NIMBLE) && BLUETOOTH_USE_NIMBLE == true
+        void onStatus(BLECharacteristic* pCharacteristic, Status s, int code) override
+    #else
+        void onStatus(BLECharacteristic* pCharacteristic, Status s, uint32_t code) override
+    #endif
+        {
+            log_d(
+            ">>\tonNotify (UUID: %s)\n\tstatus: %d, code: %u \n\tvalue: `%s`, len: %u",
+            pCharacteristic->getUUID().toString().c_str(),
+            s,
+            code,
+            pCharacteristic->getValue().c_str(),
+            pCharacteristic->getValue().length()
+            );
+        };
+    };
+
+    class MotorCharCallbacks : public BLECharacteristicCallbacks {
+    private:
+        Connection::MotorHandler_t motorTransformer;
+
+    public:
+        MotorCharCallbacks(Connection::MotorHandler_t motorTransformer) : motorTransformer(motorTransformer) {}
+
+        void onWrite(BLECharacteristic* pCharacteristic) override
+        {
+            std::string value = pCharacteristic->getValue();
+
+            this->motorTransformer(value);
+        };
+    };
+
+    class ConfigCharCallbacks : public BLECharacteristicCallbacks {
+        void onWrite(BLECharacteristic* pCharacteristic) override
+        {
+            auto value = pCharacteristic->getValue();
+
+            if (value.length() != 3) {
+                return;
+            }
+
+            auto byte_0 = value[0], byte_1 = value[1], byte_2 = value[2];
+
+            log_d(">>\tonWrite (Config Char): %3hhu %2hhu %2hhu", byte_0, byte_1, byte_2);
+        };
+    };
+
+    void Connection::begin()
+    {
+        BLEDevice::init(this->config.deviceName);
+
+        this->callbacks->postInit();
+
+        this->bleServer = BLEDevice::createServer();
+
+        this->bleServer->setCallbacks(new BHServerCallbacks(this->eventDispatcher));
+
+        auto scanResponseData = new BLEAdvertisementData();
+        scanResponseData->setAppearance(this->config.appearance);
+        scanResponseData->setName(this->config.deviceName);
+
+        this->bleServer->getAdvertising()->setAppearance(this->config.appearance);
+        this->bleServer->getAdvertising()->setScanResponseData(*scanResponseData);
+
+        // Each characteristic needs 2 handles and descriptor 1 handle.
+        this->motorService = this->bleServer->createService(BH_BLE_SERVICE_MOTOR_UUID);
+
+        {
+            MotorCharCallbacks* motorCallbacks = new MotorCharCallbacks(this->motorHandler);
+
+            auto* motorChar =
+            this->motorService->createCharacteristic(BH_BLE_SERVICE_MOTOR_CHAR_MOTOR_UUID, PROPERTY_WRITE_NR);
+            motorChar->setCallbacks(motorCallbacks);
+
+            auto* motorCharStable =
+            this->motorService->createCharacteristic(BH_BLE_SERVICE_MOTOR_CHAR_MOTOR_STABLE_UUID, PROPERTY_WRITE);
+            motorCharStable->setCallbacks(motorCallbacks);
+        }
+
+        {
+            auto* configChar = this->motorService->createCharacteristic(
+            BH_BLE_SERVICE_MOTOR_CHAR_CONFIG_UUID,
+            PROPERTY_READ | PROPERTY_WRITE
+            );
+            configChar->setCallbacks(new ConfigCharCallbacks());
+        }
+
+        {
+            auto* serialNumberChar = this->motorService->createCharacteristic(
+            BH_BLE_SERVICE_MOTOR_CHAR_SERIAL_KEY_UUID,
+            PROPERTY_READ | PROPERTY_WRITE
+            );
+            serialNumberChar->setValue(this->config.serialNumber, ConnectionConfig_t::SN_LENGTH);
+            serialNumberChar->setCallbacks(new LogOutputCharCallbacks());
+        }
+
+        {
+            this->batteryChar = this->motorService->createCharacteristic(
+            BH_BLE_SERVICE_MOTOR_CHAR_BATTERY_UUID,
+            PROPERTY_READ | PROPERTY_WRITE_NR
+                | PROPERTY_NOTIFY // for whatever reason, it have to be writable, otherwise Desktop app crashes
+            );
+
+    #if !defined(BLUETOOTH_USE_NIMBLE) || BLUETOOTH_USE_NIMBLE != true
+            batteryChar->addDescriptor(new BLE2902());
+    #endif
+
+            // original bHaptics Player require non-null value for battery level, otherwise it crashes
+    #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
+            uint16_t defaultLevel = 0;
+    #else
+            uint16_t defaultLevel = 100;
+    #endif
+
+            this->batteryChar->setValue(defaultLevel);
+            // this->batteryChar->notify();
+        }
+
+        {
+            auto* versionChar =
+            this->motorService->createCharacteristic(BH_BLE_SERVICE_MOTOR_CHAR_VERSION_UUID, PROPERTY_READ);
+            versionChar->setCallbacks(new LogOutputCharCallbacks());
+            uint16_t firmwareVersion = BH_FIRMWARE_VERSION;
+            versionChar->setValue(firmwareVersion);
+        }
+
+        {
+            auto* monitorChar = this->motorService->createCharacteristic(
+            BH_BLE_SERVICE_MOTOR_CHAR_TACTSUIT_MONITOR_UUID,
+            PROPERTY_READ | PROPERTY_WRITE | PROPERTY_NOTIFY | PROPERTY_BROADCAST | PROPERTY_INDICATE | PROPERTY_WRITE_NR
+            );
+            monitorChar->setCallbacks(new LogOutputCharCallbacks());
+
+    #if !defined(BLUETOOTH_USE_NIMBLE) || BLUETOOTH_USE_NIMBLE != true
+            monitorChar->addDescriptor(new BLE2902());
+    #endif
+
+            uint16_t audioCableState = NO_AUDIO_CABLE;
+            monitorChar->setValue(audioCableState);
+        }
+
+        // auto* athGlobalChar = this->motorService->createCharacteristic(
+        //     BH_BLE_SERVICE_MOTOR_CHAR_ATH_GLOBAL_CONF_UUID,
+        //     PROPERTY_READ | PROPERTY_WRITE | PROPERTY_NOTIFY | PROPERTY_BROADCAST | PROPERTY_INDICATE | PROPERTY_WRITE_NR
+        // );
+        // athGlobalChar->setCallbacks(new LogOutputCharCallbacks());
+
+        // auto* athThemeChar = this->motorService->createCharacteristic(
+        //     BH_BLE_SERVICE_MOTOR_CHAR_ATH_THEME_UUID,
+        //     PROPERTY_READ | PROPERTY_WRITE | PROPERTY_NOTIFY | PROPERTY_BROADCAST | PROPERTY_INDICATE | PROPERTY_WRITE_NR
+        // );
+        // athThemeChar->setCallbacks(new LogOutputCharCallbacks());
+
+        // auto* motorMappingChar = this->motorService->createCharacteristic(
+        //     BH_BLE_SERVICE_MOTOR_CHAR_MOTTOR_MAPPING_UUID,
+        //     PROPERTY_READ | PROPERTY_WRITE | PROPERTY_NOTIFY | PROPERTY_BROADCAST | PROPERTY_INDICATE | PROPERTY_WRITE_NR
+        // );
+        // motorMappingChar->setCallbacks(new LogOutputCharCallbacks());
+
+        // auto* signatureMappingChar = this->motorService->createCharacteristic(
+        //     BH_BLE_SERVICE_MOTOR_CHAR_SIGNATURE_PATTERN_UUID,
+        //     PROPERTY_READ | PROPERTY_WRITE | PROPERTY_NOTIFY | PROPERTY_BROADCAST | PROPERTY_INDICATE | PROPERTY_WRITE_NR
+        // );
+        // signatureMappingChar->setCallbacks(new LogOutputCharCallbacks());
+
+        this->motorService->start();
+
+        {
+            auto dfuService = this->bleServer->createService(BH_BLE_SERVICE_DFU_UUID);
+
+            auto* dfuControlChar = dfuService->createCharacteristic(
+            BH_BLE_SERVICE_MOTOR_CHAR_SIGNATURE_PATTERN_UUID,
+            PROPERTY_READ | PROPERTY_WRITE
+            );
+            dfuService->start();
+        }
+
+        this->bleServer->getAdvertising()->start();
+    }
+}
diff --git a/lib/bhaptics_ble/connection_bhble.hpp b/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
similarity index 67%
rename from lib/bhaptics_ble/connection_bhble.hpp
rename to lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
index 7645000f..f4d10f19 100644
--- a/lib/bhaptics_ble/connection_bhble.hpp
+++ b/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
@@ -3,8 +3,8 @@
 #include <abstract_connection.hpp>
 #include <utility.hpp>
 
-#include <bh_constants.hpp>
-#include <bh_types.hpp>
+#include <senseshift/bh/constants.hpp>
+#include <senseshift/bh/ble/constants.hpp>
 
 #include <Arduino.h>
 #include <esp_wifi.h>
@@ -19,45 +19,30 @@
 #include <abstract_battery.hpp>
 #endif
 
-// typedef void (*bh_motor_handler_t)(std::string&);
-typedef std::function<void(std::string&)> bh_motor_handler_t;
+namespace SenseShift::BH::BLE {
+    typedef struct ConnectionConfig {
+        static constexpr size_t SN_LENGTH = 10;
 
-namespace BH {
-    class BHBLEConnectionCallbacks {
+        std::string deviceName;
+        uint16_t appearance;
+        uint8_t serialNumber[SN_LENGTH];
+    } ConnectionConfig_t;
+
+    class ConnectionCallbacks {
       public:
         virtual void postInit()
         {
             log_v("Default postInit");
         };
     };
+    static ConnectionCallbacks defaultCallback;
 
-    static BHBLEConnectionCallbacks defaultCallback;
-
-    struct ConnectionBHBLE_Config {
-        std::string deviceName;
-        uint16_t appearance;
-        uint8_t* serialNumber;
-    };
-
-    class ConnectionBHBLE final : public OH::AbstractConnection, public OH::IEventListener {
-      private:
-        ConnectionBHBLE_Config config;
-        bh_motor_handler_t motorHandler;
-        OH::IEventDispatcher* eventDispatcher;
-
-        BLEServer* bleServer = nullptr;
-        BLEService* motorService = nullptr;
-        BLECharacteristic* batteryChar = nullptr;
-
-        BHBLEConnectionCallbacks* callbacks = &defaultCallback;
-
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
-        void handleBatteryChange(const OH::BatteryLevelEvent* event) const;
-#endif
-
+    class Connection final : public OH::AbstractConnection, public OH::IEventListener {
       public:
-        ConnectionBHBLE(
-          ConnectionBHBLE_Config& config, bh_motor_handler_t motorHandler, OH::IEventDispatcher* eventDispatcher
+        typedef std::function<void(std::string&)> MotorHandler_t;
+
+        Connection(
+          const ConnectionConfig_t& config, MotorHandler_t motorHandler, OH::IEventDispatcher* eventDispatcher
         ) :
           config(config), motorHandler(motorHandler), eventDispatcher(eventDispatcher)
         {
@@ -80,7 +65,7 @@ namespace BH {
 #endif
         };
 
-        void setCallbacks(BHBLEConnectionCallbacks* pCallbacks)
+        void setCallbacks(ConnectionCallbacks* pCallbacks)
         {
             if (pCallbacks != nullptr) {
                 this->callbacks = pCallbacks;
@@ -88,5 +73,20 @@ namespace BH {
                 this->callbacks = &defaultCallback;
             }
         };
+
+      private:
+        const ConnectionConfig_t& config;
+        MotorHandler_t motorHandler;
+        OH::IEventDispatcher* eventDispatcher;
+
+        BLEServer* bleServer = nullptr;
+        BLEService* motorService = nullptr;
+        BLECharacteristic* batteryChar = nullptr;
+
+        ConnectionCallbacks* callbacks = &defaultCallback;
+
+#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
+        void handleBatteryChange(const OH::BatteryLevelEvent* event) const;
+#endif
     };
-} // namespace BH
+} // namespace SenseShift::BH::BLE
diff --git a/lib/bhaptics_ble/senseshift/bh/ble/constants.hpp b/lib/bhaptics_ble/senseshift/bh/ble/constants.hpp
new file mode 100644
index 00000000..64d3ebd4
--- /dev/null
+++ b/lib/bhaptics_ble/senseshift/bh/ble/constants.hpp
@@ -0,0 +1,59 @@
+#pragma once
+
+#include <BLEUUID.h>
+
+// All below are weird choices of bHaptics engineers...
+// Why to use unconventional UUIDs, that are reserved for other purposes?
+// You have an unlimited amount of other UUIDs
+
+/**
+ * Main service for bHaptics BLE devices.
+ *
+ * Nordic UART Service (NUS)
+ * @see https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/libraries/bluetooth_services/services/nus.html
+ */
+#define BH_BLE_SERVICE_MOTOR_UUID BLEUUID("6e400001-b5a3-f393-e0a9-e50e24dcca9e")
+
+/**
+ * Legacy Characteristic for driving haptic feedback.
+ *
+ * Nordic UART RX
+ * @see https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/libraries/bluetooth_services/services/nus.html
+ */
+#define BH_BLE_SERVICE_MOTOR_CHAR_MOTOR_UUID BLEUUID("6e400002-b5a3-f393-e0a9-e50e24dcca9e")
+
+/**
+ * Characteristic for Device S/N.
+ *
+ * Nordic UART TX
+ * @see https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/libraries/bluetooth_services/services/nus.html
+ */
+#define BH_BLE_SERVICE_MOTOR_CHAR_SERIAL_KEY_UUID BLEUUID("6e400003-b5a3-f393-e0a9-e50e24dcca9e")
+
+// Glow Color
+#define BH_BLE_SERVICE_MOTOR_CHAR_CONFIG_UUID BLEUUID("6e400005-b5a3-f393-e0a9-e50e24dcca9e")
+#define BH_BLE_SERVICE_MOTOR_CHAR_VERSION_UUID BLEUUID("6e400007-b5a3-f393-e0a9-e50e24dcca9e")
+#define BH_BLE_SERVICE_MOTOR_CHAR_BATTERY_UUID BLEUUID("6e400008-b5a3-f393-e0a9-e50e24dcca9e")
+
+// Current Characteristic for driving haptic feedback
+#define BH_BLE_SERVICE_MOTOR_CHAR_MOTOR_STABLE_UUID BLEUUID("6e40000a-b5a3-f393-e0a9-e50e24dcca9e")
+#define BH_BLE_SERVICE_MOTOR_CHAR_TACTSUIT_MONITOR_UUID BLEUUID("6e40000b-b5a3-f393-e0a9-e50e24dcca9e")
+
+// Audio-to-Haptic
+#define BH_BLE_SERVICE_MOTOR_CHAR_ATH_GLOBAL_CONF_UUID BLEUUID("6e40000c-b5a3-f393-e0a9-e50e24dcca9e")
+// Audio-to-Haptic
+#define BH_BLE_SERVICE_MOTOR_CHAR_ATH_THEME_UUID BLEUUID("6e40000d-b5a3-f393-e0a9-e50e24dcca9e")
+
+#define BH_BLE_SERVICE_MOTOR_CHAR_MOTTOR_MAPPING_UUID BLEUUID("6e40000e-b5a3-f393-e0a9-e50e24dcca9e")
+#define BH_BLE_SERVICE_MOTOR_CHAR_SIGNATURE_PATTERN_UUID BLEUUID("6e40000f-b5a3-f393-e0a9-e50e24dcca9e")
+
+/**
+ * Firmware update service
+ * @see https://infocenter.nordicsemi.com/topic/ug_nrfconnect_ble/UG/nRF_Connect_BLE/nRF_Connect_DFU.html
+ */
+#define BH_BLE_SERVICE_DFU_UUID BLEUUID("0000fe59-0000-1000-8000-00805f9b34fb")
+/**
+ * Firmware update control characteristic
+ * @see https://infocenter.nordicsemi.com/topic/ug_nrfconnect_ble/UG/nRF_Connect_BLE/nRF_Connect_DFU.html
+ */
+#define BH_BLE_SERVICE_DFU_CHAR_CONTROL_UUID BLEUUID("8ec90003-f315-4f60-9fb8-838830daea50")
diff --git a/test/test_bhaptics_encoding/main.cpp b/test/test_bhaptics_encoding/main.cpp
index 7f153779..332fb25d 100644
--- a/test/test_bhaptics_encoding/main.cpp
+++ b/test/test_bhaptics_encoding/main.cpp
@@ -1,6 +1,6 @@
-#include <bh_types.hpp>
-#include <bh_encoding.hpp>
 #include <bh_devices.hpp>
+#include <bh_encoding.hpp>
+#include <bh_types.hpp>
 
 #include <unity.h>
 
@@ -224,12 +224,12 @@ void test_layout_tactglove(void)
 
     const auto& bhLayout = BH::TactGloveLeftLayout;
 
-    auto thumb = new VibroPlane({{ std::get<2>(bhLayout[0]), actuatorThumb }});
-    auto index = new VibroPlane({{ std::get<2>(bhLayout[1]), actuatorIndex }});
-    auto middle = new VibroPlane({{ std::get<2>(bhLayout[2]), actuatorMiddle }});
-    auto ring = new VibroPlane({{ std::get<2>(bhLayout[3]), actuatorRing }});
-    auto little = new VibroPlane({{ std::get<2>(bhLayout[4]), actuatorLittle }});
-    auto wrist = new VibroPlane({{ std::get<2>(bhLayout[5]), actuatorWrist }});
+    auto thumb = new VibroPlane({ { std::get<2>(bhLayout[0]), actuatorThumb } });
+    auto index = new VibroPlane({ { std::get<2>(bhLayout[1]), actuatorIndex } });
+    auto middle = new VibroPlane({ { std::get<2>(bhLayout[2]), actuatorMiddle } });
+    auto ring = new VibroPlane({ { std::get<2>(bhLayout[3]), actuatorRing } });
+    auto little = new VibroPlane({ { std::get<2>(bhLayout[4]), actuatorLittle } });
+    auto wrist = new VibroPlane({ { std::get<2>(bhLayout[5]), actuatorWrist } });
 
     auto body = new HapticBody();
 

From 445efe758f3aa0ea063d996b0dd1393098119dd5 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Mon, 21 Aug 2023 18:56:30 +0400
Subject: [PATCH 07/82] refactor(bHaptics): use updated haptics core

---
 .vscode/settings.json                         |  12 +-
 examples/bhaptics-ble-bt-serial.cpp           |   2 +-
 firmware/mode_configs/bhaptics/tactal.cpp     |  17 +-
 firmware/mode_configs/bhaptics/tactglove.cpp  |  53 ++--
 firmware/mode_configs/bhaptics/tactosy2.cpp   |  33 ++-
 firmware/mode_configs/bhaptics/tactosyf.cpp   |  29 +-
 firmware/mode_configs/bhaptics/tactosyh.cpp   |  35 ++-
 .../mode_configs/bhaptics/tactsuit_x16.cpp    |  41 ++-
 .../bhaptics/tactsuit_x16_pca9685.cpp         |  41 ++-
 .../mode_configs/bhaptics/tactsuit_x40.cpp    |  53 ++--
 firmware/mode_configs/bhaptics/tactvisor.cpp  |  31 +--
 firmware/senseshift.cpp                       |   2 +-
 include/senseshift.h                          |   6 +-
 ini/bhaptics.ini                              |   4 +-
 lib/arduino/components/serial_plotter.cpp     |   2 +-
 lib/arduino/components/serial_plotter.hpp     |   3 +-
 lib/bhaptics/senseshift/bh/devices.hpp        | 262 +++++++++++-------
 lib/bhaptics/senseshift/bh/encoding.hpp       |  41 +--
 .../senseshift/bh/ble/connection.cpp          | 105 +++----
 .../senseshift/bh/ble/connection.hpp          |   2 +-
 lib/core/types.hpp                            |   7 -
 lib/hands/hand_interface.hpp                  |  12 +-
 lib/haptics/haptic_body.hpp                   |   6 +-
 lib/haptics/haptic_plane.hpp                  |   4 +-
 lib/haptics/haptics_interface.hpp             |   4 +-
 test/test_bhaptics_encoding/main.cpp          |  45 ++-
 26 files changed, 451 insertions(+), 401 deletions(-)

diff --git a/.vscode/settings.json b/.vscode/settings.json
index 56a04ab3..1af45755 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -59,7 +59,17 @@
         "xtree": "cpp",
         "xhash": "cpp",
         "xlocnum": "cpp",
-        "ledc.h": "c"
+        "ledc.h": "c",
+        "variant": "cpp",
+        "bitset": "cpp",
+        "chrono": "cpp",
+        "condition_variable": "cpp",
+        "csignal": "cpp",
+        "ratio": "cpp",
+        "regex": "cpp",
+        "future": "cpp",
+        "mutex": "cpp",
+        "thread": "cpp"
     },
     "files.eol": "\n",
     "files.insertFinalNewline": true,
diff --git a/examples/bhaptics-ble-bt-serial.cpp b/examples/bhaptics-ble-bt-serial.cpp
index 4388d41b..d0c42d95 100644
--- a/examples/bhaptics-ble-bt-serial.cpp
+++ b/examples/bhaptics-ble-bt-serial.cpp
@@ -23,7 +23,7 @@ BluetoothSerial SerialBT;
 BluetoothSerial* btSerial = &SerialBT;
 
 static const size_t bhLayoutSize = BH_LAYOUT_TACTAL_SIZE;
-static const oh_output_point_t* bhLayout[bhLayoutSize] = BH_LAYOUT_TACTAL;
+static const ::SenseShift::Body::Haptics::Position_t* bhLayout[bhLayoutSize] = BH_LAYOUT_TACTAL;
 
 class BLECallbacks : public BHBLEConnectionCallbacks {
   public:
diff --git a/firmware/mode_configs/bhaptics/tactal.cpp b/firmware/mode_configs/bhaptics/tactal.cpp
index 89b3a620..de7f9560 100644
--- a/firmware/mode_configs/bhaptics/tactal.cpp
+++ b/firmware/mode_configs/bhaptics/tactal.cpp
@@ -6,10 +6,10 @@
 
 #include "senseshift.h"
 
+#include <output_writers/pwm.hpp>
+#include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
-#include <senseshift/bh/ble/connection.hpp>
-#include <output_writers/pwm.hpp>
 
 #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
 #include "battery/adc_naive.hpp"
@@ -23,19 +23,18 @@ extern SenseShift::SenseShift App;
 SenseShift::SenseShift* app = &App;
 
 static const size_t bhLayoutSize = BH_LAYOUT_TACTAL_SIZE;
-static const oh_output_point_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTAL;
+static const Position_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTAL;
 
 void setupMode()
 {
     // Configure PWM pins to their positions on the face
-    auto faceOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
+    const auto faceOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
       // clang-format off
-      {new PWMOutputWriter(32), new PWMOutputWriter(33), new PWMOutputWriter(25), new PWMOutputWriter(26), new PWMOutputWriter(27), new PWMOutputWriter(14)},
+      { new PWMOutputWriter(32), new PWMOutputWriter(33), new PWMOutputWriter(25), new PWMOutputWriter(26), new PWMOutputWriter(27), new PWMOutputWriter(14) },
       // clang-format on
     });
 
-    auto* face = new VibroPlane(faceOutputs);
-    app->getHapticBody()->addTarget(Target::FaceFront, face);
+    app->getHapticBody()->addTarget(Target::FaceFront, new VibroPlane_Closest(faceOutputs));
 
     app->getHapticBody()->setup();
 
@@ -44,9 +43,9 @@ void setupMode()
         .deviceName = BLUETOOTH_NAME,
         .appearance = BH_BLE_APPEARANCE,
         .serialNumber = BH_SERIAL_NUMBER,
-        },
+      },
       [](std::string& value) -> void {
-        BH::Decoder::applyPlain<6>(app->getHapticBody(), value, bhLayout, Effect::Vibro, Target::FaceFront);
+          BH::Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect::Vibro, Target::FaceFront);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactglove.cpp b/firmware/mode_configs/bhaptics/tactglove.cpp
index cb4cbea8..14acadd8 100644
--- a/firmware/mode_configs/bhaptics/tactglove.cpp
+++ b/firmware/mode_configs/bhaptics/tactglove.cpp
@@ -8,58 +8,53 @@
 
 #include "senseshift.h"
 
-#include <bh_encoding.hpp>
-#include <bh_devices.hpp>
-#include <connection_bhble.hpp>
 #include <output_writers/pwm.hpp>
+#include <senseshift/bh/ble/connection.hpp>
+#include <senseshift/bh/devices.hpp>
+#include <senseshift/bh/encoding.hpp>
 
 #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
 #include <battery/adc_naive.hpp>
 #endif
 
-#ifndef BH_LAYOUT
-#define BH_LAYOUT BH::TactGloveLeftLayout
-#endif
-
 using namespace OH;
 using namespace SenseShift;
 using namespace SenseShift::Body::Haptics;
-using namespace BH;
 
 extern SenseShift::SenseShift App;
 SenseShift::SenseShift* app = &App;
 
-const auto& bhLayout = BH_LAYOUT;
+static const Body::Hands::HandSide_t handSide = Body::Hands::HandSide::SENSESHIFT_HAND_SIDE;
+static const size_t bhLayoutSize = BH_LAYOUT_TACTGLOVE_SIZE;
+// clang-format off
+static const BH::OutputLayout_t (&bhLayout)[bhLayoutSize] = handSide == Body::Hands::HandSide::Left ? BH::TactGloveLeftLayout : BH::TactGloveRightLayout;
+// clang-format on
 
 void setupMode()
 {
     // Configure PWM pins to their positions on the glove
-    auto* motorThumb = new PWMOutputWriter(33); // Thumb
-    auto* motorIndex = new PWMOutputWriter(32); // Index
-    auto* motorMiddle = new PWMOutputWriter(25); // Middle
-    auto* motorRing = new PWMOutputWriter(26); // Ring
-    auto* motorLittle = new PWMOutputWriter(27); // Little
-    auto* motorWrist = new PWMOutputWriter(14); // Wrist
-
-    app->getHapticBody()->addTarget(std::get<0>(bhLayout[0]), new VibroPlane({{ std::get<1>(bhLayout[0]), motorThumb }}));
-    app->getHapticBody()->addTarget(std::get<0>(bhLayout[1]), new VibroPlane({{ std::get<1>(bhLayout[1]), motorIndex }}));
-    app->getHapticBody()->addTarget(std::get<0>(bhLayout[2]), new VibroPlane({{ std::get<1>(bhLayout[2]), motorMiddle }}));
-    app->getHapticBody()->addTarget(std::get<0>(bhLayout[3]), new VibroPlane({{ std::get<1>(bhLayout[3]), motorRing }}));
-    app->getHapticBody()->addTarget(std::get<0>(bhLayout[4]), new VibroPlane({{ std::get<1>(bhLayout[4]), motorLittle }}));
-    app->getHapticBody()->addTarget(std::get<0>(bhLayout[5]), new VibroPlane({{ std::get<1>(bhLayout[5]), motorWrist }}));
+    // Replace `new PWMOutputWriter(...)` with `nullptr` to disable a specific actuator
+    BH::addTactGloveActuators(
+      app->getHapticBody(),
+      handSide,
+      new PWMOutputWriter(32), // Thumb
+      new PWMOutputWriter(33), // Index
+      new PWMOutputWriter(25), // Middle
+      new PWMOutputWriter(26), // Ring
+      new PWMOutputWriter(27), // Little
+      new PWMOutputWriter(14)  // Wrist
+    );
 
     app->getHapticBody()->setup();
 
-    uint8_t serialNumber[BH_SERIAL_NUMBER_LENGTH] = BH_SERIAL_NUMBER;
-    ConnectionBHBLE_Config config = {
+    auto* bhBleConnection = new BH::BLE::Connection(
+      {
         .deviceName = BLUETOOTH_NAME,
         .appearance = BH_BLE_APPEARANCE,
-        .serialNumber = serialNumber,
-    };
-    auto* bhBleConnection = new ConnectionBHBLE(
-      config,
+        .serialNumber = BH_SERIAL_NUMBER,
+      },
       [](std::string& value) -> void {
-        Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect_t::Vibro);
+          BH::Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect_t::Vibro);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactosy2.cpp b/firmware/mode_configs/bhaptics/tactosy2.cpp
index e42ea4a9..fb6bd77f 100644
--- a/firmware/mode_configs/bhaptics/tactosy2.cpp
+++ b/firmware/mode_configs/bhaptics/tactosy2.cpp
@@ -6,48 +6,47 @@
 
 #include "senseshift.h"
 
-#include <bh_utils.hpp>
-#include <connection_bhble.hpp>
 #include <output_writers/pwm.hpp>
+#include <senseshift/bh/ble/connection.hpp>
+#include <senseshift/bh/devices.hpp>
+#include <senseshift/bh/encoding.hpp>
 
 #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
 #include <battery/adc_naive.hpp>
 #endif
 
 using namespace OH;
-using namespace BH;
+using namespace SenseShift;
+using namespace SenseShift::Body::Haptics;
 
-extern SenseShift App;
-SenseShift* app = &App;
+extern SenseShift::SenseShift App;
+SenseShift::SenseShift* app = &App;
 
 static const size_t bhLayoutSize = BH_LAYOUT_TACTOSY2_SIZE;
-static const oh_output_point_t* bhLayout[bhLayoutSize] = BH_LAYOUT_TACTOSY2;
+static const Position_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTOSY2;
 
 void setupMode()
 {
     // Configure PWM pins to their positions on the forearm
     auto forearmOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
       // clang-format off
-      {new PWMOutputWriter(32), new PWMOutputWriter(33), new PWMOutputWriter(25)},
-      {new PWMOutputWriter(26), new PWMOutputWriter(27), new PWMOutputWriter(14)},
+      { new PWMOutputWriter(32), new PWMOutputWriter(33), new PWMOutputWriter(25) },
+      { new PWMOutputWriter(26), new PWMOutputWriter(27), new PWMOutputWriter(14) },
       // clang-format on
     });
 
-    auto* forearm = new HapticPlane_Closest(forearmOutputs);
-    app->getHapticBody()->addComponent(OUTPUT_PATH_ACCESSORY, forearm);
+    app->getHapticBody()->addTarget(Target::Accessory, new VibroPlane_Closest(forearmOutputs));
 
     app->getHapticBody()->setup();
 
-    uint8_t serialNumber[BH_SERIAL_NUMBER_LENGTH] = BH_SERIAL_NUMBER;
-    ConnectionBHBLE_Config config = {
+    auto* bhBleConnection = new BH::BLE::Connection(
+      {
         .deviceName = BLUETOOTH_NAME,
         .appearance = BH_BLE_APPEARANCE,
-        .serialNumber = serialNumber,
-    };
-    auto* bhBleConnection = new ConnectionBHBLE(
-      config,
+        .serialNumber = BH_SERIAL_NUMBER,
+      },
       [](std::string& value) -> void {
-          plainOutputTransformer(app->getHapticBody(), value, bhLayout, bhLayoutSize, OUTPUT_PATH_ACCESSORY);
+          BH::Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect::Vibro, Target::Accessory);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactosyf.cpp b/firmware/mode_configs/bhaptics/tactosyf.cpp
index 66629aa9..277377ae 100644
--- a/firmware/mode_configs/bhaptics/tactosyf.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyf.cpp
@@ -6,22 +6,24 @@
 
 #include "senseshift.h"
 
-#include <bh_utils.hpp>
-#include <connection_bhble.hpp>
 #include <output_writers/pwm.hpp>
+#include <senseshift/bh/ble/connection.hpp>
+#include <senseshift/bh/devices.hpp>
+#include <senseshift/bh/encoding.hpp>
 
 #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
 #include <battery/adc_naive.hpp>
 #endif
 
 using namespace OH;
-using namespace BH;
+using namespace SenseShift;
+using namespace SenseShift::Body::Haptics;
 
-extern SenseShift App;
-SenseShift* app = &App;
+extern SenseShift::SenseShift App;
+SenseShift::SenseShift* app = &App;
 
 static const size_t bhLayoutSize = BH_LAYOUT_TACTOSYF_SIZE;
-static const oh_output_point_t* bhLayout[bhLayoutSize] = BH_LAYOUT_TACTOSYF;
+static const Position_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTOSYF;
 
 void setupMode()
 {
@@ -34,21 +36,18 @@ void setupMode()
       // clang-format on
     });
 
-    auto* foot = new HapticPlane_Closest(footOutputs);
-    app->getHapticBody()->addComponent(OUTPUT_PATH_ACCESSORY, foot);
+    app->getHapticBody()->addTarget(Target::Accessory, new VibroPlane_Closest(footOutputs));
 
     app->getHapticBody()->setup();
 
-    uint8_t serialNumber[BH_SERIAL_NUMBER_LENGTH] = BH_SERIAL_NUMBER;
-    ConnectionBHBLE_Config config = {
+    auto* bhBleConnection = new BH::BLE::Connection(
+      {
         .deviceName = BLUETOOTH_NAME,
         .appearance = BH_BLE_APPEARANCE,
-        .serialNumber = serialNumber,
-    };
-    auto* bhBleConnection = new ConnectionBHBLE(
-      config,
+        .serialNumber = BH_SERIAL_NUMBER,
+      },
       [](std::string& value) -> void {
-          plainOutputTransformer(app->getHapticBody(), value, bhLayout, bhLayoutSize, OUTPUT_PATH_ACCESSORY);
+          BH::Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect::Vibro, Target::Accessory);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactosyh.cpp b/firmware/mode_configs/bhaptics/tactosyh.cpp
index 967b1f84..543f2275 100644
--- a/firmware/mode_configs/bhaptics/tactosyh.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyh.cpp
@@ -6,49 +6,48 @@
 
 #include "senseshift.h"
 
-#include <bh_utils.hpp>
-#include <connection_bhble.hpp>
 #include <output_writers/pwm.hpp>
+#include <senseshift/bh/ble/connection.hpp>
+#include <senseshift/bh/devices.hpp>
+#include <senseshift/bh/encoding.hpp>
 
 #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
 #include <battery/adc_naive.hpp>
 #endif
 
 using namespace OH;
-using namespace BH;
+using namespace SenseShift;
+using namespace SenseShift::Body::Haptics;
 
-extern SenseShift App;
-SenseShift* app = &App;
+extern SenseShift::SenseShift App;
+SenseShift::SenseShift* app = &App;
 
 static const size_t bhLayoutSize = BH_LAYOUT_TACTOSYH_SIZE;
-static const oh_output_point_t* bhLayout[bhLayoutSize] = BH_LAYOUT_TACTOSYH;
+static const Position_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTOSYH;
 
 void setupMode()
 {
     // Configure PWM pins to their positions on the hands
     auto handOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
       // clang-format off
-      {new PWMOutputWriter(32)},
-      {new PWMOutputWriter(33)},
-      {new PWMOutputWriter(25)}
+      { new PWMOutputWriter(32) },
+      { new PWMOutputWriter(33) },
+      { new PWMOutputWriter(25) }
       // clang-format on
     });
 
-    auto* hand = new HapticPlane_Closest(handOutputs);
-    app->getHapticBody()->addComponent(OUTPUT_PATH_ACCESSORY, hand);
+    app->getHapticBody()->addTarget(Target::Accessory, new VibroPlane_Closest(handOutputs));
 
     app->getHapticBody()->setup();
 
-    uint8_t serialNumber[BH_SERIAL_NUMBER_LENGTH] = BH_SERIAL_NUMBER;
-    ConnectionBHBLE_Config config = {
+    auto* bhBleConnection = new BH::BLE::Connection(
+      {
         .deviceName = BLUETOOTH_NAME,
         .appearance = BH_BLE_APPEARANCE,
-        .serialNumber = serialNumber,
-    };
-    auto* bhBleConnection = new ConnectionBHBLE(
-      config,
+        .serialNumber = BH_SERIAL_NUMBER,
+      },
       [](std::string& value) -> void {
-          plainOutputTransformer(app->getHapticBody(), value, bhLayout, bhLayoutSize, OUTPUT_PATH_ACCESSORY);
+          BH::Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect::Vibro, Target::Accessory);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
index a1a9ba1f..4b4cf7d2 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
@@ -6,22 +6,24 @@
 
 #include "senseshift.h"
 
-#include <bh_utils.hpp>
-#include <connection_bhble.hpp>
 #include <output_writers/pwm.hpp>
+#include <senseshift/bh/ble/connection.hpp>
+#include <senseshift/bh/devices.hpp>
+#include <senseshift/bh/encoding.hpp>
 
 #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
 #include <battery/adc_naive.hpp>
 #endif
 
 using namespace OH;
-using namespace BH;
+using namespace SenseShift;
+using namespace SenseShift::Body::Haptics;
 
-extern SenseShift App;
-SenseShift* app = &App;
+extern SenseShift::SenseShift App;
+SenseShift::SenseShift* app = &App;
 
-static const oh_output_point_t* bhLayout[] = BH_LAYOUT_TACTSUITX16;
 static const size_t bhLayoutSize = BH_LAYOUT_TACTSUITX16_SIZE;
+static const BH::OutputLayout_t bhLayout[BH_LAYOUT_TACTSUITX16_SIZE] = BH_LAYOUT_TACTSUITX16;
 
 // Ouput indices, responsible for x40 => x16 grouping
 static const size_t layoutGroupsSize = BH_LAYOUT_TACTSUITX16_GROUPS_SIZE;
@@ -32,35 +34,30 @@ void setupMode()
     // Configure PWM pins to their positions on the vest
     auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
       // clang-format off
-      {new PWMOutputWriter(32), new PWMOutputWriter(33), new PWMOutputWriter(25), new PWMOutputWriter(26)},
-      {new PWMOutputWriter(27), new PWMOutputWriter(14), new PWMOutputWriter(12), new PWMOutputWriter(13)},
+      { new PWMOutputWriter(32), new PWMOutputWriter(33), new PWMOutputWriter(25), new PWMOutputWriter(26) },
+      { new PWMOutputWriter(27), new PWMOutputWriter(14), new PWMOutputWriter(12), new PWMOutputWriter(13) },
       // clang-format on
     });
     auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
       // clang-format off
-      {new PWMOutputWriter(19), new PWMOutputWriter(18), new PWMOutputWriter(5), new PWMOutputWriter(17)},
-      {new PWMOutputWriter(16), new PWMOutputWriter(4), new PWMOutputWriter(2), new PWMOutputWriter(15)},
+      { new PWMOutputWriter(19), new PWMOutputWriter(18), new PWMOutputWriter(5), new PWMOutputWriter(17) },
+      { new PWMOutputWriter(16), new PWMOutputWriter(4), new PWMOutputWriter(2), new PWMOutputWriter(15) },
       // clang-format on
     });
 
-    auto* chestFront = new HapticPlane_Closest(frontOutputs);
-    auto* chestBack = new HapticPlane_Closest(backOutputs);
-
-    app->getHapticBody()->addComponent(OUTPUT_PATH_CHEST_FRONT, chestFront);
-    app->getHapticBody()->addComponent(OUTPUT_PATH_CHEST_BACK, chestBack);
+    app->getHapticBody()->addTarget(Target::ChestFront, new VibroPlane_Closest(frontOutputs));
+    app->getHapticBody()->addTarget(Target::ChestBack, new VibroPlane_Closest(backOutputs));
 
     app->getHapticBody()->setup();
 
-    uint8_t serialNumber[BH_SERIAL_NUMBER_LENGTH] = BH_SERIAL_NUMBER;
-    ConnectionBHBLE_Config config = {
+    auto* bhBleConnection = new BH::BLE::Connection(
+      {
         .deviceName = BLUETOOTH_NAME,
         .appearance = BH_BLE_APPEARANCE,
-        .serialNumber = serialNumber,
-    };
-    auto* bhBleConnection = new ConnectionBHBLE(
-      config,
+        .serialNumber = BH_SERIAL_NUMBER,
+      },
       [](std::string& value) -> void {
-          vestX16OutputTransformer(app->getHapticBody(), value, bhLayout, bhLayoutSize, layoutGroups, layoutGroupsSize);
+          BH::Decoder::applyVestGrouped(app->getHapticBody(), value, bhLayout, layoutGroups);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
index fbe1e567..b9051e9e 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
@@ -6,22 +6,24 @@
 
 #include "senseshift.h"
 
-#include <bh_utils.hpp>
-#include <connection_bhble.hpp>
 #include <output_writers/pca9685.hpp>
+#include <senseshift/bh/ble/connection.hpp>
+#include <senseshift/bh/devices.hpp>
+#include <senseshift/bh/encoding.hpp>
 
 #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
 #include <battery/adc_naive.hpp>
 #endif
 
 using namespace OH;
-using namespace BH;
+using namespace SenseShift;
+using namespace SenseShift::Body::Haptics;
 
-extern SenseShift App;
-SenseShift* app = &App;
+extern SenseShift::SenseShift App;
+SenseShift::SenseShift* app = &App;
 
-static const oh_output_point_t* bhLayout[] = BH_LAYOUT_TACTSUITX16;
 static const size_t bhLayoutSize = BH_LAYOUT_TACTSUITX16_SIZE;
+static const BH::OutputLayout_t bhLayout[BH_LAYOUT_TACTSUITX16_SIZE] = BH_LAYOUT_TACTSUITX16;
 
 // Ouput indices, responsible for x40 => x16 grouping
 static const size_t layoutGroupsSize = BH_LAYOUT_TACTSUITX16_GROUPS_SIZE;
@@ -37,35 +39,30 @@ void setupMode()
     // Assign the pins on the configured PCA9685 to positions on the vest
     auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
       // clang-format off
-      {new PCA9685OutputWriter(pwm, 0), new PCA9685OutputWriter(pwm, 1), new PCA9685OutputWriter(pwm, 2), new PCA9685OutputWriter(pwm, 3)},
-      {new PCA9685OutputWriter(pwm, 4), new PCA9685OutputWriter(pwm, 5), new PCA9685OutputWriter(pwm, 6), new PCA9685OutputWriter(pwm, 7)},
+      { new PCA9685OutputWriter(pwm, 0), new PCA9685OutputWriter(pwm, 1), new PCA9685OutputWriter(pwm, 2), new PCA9685OutputWriter(pwm, 3) },
+      { new PCA9685OutputWriter(pwm, 4), new PCA9685OutputWriter(pwm, 5), new PCA9685OutputWriter(pwm, 6), new PCA9685OutputWriter(pwm, 7) },
       // clang-format on
     });
     auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
       // clang-format off
-      {new PCA9685OutputWriter(pwm, 8),  new PCA9685OutputWriter(pwm, 9),  new PCA9685OutputWriter(pwm, 10), new PCA9685OutputWriter(pwm, 11)},
-      {new PCA9685OutputWriter(pwm, 12), new PCA9685OutputWriter(pwm, 13), new PCA9685OutputWriter(pwm, 14), new PCA9685OutputWriter(pwm, 15)},
+      { new PCA9685OutputWriter(pwm, 8),  new PCA9685OutputWriter(pwm, 9),  new PCA9685OutputWriter(pwm, 10), new PCA9685OutputWriter(pwm, 11) },
+      { new PCA9685OutputWriter(pwm, 12), new PCA9685OutputWriter(pwm, 13), new PCA9685OutputWriter(pwm, 14), new PCA9685OutputWriter(pwm, 15) },
       // clang-format on
     });
 
-    auto* chestFront = new HapticPlane_Closest(frontOutputs);
-    auto* chestBack = new HapticPlane_Closest(backOutputs);
-
-    app->getHapticBody()->addComponent(OUTPUT_PATH_CHEST_FRONT, chestFront);
-    app->getHapticBody()->addComponent(OUTPUT_PATH_CHEST_BACK, chestBack);
+    app->getHapticBody()->addTarget(Target::ChestFront, new VibroPlane_Closest(frontOutputs));
+    app->getHapticBody()->addTarget(Target::ChestBack, new VibroPlane_Closest(backOutputs));
 
     app->getHapticBody()->setup();
 
-    uint8_t serialNumber[BH_SERIAL_NUMBER_LENGTH] = BH_SERIAL_NUMBER;
-    ConnectionBHBLE_Config config = {
+    auto* bhBleConnection = new BH::BLE::Connection(
+      {
         .deviceName = BLUETOOTH_NAME,
         .appearance = BH_BLE_APPEARANCE,
-        .serialNumber = serialNumber,
-    };
-    auto* bhBleConnection = new ConnectionBHBLE(
-      config,
+        .serialNumber = BH_SERIAL_NUMBER,
+      },
       [](std::string& value) -> void {
-          vestX16OutputTransformer(app->getHapticBody(), value, bhLayout, bhLayoutSize, layoutGroups, layoutGroupsSize);
+          BH::Decoder::applyVestGrouped(app->getHapticBody(), value, bhLayout, layoutGroups);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
index 870a0859..6ec93001 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
@@ -6,23 +6,25 @@
 
 #include "senseshift.h"
 
-#include <bh_utils.hpp>
-#include <connection_bhble.hpp>
 #include <output_writers/pca9685.hpp>
 #include <output_writers/pwm.hpp>
+#include <senseshift/bh/ble/connection.hpp>
+#include <senseshift/bh/devices.hpp>
+#include <senseshift/bh/encoding.hpp>
 
 #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
 #include <battery/adc_naive.hpp>
 #endif
 
 using namespace OH;
-using namespace BH;
+using namespace SenseShift;
+using namespace SenseShift::Body::Haptics;
 
-extern SenseShift App;
-SenseShift* app = &App;
+extern SenseShift::SenseShift App;
+SenseShift::SenseShift* app = &App;
 
 static const size_t bhLayoutSize = BH_LAYOUT_TACTSUITX40_SIZE;
-static const oh_output_point_t* bhLayout[bhLayoutSize] = BH_LAYOUT_TACTSUITX40;
+static const BH::OutputLayout_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTSUITX40;
 
 void setupMode()
 {
@@ -39,41 +41,36 @@ void setupMode()
     // vest
     auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
       // clang-format off
-      {new PCA9685OutputWriter(pwm0, 0),  new PCA9685OutputWriter(pwm0, 1),  new PCA9685OutputWriter(pwm0, 2),  new PCA9685OutputWriter(pwm0, 3)},
-      {new PCA9685OutputWriter(pwm0, 4),  new PCA9685OutputWriter(pwm0, 5),  new PCA9685OutputWriter(pwm0, 6),  new PCA9685OutputWriter(pwm0, 7)},
-      {new PCA9685OutputWriter(pwm0, 8),  new PCA9685OutputWriter(pwm0, 9),  new PCA9685OutputWriter(pwm0, 10), new PCA9685OutputWriter(pwm0, 11)},
-      {new PCA9685OutputWriter(pwm0, 12), new PCA9685OutputWriter(pwm0, 13), new PCA9685OutputWriter(pwm0, 14), new PCA9685OutputWriter(pwm0, 15)},
-      {new PWMOutputWriter(32),           new PWMOutputWriter(33),           new PWMOutputWriter(25),           new PWMOutputWriter(26)},
+      { new PCA9685OutputWriter(pwm0, 0),  new PCA9685OutputWriter(pwm0, 1),  new PCA9685OutputWriter(pwm0, 2),  new PCA9685OutputWriter(pwm0, 3)  },
+      { new PCA9685OutputWriter(pwm0, 4),  new PCA9685OutputWriter(pwm0, 5),  new PCA9685OutputWriter(pwm0, 6),  new PCA9685OutputWriter(pwm0, 7)  },
+      { new PCA9685OutputWriter(pwm0, 8),  new PCA9685OutputWriter(pwm0, 9),  new PCA9685OutputWriter(pwm0, 10), new PCA9685OutputWriter(pwm0, 11) },
+      { new PCA9685OutputWriter(pwm0, 12), new PCA9685OutputWriter(pwm0, 13), new PCA9685OutputWriter(pwm0, 14), new PCA9685OutputWriter(pwm0, 15) },
+      { new PWMOutputWriter(32),           new PWMOutputWriter(33),           new PWMOutputWriter(25),           new PWMOutputWriter(26)           },
       // clang-format on
     });
     auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
       // clang-format off
-      {new PCA9685OutputWriter(pwm1, 0),  new PCA9685OutputWriter(pwm1, 1),  new PCA9685OutputWriter(pwm1, 2),  new PCA9685OutputWriter(pwm1, 3)},
-      {new PCA9685OutputWriter(pwm1, 4),  new PCA9685OutputWriter(pwm1, 5),  new PCA9685OutputWriter(pwm1, 6),  new PCA9685OutputWriter(pwm1, 7)},
-      {new PCA9685OutputWriter(pwm1, 8),  new PCA9685OutputWriter(pwm1, 9),  new PCA9685OutputWriter(pwm1, 10), new PCA9685OutputWriter(pwm1, 11)},
-      {new PCA9685OutputWriter(pwm1, 12), new PCA9685OutputWriter(pwm1, 13), new PCA9685OutputWriter(pwm1, 14), new PCA9685OutputWriter(pwm1, 15)},
-      {new PWMOutputWriter(27),           new PWMOutputWriter(14),           new PWMOutputWriter(12),           new PWMOutputWriter(13)},
+      { new PCA9685OutputWriter(pwm1, 0),  new PCA9685OutputWriter(pwm1, 1),  new PCA9685OutputWriter(pwm1, 2),  new PCA9685OutputWriter(pwm1, 3)  },
+      { new PCA9685OutputWriter(pwm1, 4),  new PCA9685OutputWriter(pwm1, 5),  new PCA9685OutputWriter(pwm1, 6),  new PCA9685OutputWriter(pwm1, 7)  },
+      { new PCA9685OutputWriter(pwm1, 8),  new PCA9685OutputWriter(pwm1, 9),  new PCA9685OutputWriter(pwm1, 10), new PCA9685OutputWriter(pwm1, 11) },
+      { new PCA9685OutputWriter(pwm1, 12), new PCA9685OutputWriter(pwm1, 13), new PCA9685OutputWriter(pwm1, 14), new PCA9685OutputWriter(pwm1, 15) },
+      { new PWMOutputWriter(27),           new PWMOutputWriter(14),           new PWMOutputWriter(12),           new PWMOutputWriter(13)           },
       // clang-format on
     });
 
-    auto* chestFront = new HapticPlane_Closest(frontOutputs);
-    auto* chestBack = new HapticPlane_Closest(backOutputs);
-
-    app->getHapticBody()->addComponent(OUTPUT_PATH_CHEST_FRONT, chestFront);
-    app->getHapticBody()->addComponent(OUTPUT_PATH_CHEST_BACK, chestBack);
+    app->getHapticBody()->addTarget(Target::ChestFront, new VibroPlane_Closest(frontOutputs));
+    app->getHapticBody()->addTarget(Target::ChestBack, new VibroPlane_Closest(backOutputs));
 
     app->getHapticBody()->setup();
 
-    uint8_t serialNumber[BH_SERIAL_NUMBER_LENGTH] = BH_SERIAL_NUMBER;
-    ConnectionBHBLE_Config config = {
+    auto* bhBleConnection = new BH::BLE::Connection(
+      {
         .deviceName = BLUETOOTH_NAME,
         .appearance = BH_BLE_APPEARANCE,
-        .serialNumber = serialNumber,
-    };
-    auto* bhBleConnection = new ConnectionBHBLE(
-      config,
+        .serialNumber = BH_SERIAL_NUMBER,
+      },
       [](std::string& value) -> void {
-          vestOutputTransformer(app->getHapticBody(), value, bhLayout, bhLayoutSize);
+          BH::Decoder::applyVest(app->getHapticBody(), value, bhLayout);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactvisor.cpp b/firmware/mode_configs/bhaptics/tactvisor.cpp
index 2cd4692e..9e2bb10d 100644
--- a/firmware/mode_configs/bhaptics/tactvisor.cpp
+++ b/firmware/mode_configs/bhaptics/tactvisor.cpp
@@ -6,47 +6,46 @@
 
 #include "senseshift.h"
 
-#include <bh_utils.hpp>
-#include <connection_bhble.hpp>
 #include <output_writers/pwm.hpp>
+#include <senseshift/bh/ble/connection.hpp>
+#include <senseshift/bh/devices.hpp>
+#include <senseshift/bh/encoding.hpp>
 
 #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
 #include "battery/adc_naive.hpp"
 #endif
 
 using namespace OH;
-using namespace BH;
+using namespace SenseShift;
+using namespace SenseShift::Body::Haptics;
 
-extern SenseShift App;
-SenseShift* app = &App;
+extern SenseShift::SenseShift App;
+SenseShift::SenseShift* app = &App;
 
 static const size_t bhLayoutSize = BH_LAYOUT_TACTVISOR_SIZE;
-static const oh_output_point_t* bhLayout[bhLayoutSize] = BH_LAYOUT_TACTVISOR;
+static const Position_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTVISOR;
 
 void setupMode()
 {
     // Configure PWM pins to their positions on the face
     auto faceOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
       // clang-format off
-      {new PWMOutputWriter(32), new PWMOutputWriter(33), new PWMOutputWriter(25), new PWMOutputWriter(26)},
+      { new PWMOutputWriter(32), new PWMOutputWriter(33), new PWMOutputWriter(25), new PWMOutputWriter(26) },
       // clang-format on
     });
 
-    auto* face = new HapticPlane_Closest(faceOutputs);
-    app->getHapticBody()->addComponent(OUTPUT_PATH_ACCESSORY, face);
+    app->getHapticBody()->addTarget(Target::FaceFront, new VibroPlane_Closest(faceOutputs));
 
     app->getHapticBody()->setup();
 
-    uint8_t serialNumber[BH_SERIAL_NUMBER_LENGTH] = BH_SERIAL_NUMBER;
-    ConnectionBHBLE_Config config = {
+    auto* bhBleConnection = new BH::BLE::Connection(
+      {
         .deviceName = BLUETOOTH_NAME,
         .appearance = BH_BLE_APPEARANCE,
-        .serialNumber = serialNumber,
-    };
-    auto* bhBleConnection = new ConnectionBHBLE(
-      config,
+        .serialNumber = BH_SERIAL_NUMBER,
+      },
       [](std::string& value) -> void {
-          plainOutputTransformer(app->getHapticBody(), value, bhLayout, bhLayoutSize, OUTPUT_PATH_ACCESSORY);
+          BH::Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect::Vibro, Target::FaceFront);
       },
       app
     );
diff --git a/firmware/senseshift.cpp b/firmware/senseshift.cpp
index f54fda9a..2605ce3e 100644
--- a/firmware/senseshift.cpp
+++ b/firmware/senseshift.cpp
@@ -29,4 +29,4 @@ namespace SenseShift {
     {
         this->eventListeners.push_back(listener);
     }
-}
+} // namespace SenseShift
diff --git a/include/senseshift.h b/include/senseshift.h
index f4346148..f973af46 100644
--- a/include/senseshift.h
+++ b/include/senseshift.h
@@ -14,7 +14,7 @@
 
 namespace SenseShift {
     class SenseShift final : public OH::IEventDispatcher {
-    private:
+      private:
         std::vector<const OH::IEventListener*> eventListeners{};
         Body::Haptics::HapticBody* pHapticBody;
 
@@ -22,7 +22,7 @@ namespace SenseShift {
         OH::BatterySensor* battery;
 #endif
 
-    public:
+      public:
         SenseShift();
 
         Body::Haptics::HapticBody* getHapticBody()
@@ -33,4 +33,4 @@ namespace SenseShift {
         void postEvent(const OH::IEvent* event) override;
         void addEventListener(const OH::IEventListener* listener) override;
     };
-}
+} // namespace SenseShift
diff --git a/ini/bhaptics.ini b/ini/bhaptics.ini
index 54c5aa1e..ac624ea8 100644
--- a/ini/bhaptics.ini
+++ b/ini/bhaptics.ini
@@ -239,7 +239,7 @@ monitor_speed		= ${bhaptics.monitor_speed}
 
 build_flags 		= ${bhaptics.build_flags}
 	-D BH_DEVICE_TACTGLOVE
-	-D BH_LAYOUT=BH::TactGloveLeftLayout
+	-D SENSESHIFT_HAND_SIDE=Left
 	-D BH_BLE_APPEARANCE=508
 	'-D BLUETOOTH_NAME="TactGlove (L"'
 	'-D BH_SERIAL_NUMBER={ 0xcd, 0x0b, 0x81, 0x45, 0x85, 0xf9, 0x2b, 0x6c, 0xed, 0x5b }'
@@ -258,7 +258,7 @@ monitor_speed		= ${bhaptics.monitor_speed}
 
 build_flags 		= ${bhaptics.build_flags}
 	-D BH_DEVICE_TACTGLOVE
-	-D BH_LAYOUT=BH::TactGloveRightLayout
+	-D SENSESHIFT_HAND_SIDE=Right
 	-D BH_BLE_APPEARANCE=508
 	'-D BLUETOOTH_NAME="TactGlove (R"'
 	'-D BH_SERIAL_NUMBER={ 0x12, 0x0b, 0xae, 0xbf, 0xbc, 0x90, 0x3b, 0x0d, 0x84, 0xdd }'
diff --git a/lib/arduino/components/serial_plotter.cpp b/lib/arduino/components/serial_plotter.cpp
index 21a1ffb1..d5be860b 100644
--- a/lib/arduino/components/serial_plotter.cpp
+++ b/lib/arduino/components/serial_plotter.cpp
@@ -17,7 +17,7 @@ void OH::SerialPlotter_OutputStates<_Tp>::run()
 {
     while (true) {
         for (const auto& [target, plane] : *output->getTargets()) {
-            std::visit(PlaneVisitor{target, this->serial}, plane);
+            std::visit(PlaneVisitor{ target, this->serial }, plane);
         }
         this->serial->println();
 
diff --git a/lib/arduino/components/serial_plotter.hpp b/lib/arduino/components/serial_plotter.hpp
index e0a9be8a..b2b9b085 100644
--- a/lib/arduino/components/serial_plotter.hpp
+++ b/lib/arduino/components/serial_plotter.hpp
@@ -35,7 +35,8 @@ namespace OH {
           TaskConfig taskConfig = { "Serial Plotter", 2048, 1, tskNO_AFFINITY }
         ) :
           Task<SerialPlotter_OutputStates<_Tp>>(taskConfig), serial(&serial), output(output), sampleRate(sampleRate){};
-        SerialPlotter_OutputStates(_Tp& serial, SenseShift::Body::Haptics::HapticBody* output) : SerialPlotter_OutputStates(serial, output, 100){};
+        SerialPlotter_OutputStates(_Tp& serial, SenseShift::Body::Haptics::HapticBody* output) :
+          SerialPlotter_OutputStates(serial, output, 100){};
 
         void begin() override
         {
diff --git a/lib/bhaptics/senseshift/bh/devices.hpp b/lib/bhaptics/senseshift/bh/devices.hpp
index e500c09f..7c5531f0 100644
--- a/lib/bhaptics/senseshift/bh/devices.hpp
+++ b/lib/bhaptics/senseshift/bh/devices.hpp
@@ -1,18 +1,18 @@
 #pragma once
 
 #include <hand_interface.hpp>
-#include <haptic_plane.hpp>
+#include <haptic_body.hpp>
 
 #pragma region BH_DEVICE_TACTSUITX40
 
 #define BH_LAYOUT_TACTSUITX40_SIZE_X 4
 #define BH_LAYOUT_TACTSUITX40_SIZE_Y 5
-#define BH_LAYOUT_TACTSUITX40_MAKE_POINT(x, y)                                  \
-    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
-      x,                                                                        \
-      y,                                                                        \
-      (oh_output_coord_t) (BH_LAYOUT_TACTSUITX40_SIZE_X - 1),                   \
-      (oh_output_coord_t) (BH_LAYOUT_TACTSUITX40_SIZE_Y - 1)                    \
+#define BH_LAYOUT_TACTSUITX40_MAKE_POINT(x, y)                                                            \
+    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<::SenseShift::Body::Haptics::Coordinate_t>( \
+      x,                                                                                                  \
+      y,                                                                                                  \
+      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTSUITX40_SIZE_X - 1),                      \
+      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTSUITX40_SIZE_Y - 1)                       \
     )
 
 // X * Y for front and back
@@ -74,12 +74,12 @@
 
 #define BH_LAYOUT_TACTSUITX16_SIZE_X 4
 #define BH_LAYOUT_TACTSUITX16_SIZE_Y 2
-#define BH_LAYOUT_TACTSUITX16_MAKE_POINT(x, y)                                  \
-    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
-      x,                                                                        \
-      y,                                                                        \
-      (oh_output_coord_t) (BH_LAYOUT_TACTSUITX16_SIZE_X - 1),                   \
-      (oh_output_coord_t) (BH_LAYOUT_TACTSUITX16_SIZE_Y - 1)                    \
+#define BH_LAYOUT_TACTSUITX16_MAKE_POINT(x, y)                                                            \
+    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<::SenseShift::Body::Haptics::Coordinate_t>( \
+      x,                                                                                                  \
+      y,                                                                                                  \
+      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTSUITX16_SIZE_X - 1),                      \
+      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTSUITX16_SIZE_Y - 1)                       \
     )
 
 // X16 suit uses the same packets structure as x40 suit and performs motor grouping in firmware
@@ -87,55 +87,55 @@
 // clang-format off
 #define BH_LAYOUT_TACTSUITX16 {                                 \
     /* Front, left part */                                      \
-    /*  0 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0),  /*  0 */  \
-    /*  1 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0),  /*  1 */  \
-    /*  2 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0),  /*  4 */  \
-    /*  3 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0),  /*  5 */  \
+    /*  0 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0) },  /*  0 */  \
+    /*  1 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0) },  /*  1 */  \
+    /*  2 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0) },  /*  4 */  \
+    /*  3 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0) },  /*  5 */  \
                                                                 \
-    /*  4 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1),  /*  8 */  \
-    /*  5 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1),  /*  9 */  \
-    /*  6 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1),  /* 12 */  \
-    /*  7 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1),  /* 13 */  \
-    /*  8 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1),  /* 16 */  \
-    /*  9 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1),  /* 17 */  \
+    /*  4 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) },  /*  8 */  \
+    /*  5 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) },  /*  9 */  \
+    /*  6 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) },  /* 12 */  \
+    /*  7 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) },  /* 13 */  \
+    /*  8 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) },  /* 16 */  \
+    /*  9 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) },  /* 17 */  \
                                                                 \
     /* Back */                                                  \
-    /* 10 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0),  /*  0 */  \
-    /* 11 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0),  /*  1 */  \
-    /* 12 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0),  /*  4 */  \
-    /* 13 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0),  /*  5 */  \
+    /* 10 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0) },  /*  0 */  \
+    /* 11 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0) },  /*  1 */  \
+    /* 12 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0) },  /*  4 */  \
+    /* 13 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0) },  /*  5 */  \
                                                                 \
-    /* 14 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1),  /*  8 */  \
-    /* 15 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1),  /*  9 */  \
-    /* 16 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1),  /* 12 */  \
-    /* 17 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1),  /* 13 */  \
-    /* 18 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1),  /* 16 */  \
-    /* 19 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1),  /* 17 */  \
+    /* 14 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) },  /*  8 */  \
+    /* 15 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) },  /*  9 */  \
+    /* 16 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) },  /* 12 */  \
+    /* 17 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) },  /* 13 */  \
+    /* 18 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) },  /* 16 */  \
+    /* 19 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) },  /* 17 */  \
                                                                 \
-    /* 20 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0),  /*  2 */  \
-    /* 21 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0),  /*  3 */  \
-    /* 22 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0),  /*  4 */  \
-    /* 23 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0),  /*  7 */  \
+    /* 20 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0) },  /*  2 */  \
+    /* 21 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0) },  /*  3 */  \
+    /* 22 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0) },  /*  4 */  \
+    /* 23 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0) },  /*  7 */  \
                                                                 \
-    /* 24 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1),  /* 10 */  \
-    /* 25 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1),  /* 11 */  \
-    /* 26 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1),  /* 14 */  \
-    /* 27 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1),  /* 15 */  \
-    /* 28 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1),  /* 18 */  \
-    /* 29 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1),  /* 19 */  \
+    /* 24 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) },  /* 10 */  \
+    /* 25 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) },  /* 11 */  \
+    /* 26 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) },  /* 14 */  \
+    /* 27 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) },  /* 15 */  \
+    /* 28 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) },  /* 18 */  \
+    /* 29 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) },  /* 19 */  \
                                                                 \
     /* Front, again... Now right part */                        \
-    /* 30 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0),  /*  2 */  \
-    /* 31 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0),  /*  3 */  \
-    /* 32 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0),  /*  4 */  \
-    /* 33 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0),  /*  7 */  \
+    /* 30 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0) },  /*  2 */  \
+    /* 31 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0) },  /*  3 */  \
+    /* 32 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0) },  /*  4 */  \
+    /* 33 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0) },  /*  7 */  \
                                                                 \
-    /* 34 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1),  /* 10 */  \
-    /* 35 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1),  /* 11 */  \
-    /* 36 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1),  /* 14 */  \
-    /* 37 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1),  /* 15 */  \
-    /* 38 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1),  /* 18 */  \
-    /* 39 */ BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1),  /* 19 */  \
+    /* 34 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) },  /* 10 */  \
+    /* 35 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) },  /* 11 */  \
+    /* 36 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) },  /* 14 */  \
+    /* 37 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) },  /* 15 */  \
+    /* 38 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) },  /* 18 */  \
+    /* 39 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) },  /* 19 */  \
 }
 // clang-format on
 
@@ -152,12 +152,12 @@
 
 #define BH_LAYOUT_TACTAL_SIZE_X 6
 #define BH_LAYOUT_TACTAL_SIZE_Y 1
-#define BH_LAYOUT_TACTAL_MAKE_POINT(x, y)                                       \
-    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
-      x,                                                                        \
-      y,                                                                        \
-      (oh_output_coord_t) (BH_LAYOUT_TACTAL_SIZE_X - 1),                        \
-      (oh_output_coord_t) (BH_LAYOUT_TACTAL_SIZE_Y - 1)                         \
+#define BH_LAYOUT_TACTAL_MAKE_POINT(x, y)                                                                 \
+    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<::SenseShift::Body::Haptics::Coordinate_t>( \
+      x,                                                                                                  \
+      y,                                                                                                  \
+      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTAL_SIZE_X - 1),                           \
+      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTAL_SIZE_Y - 1)                            \
     )
 
 #define BH_LAYOUT_TACTAL_SIZE (BH_LAYOUT_TACTAL_SIZE_X * BH_LAYOUT_TACTAL_SIZE_Y)
@@ -178,12 +178,12 @@
 
 #define BH_LAYOUT_TACTVISOR_SIZE_X 4
 #define BH_LAYOUT_TACTVISOR_SIZE_Y 1
-#define BH_LAYOUT_TACTVISOR_MAKE_POINT(x, y)                                    \
-    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
-      x,                                                                        \
-      y,                                                                        \
-      (oh_output_coord_t) (BH_LAYOUT_TACTVISOR_SIZE_X - 1),                     \
-      (oh_output_coord_t) (BH_LAYOUT_TACTVISOR_SIZE_Y - 1)                      \
+#define BH_LAYOUT_TACTVISOR_MAKE_POINT(x, y)                                                              \
+    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<::SenseShift::Body::Haptics::Coordinate_t>( \
+      x,                                                                                                  \
+      y,                                                                                                  \
+      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTVISOR_SIZE_X - 1),                        \
+      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTVISOR_SIZE_Y - 1)                         \
     )
 
 #define BH_LAYOUT_TACTVISOR_SIZE (BH_LAYOUT_TACTVISOR_SIZE_X * BH_LAYOUT_TACTVISOR_SIZE_Y)
@@ -202,12 +202,12 @@
 
 #define BH_LAYOUT_TACTOSY2_SIZE_X 3
 #define BH_LAYOUT_TACTOSY2_SIZE_Y 2
-#define BH_LAYOUT_TACTOSY2_MAKE_POINT(x, y)                                     \
-    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
-      x,                                                                        \
-      y,                                                                        \
-      (oh_output_coord_t) (BH_LAYOUT_TACTOSY2_SIZE_X - 1),                      \
-      (oh_output_coord_t) (BH_LAYOUT_TACTOSY2_SIZE_Y - 1)                       \
+#define BH_LAYOUT_TACTOSY2_MAKE_POINT(x, y)                                                               \
+    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<::SenseShift::Body::Haptics::Coordinate_t>( \
+      x,                                                                                                  \
+      y,                                                                                                  \
+      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTOSY2_SIZE_X - 1),                         \
+      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTOSY2_SIZE_Y - 1)                          \
     )
 
 #define BH_LAYOUT_TACTOSY2_SIZE (BH_LAYOUT_TACTOSY2_SIZE_X * BH_LAYOUT_TACTOSY2_SIZE_Y)
@@ -224,12 +224,12 @@
 
 #define BH_LAYOUT_TACTOSYH_SIZE_X 1
 #define BH_LAYOUT_TACTOSYH_SIZE_Y 3
-#define BH_LAYOUT_TACTOSYH_MAKE_POINT(x, y)                                     \
-    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
-      x,                                                                        \
-      y,                                                                        \
-      (oh_output_coord_t) (BH_LAYOUT_TACTOSYH_SIZE_X - 1),                      \
-      (oh_output_coord_t) (BH_LAYOUT_TACTOSYH_SIZE_Y - 1)                       \
+#define BH_LAYOUT_TACTOSYH_MAKE_POINT(x, y)                                                               \
+    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<::SenseShift::Body::Haptics::Coordinate_t>( \
+      x,                                                                                                  \
+      y,                                                                                                  \
+      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTOSYH_SIZE_X - 1),                         \
+      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTOSYH_SIZE_Y - 1)                          \
     )
 
 #define BH_LAYOUT_TACTOSYH_SIZE (BH_LAYOUT_TACTOSYH_SIZE_X * BH_LAYOUT_TACTOSYH_SIZE_Y)
@@ -243,12 +243,12 @@
 
 #define BH_LAYOUT_TACTOSYF_SIZE_X 1
 #define BH_LAYOUT_TACTOSYF_SIZE_Y 3
-#define BH_LAYOUT_TACTOSYF_MAKE_POINT(x, y)                                     \
-    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<oh_output_coord_t>( \
-      x,                                                                        \
-      y,                                                                        \
-      (oh_output_coord_t) (BH_LAYOUT_TACTOSYF_SIZE_X - 1),                      \
-      (oh_output_coord_t) (BH_LAYOUT_TACTOSYF_SIZE_Y - 1)                       \
+#define BH_LAYOUT_TACTOSYF_MAKE_POINT(x, y)                                                               \
+    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<::SenseShift::Body::Haptics::Coordinate_t>( \
+      x,                                                                                                  \
+      y,                                                                                                  \
+      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTOSYF_SIZE_X - 1),                         \
+      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTOSYF_SIZE_Y - 1)                          \
     )
 
 #define BH_LAYOUT_TACTOSYF_SIZE (BH_LAYOUT_TACTOSYF_SIZE_X * BH_LAYOUT_TACTOSYF_SIZE_Y)
@@ -258,28 +258,92 @@
 
 #pragma endregion BH_DEVICE_TACTOSYF
 
+#pragma region BH_DEVIDE_TACTGLOVE
+
+#define BH_LAYOUT_TACTGLOVE_SIZE 6
+// TactGlove (Left) motor positions
+#define BH_LAYOUT_TACTGLOVE_LEFT                                                                                \
+    {                                                                                                           \
+        { Target_t::HandLeftThumb, FINGERTIP_POSITION }, { Target_t::HandLeftIndex, FINGERTIP_POSITION },       \
+          { Target_t::HandLeftMiddle, FINGERTIP_POSITION }, { Target_t::HandLeftRing, FINGERTIP_POSITION },     \
+          { Target_t::HandLeftLittle, FINGERTIP_POSITION }, { Target_t::HandLeftDorsal, WRIST_MOTOR_POSITION }, \
+    }
+// TactGlove (Right) motor positions
+#define BH_LAYOUT_TACTGLOVE_RIGHT                                                                             \
+    {                                                                                                         \
+        { Target_t::HandRightThumb, FINGERTIP_POSITION }, { Target_t::HandRightIndex, FINGERTIP_POSITION },   \
+          { Target_t::HandRightMiddle, FINGERTIP_POSITION }, { Target_t::HandRightRing, FINGERTIP_POSITION }, \
+          { Target_t::HandRightLittle, FINGERTIP_POSITION },                                                  \
+        {                                                                                                     \
+            Target_t::HandRightDorsal, WRIST_MOTOR_POSITION                                                   \
+        }                                                                                                     \
+    }
+
+#pragma endregion BH_DEVIDE_TACTGLOVE
+
 namespace SenseShift::BH {
     using namespace ::SenseShift::Body::Hands::Haptics;
+    using namespace ::SenseShift::Body::Haptics;
 
-    using Effect_t = ::SenseShift::Body::Haptics::Effect_t;
-    using Target_t = ::SenseShift::Body::Haptics::Target_t;
-    using Position_t = ::SenseShift::Body::Haptics::Position_t;
+    using HandSide_t = ::SenseShift::Body::Hands::HandSide_t;
 
     typedef std::tuple<Target_t, Position_t> OutputLayout_t;
 
-    // TactSuit X40 motor positions
-    static constexpr const OutputLayout_t TactSuitX40Layout[] = BH_LAYOUT_TACTSUITX40;
-
     // TactGlove Wrist motor position
     static constexpr const Position_t WRIST_MOTOR_POSITION(127, 191);
-    static constexpr const OutputLayout_t TactGloveLeftLayout[] = {
-        { Target_t::HandLeftThumb, FINGERTIP_POSITION },  { Target_t::HandLeftIndex, FINGERTIP_POSITION },
-        { Target_t::HandLeftMiddle, FINGERTIP_POSITION }, { Target_t::HandLeftRing, FINGERTIP_POSITION },
-        { Target_t::HandLeftLittle, FINGERTIP_POSITION }, { Target_t::HandLeftDorsal, WRIST_MOTOR_POSITION },
-    };
-    static constexpr const OutputLayout_t TactGloveRightLayout[] = {
-        { Target_t::HandRightThumb, FINGERTIP_POSITION },  { Target_t::HandRightIndex, FINGERTIP_POSITION },
-        { Target_t::HandRightMiddle, FINGERTIP_POSITION }, { Target_t::HandRightRing, FINGERTIP_POSITION },
-        { Target_t::HandRightLittle, FINGERTIP_POSITION }, { Target_t::HandRightDorsal, WRIST_MOTOR_POSITION },
-    };
+    static constexpr const OutputLayout_t TactGloveLeftLayout[] = BH_LAYOUT_TACTGLOVE_LEFT;
+    static constexpr const OutputLayout_t TactGloveRightLayout[] = BH_LAYOUT_TACTGLOVE_RIGHT;
+
+    inline void addTactGloveActuators(
+      HapticBody* hapticBody,
+      const HandSide_t side,
+      OH::AbstractActuator* const thumbActuator,
+      OH::AbstractActuator* const indexActuator,
+      OH::AbstractActuator* const middleActuator,
+      OH::AbstractActuator* const ringActuator,
+      OH::AbstractActuator* const littleActuator,
+      OH::AbstractActuator* const wristActuator
+    )
+    {
+        const OutputLayout_t(&layout)[6] = (side == HandSide_t::Left) ? TactGloveLeftLayout : TactGloveRightLayout;
+
+        if (thumbActuator != nullptr) {
+            hapticBody->addTarget(
+              std::get<0>(layout[0]),
+              new VibroPlane({ { std::get<1>(layout[0]), thumbActuator } })
+            );
+        }
+
+        if (indexActuator != nullptr) {
+            hapticBody->addTarget(
+              std::get<0>(layout[1]),
+              new VibroPlane({ { std::get<1>(layout[1]), indexActuator } })
+            );
+        }
+
+        if (middleActuator != nullptr) {
+            hapticBody->addTarget(
+              std::get<0>(layout[2]),
+              new VibroPlane({ { std::get<1>(layout[2]), middleActuator } })
+            );
+        }
+
+        if (ringActuator != nullptr) {
+            hapticBody->addTarget(std::get<0>(layout[3]), new VibroPlane({ { std::get<1>(layout[3]), ringActuator } }));
+        }
+
+        if (littleActuator != nullptr) {
+            hapticBody->addTarget(
+              std::get<0>(layout[4]),
+              new VibroPlane({ { std::get<1>(layout[4]), littleActuator } })
+            );
+        }
+
+        if (wristActuator != nullptr) {
+            hapticBody->addTarget(
+              std::get<0>(layout[5]),
+              new VibroPlane({ { std::get<1>(layout[5]), wristActuator } })
+            );
+        }
+    }
 } // namespace SenseShift::BH
diff --git a/lib/bhaptics/senseshift/bh/encoding.hpp b/lib/bhaptics/senseshift/bh/encoding.hpp
index c6b876e1..83575f8c 100644
--- a/lib/bhaptics/senseshift/bh/encoding.hpp
+++ b/lib/bhaptics/senseshift/bh/encoding.hpp
@@ -22,10 +22,7 @@ namespace SenseShift::BH {
 
         template<size_t N>
         static void applyPlain(
-          HapticBody_t* output,
-          const uint8_t (&value)[N],
-          const OutputLayout_t (&layout)[N],
-          const Effect_t effect
+          HapticBody_t* output, const uint8_t (&value)[N], const OutputLayout_t (&layout)[N], const Effect_t effect
         )
         {
             for (size_t i = 0; i < N; i++) {
@@ -42,12 +39,8 @@ namespace SenseShift::BH {
         }
 
         template<size_t N>
-        static void applyPlain(
-          HapticBody_t* output,
-          std::string& value,
-          const OutputLayout_t (&layout)[N],
-          const Effect_t effect
-        )
+        static void
+          applyPlain(HapticBody_t* output, std::string& value, const OutputLayout_t (&layout)[N], const Effect_t effect)
         {
             std::uint8_t buf[N];
             std::size_t copyLength = std::min(value.size(), sizeof(buf));
@@ -103,34 +96,29 @@ namespace SenseShift::BH {
         static void applyVest(
           HapticBody_t* output,
           const uint8_t (&value)[VEST_PAYLOAD_SIZE],
-          const Position_t (&layout)[VEST_LAYOUT_SIZE]
+          const OutputLayout_t (&layout)[VEST_LAYOUT_SIZE]
         )
         {
             for (size_t i = 0; i < VEST_PAYLOAD_SIZE; i++) {
                 uint8_t byte = value[i];
                 uint actIndex = i * 2;
-                const auto target = (actIndex < 10 || actIndex >= 30) ? Target_t::ChestFront : Target_t::ChestBack;
-
                 output->effect({
                   .effect = Effect_t::Vibro,
-                  .target = target,
-                  .position = layout[actIndex],
+                  .target = std::get<0>(layout[actIndex]),
+                  .position = std::get<1>(layout[actIndex]),
                   .data = effectDataFromByte(Effect_t::Vibro, ((byte >> 4) & 0xf), 15),
                 });
                 output->effect({
                   .effect = Effect_t::Vibro,
-                  .target = target,
-                  .position = layout[actIndex + 1],
+                  .target = std::get<0>(layout[actIndex + 1]),
+                  .position = std::get<1>(layout[actIndex + 1]),
                   .data = effectDataFromByte(Effect_t::Vibro, (byte & 0xf), 15),
                 });
             }
         }
 
-        static void applyVest(
-          HapticBody_t* output,
-          std::string& value,
-          const Position_t (&layout)[VEST_LAYOUT_SIZE]
-        )
+        static void
+          applyVest(HapticBody_t* output, std::string& value, const OutputLayout_t (&layout)[VEST_LAYOUT_SIZE])
         {
             std::uint8_t buf[VEST_PAYLOAD_SIZE];
             std::size_t copyLength = std::min(value.size(), sizeof(buf));
@@ -146,7 +134,7 @@ namespace SenseShift::BH {
         static void applyVestGrouped(
           HapticBody_t* output,
           const uint8_t (&value)[VEST_PAYLOAD_SIZE],
-          const Position_t (&layout)[VEST_LAYOUT_SIZE],
+          const OutputLayout_t (&layout)[VEST_LAYOUT_SIZE],
           const uint8_t (&layoutGroups)[N]
         )
         {
@@ -187,12 +175,13 @@ namespace SenseShift::BH {
                     continue;
                 }
 
-                const auto target = (i < 10 || i >= 30) ? Target_t::ChestFront : Target_t::ChestBack;
+                const auto target = std::get<0>(layout[i]);
+                const auto position = std::get<1>(layout[i]);
 
                 output->effect({
                   .effect = Effect_t::Vibro,
                   .target = target,
-                  .position = layout[i],
+                  .position = position,
                   .data = effectDataFromByte(Effect_t::Vibro, result[i], 15),
                 });
             }
@@ -202,7 +191,7 @@ namespace SenseShift::BH {
         static void applyVestGrouped(
           HapticBody_t* output,
           std::string& value,
-          const Position_t (&layout)[VEST_LAYOUT_SIZE],
+          const OutputLayout_t (&layout)[VEST_LAYOUT_SIZE],
           const uint8_t (&layoutGroups)[N]
         )
         {
diff --git a/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp b/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
index a4f60bbc..68de48b5 100644
--- a/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
+++ b/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
@@ -29,10 +29,10 @@
 
 namespace SenseShift::BH::BLE {
     class BHServerCallbacks final : public BLEServerCallbacks {
-    private:
+      private:
         OH::IEventDispatcher* dispatcher;
 
-    public:
+      public:
         BHServerCallbacks(OH::IEventDispatcher* eventDispatcher) : dispatcher(eventDispatcher) {}
 
         void onConnect(BLEServer* pServer)
@@ -51,55 +51,55 @@ namespace SenseShift::BH::BLE {
         void onWrite(BLECharacteristic* pCharacteristic) override
         {
             log_d(
-            ">>\tonWrite (UUID: %s)\n\tvalue: `%s`, len: %u",
-            pCharacteristic->getUUID().toString().c_str(),
-            pCharacteristic->getValue().c_str(),
-            pCharacteristic->getValue().length()
+              ">>\tonWrite (UUID: %s)\n\tvalue: `%s`, len: %u",
+              pCharacteristic->getUUID().toString().c_str(),
+              pCharacteristic->getValue().c_str(),
+              pCharacteristic->getValue().length()
             );
         };
 
         void onRead(BLECharacteristic* pCharacteristic) override
         {
             log_d(
-            ">>\tonRead (UUID: %s)\n\tvalue: `%s`, len: %u",
-            pCharacteristic->getUUID().toString().c_str(),
-            pCharacteristic->getValue().c_str(),
-            pCharacteristic->getValue().length()
+              ">>\tonRead (UUID: %s)\n\tvalue: `%s`, len: %u",
+              pCharacteristic->getUUID().toString().c_str(),
+              pCharacteristic->getValue().c_str(),
+              pCharacteristic->getValue().length()
             );
         };
 
         void onNotify(BLECharacteristic* pCharacteristic) override
         {
             log_d(
-            ">>\tonNotify (UUID: %s)\n\tvalue: `%s`, len: %u",
-            pCharacteristic->getUUID().toString().c_str(),
-            pCharacteristic->getValue().c_str(),
-            pCharacteristic->getValue().length()
+              ">>\tonNotify (UUID: %s)\n\tvalue: `%s`, len: %u",
+              pCharacteristic->getUUID().toString().c_str(),
+              pCharacteristic->getValue().c_str(),
+              pCharacteristic->getValue().length()
             );
         };
 
-    #if defined(BLUETOOTH_USE_NIMBLE) && BLUETOOTH_USE_NIMBLE == true
+#if defined(BLUETOOTH_USE_NIMBLE) && BLUETOOTH_USE_NIMBLE == true
         void onStatus(BLECharacteristic* pCharacteristic, Status s, int code) override
-    #else
+#else
         void onStatus(BLECharacteristic* pCharacteristic, Status s, uint32_t code) override
-    #endif
+#endif
         {
             log_d(
-            ">>\tonNotify (UUID: %s)\n\tstatus: %d, code: %u \n\tvalue: `%s`, len: %u",
-            pCharacteristic->getUUID().toString().c_str(),
-            s,
-            code,
-            pCharacteristic->getValue().c_str(),
-            pCharacteristic->getValue().length()
+              ">>\tonNotify (UUID: %s)\n\tstatus: %d, code: %u \n\tvalue: `%s`, len: %u",
+              pCharacteristic->getUUID().toString().c_str(),
+              s,
+              code,
+              pCharacteristic->getValue().c_str(),
+              pCharacteristic->getValue().length()
             );
         };
     };
 
     class MotorCharCallbacks : public BLECharacteristicCallbacks {
-    private:
+      private:
         Connection::MotorHandler_t motorTransformer;
 
-    public:
+      public:
         MotorCharCallbacks(Connection::MotorHandler_t motorTransformer) : motorTransformer(motorTransformer) {}
 
         void onWrite(BLECharacteristic* pCharacteristic) override
@@ -149,26 +149,26 @@ namespace SenseShift::BH::BLE {
             MotorCharCallbacks* motorCallbacks = new MotorCharCallbacks(this->motorHandler);
 
             auto* motorChar =
-            this->motorService->createCharacteristic(BH_BLE_SERVICE_MOTOR_CHAR_MOTOR_UUID, PROPERTY_WRITE_NR);
+              this->motorService->createCharacteristic(BH_BLE_SERVICE_MOTOR_CHAR_MOTOR_UUID, PROPERTY_WRITE_NR);
             motorChar->setCallbacks(motorCallbacks);
 
             auto* motorCharStable =
-            this->motorService->createCharacteristic(BH_BLE_SERVICE_MOTOR_CHAR_MOTOR_STABLE_UUID, PROPERTY_WRITE);
+              this->motorService->createCharacteristic(BH_BLE_SERVICE_MOTOR_CHAR_MOTOR_STABLE_UUID, PROPERTY_WRITE);
             motorCharStable->setCallbacks(motorCallbacks);
         }
 
         {
             auto* configChar = this->motorService->createCharacteristic(
-            BH_BLE_SERVICE_MOTOR_CHAR_CONFIG_UUID,
-            PROPERTY_READ | PROPERTY_WRITE
+              BH_BLE_SERVICE_MOTOR_CHAR_CONFIG_UUID,
+              PROPERTY_READ | PROPERTY_WRITE
             );
             configChar->setCallbacks(new ConfigCharCallbacks());
         }
 
         {
             auto* serialNumberChar = this->motorService->createCharacteristic(
-            BH_BLE_SERVICE_MOTOR_CHAR_SERIAL_KEY_UUID,
-            PROPERTY_READ | PROPERTY_WRITE
+              BH_BLE_SERVICE_MOTOR_CHAR_SERIAL_KEY_UUID,
+              PROPERTY_READ | PROPERTY_WRITE
             );
             serialNumberChar->setValue(this->config.serialNumber, ConnectionConfig_t::SN_LENGTH);
             serialNumberChar->setCallbacks(new LogOutputCharCallbacks());
@@ -176,21 +176,21 @@ namespace SenseShift::BH::BLE {
 
         {
             this->batteryChar = this->motorService->createCharacteristic(
-            BH_BLE_SERVICE_MOTOR_CHAR_BATTERY_UUID,
-            PROPERTY_READ | PROPERTY_WRITE_NR
+              BH_BLE_SERVICE_MOTOR_CHAR_BATTERY_UUID,
+              PROPERTY_READ | PROPERTY_WRITE_NR
                 | PROPERTY_NOTIFY // for whatever reason, it have to be writable, otherwise Desktop app crashes
             );
 
-    #if !defined(BLUETOOTH_USE_NIMBLE) || BLUETOOTH_USE_NIMBLE != true
+#if !defined(BLUETOOTH_USE_NIMBLE) || BLUETOOTH_USE_NIMBLE != true
             batteryChar->addDescriptor(new BLE2902());
-    #endif
+#endif
 
             // original bHaptics Player require non-null value for battery level, otherwise it crashes
-    #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
+#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
             uint16_t defaultLevel = 0;
-    #else
+#else
             uint16_t defaultLevel = 100;
-    #endif
+#endif
 
             this->batteryChar->setValue(defaultLevel);
             // this->batteryChar->notify();
@@ -198,7 +198,7 @@ namespace SenseShift::BH::BLE {
 
         {
             auto* versionChar =
-            this->motorService->createCharacteristic(BH_BLE_SERVICE_MOTOR_CHAR_VERSION_UUID, PROPERTY_READ);
+              this->motorService->createCharacteristic(BH_BLE_SERVICE_MOTOR_CHAR_VERSION_UUID, PROPERTY_READ);
             versionChar->setCallbacks(new LogOutputCharCallbacks());
             uint16_t firmwareVersion = BH_FIRMWARE_VERSION;
             versionChar->setValue(firmwareVersion);
@@ -206,14 +206,15 @@ namespace SenseShift::BH::BLE {
 
         {
             auto* monitorChar = this->motorService->createCharacteristic(
-            BH_BLE_SERVICE_MOTOR_CHAR_TACTSUIT_MONITOR_UUID,
-            PROPERTY_READ | PROPERTY_WRITE | PROPERTY_NOTIFY | PROPERTY_BROADCAST | PROPERTY_INDICATE | PROPERTY_WRITE_NR
+              BH_BLE_SERVICE_MOTOR_CHAR_TACTSUIT_MONITOR_UUID,
+              PROPERTY_READ | PROPERTY_WRITE | PROPERTY_NOTIFY | PROPERTY_BROADCAST | PROPERTY_INDICATE
+                | PROPERTY_WRITE_NR
             );
             monitorChar->setCallbacks(new LogOutputCharCallbacks());
 
-    #if !defined(BLUETOOTH_USE_NIMBLE) || BLUETOOTH_USE_NIMBLE != true
+#if !defined(BLUETOOTH_USE_NIMBLE) || BLUETOOTH_USE_NIMBLE != true
             monitorChar->addDescriptor(new BLE2902());
-    #endif
+#endif
 
             uint16_t audioCableState = NO_AUDIO_CABLE;
             monitorChar->setValue(audioCableState);
@@ -221,25 +222,29 @@ namespace SenseShift::BH::BLE {
 
         // auto* athGlobalChar = this->motorService->createCharacteristic(
         //     BH_BLE_SERVICE_MOTOR_CHAR_ATH_GLOBAL_CONF_UUID,
-        //     PROPERTY_READ | PROPERTY_WRITE | PROPERTY_NOTIFY | PROPERTY_BROADCAST | PROPERTY_INDICATE | PROPERTY_WRITE_NR
+        //     PROPERTY_READ | PROPERTY_WRITE | PROPERTY_NOTIFY | PROPERTY_BROADCAST | PROPERTY_INDICATE |
+        //     PROPERTY_WRITE_NR
         // );
         // athGlobalChar->setCallbacks(new LogOutputCharCallbacks());
 
         // auto* athThemeChar = this->motorService->createCharacteristic(
         //     BH_BLE_SERVICE_MOTOR_CHAR_ATH_THEME_UUID,
-        //     PROPERTY_READ | PROPERTY_WRITE | PROPERTY_NOTIFY | PROPERTY_BROADCAST | PROPERTY_INDICATE | PROPERTY_WRITE_NR
+        //     PROPERTY_READ | PROPERTY_WRITE | PROPERTY_NOTIFY | PROPERTY_BROADCAST | PROPERTY_INDICATE |
+        //     PROPERTY_WRITE_NR
         // );
         // athThemeChar->setCallbacks(new LogOutputCharCallbacks());
 
         // auto* motorMappingChar = this->motorService->createCharacteristic(
         //     BH_BLE_SERVICE_MOTOR_CHAR_MOTTOR_MAPPING_UUID,
-        //     PROPERTY_READ | PROPERTY_WRITE | PROPERTY_NOTIFY | PROPERTY_BROADCAST | PROPERTY_INDICATE | PROPERTY_WRITE_NR
+        //     PROPERTY_READ | PROPERTY_WRITE | PROPERTY_NOTIFY | PROPERTY_BROADCAST | PROPERTY_INDICATE |
+        //     PROPERTY_WRITE_NR
         // );
         // motorMappingChar->setCallbacks(new LogOutputCharCallbacks());
 
         // auto* signatureMappingChar = this->motorService->createCharacteristic(
         //     BH_BLE_SERVICE_MOTOR_CHAR_SIGNATURE_PATTERN_UUID,
-        //     PROPERTY_READ | PROPERTY_WRITE | PROPERTY_NOTIFY | PROPERTY_BROADCAST | PROPERTY_INDICATE | PROPERTY_WRITE_NR
+        //     PROPERTY_READ | PROPERTY_WRITE | PROPERTY_NOTIFY | PROPERTY_BROADCAST | PROPERTY_INDICATE |
+        //     PROPERTY_WRITE_NR
         // );
         // signatureMappingChar->setCallbacks(new LogOutputCharCallbacks());
 
@@ -249,12 +254,12 @@ namespace SenseShift::BH::BLE {
             auto dfuService = this->bleServer->createService(BH_BLE_SERVICE_DFU_UUID);
 
             auto* dfuControlChar = dfuService->createCharacteristic(
-            BH_BLE_SERVICE_MOTOR_CHAR_SIGNATURE_PATTERN_UUID,
-            PROPERTY_READ | PROPERTY_WRITE
+              BH_BLE_SERVICE_MOTOR_CHAR_SIGNATURE_PATTERN_UUID,
+              PROPERTY_READ | PROPERTY_WRITE
             );
             dfuService->start();
         }
 
         this->bleServer->getAdvertising()->start();
     }
-}
+} // namespace SenseShift::BH::BLE
diff --git a/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp b/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
index f4d10f19..485e0212 100644
--- a/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
+++ b/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
@@ -3,8 +3,8 @@
 #include <abstract_connection.hpp>
 #include <utility.hpp>
 
-#include <senseshift/bh/constants.hpp>
 #include <senseshift/bh/ble/constants.hpp>
+#include <senseshift/bh/constants.hpp>
 
 #include <Arduino.h>
 #include <esp_wifi.h>
diff --git a/lib/core/types.hpp b/lib/core/types.hpp
index ed2c7a20..49a7c3c3 100644
--- a/lib/core/types.hpp
+++ b/lib/core/types.hpp
@@ -4,10 +4,3 @@
 #include <point2.hpp>
 
 #include <map>
-
-#define OH_OUTPUT_COORD_T uint8_t
-#define OH_OUTPUT_COORD_MAX UINT8_MAX
-
-typedef uint8_t oh_output_path_t;
-typedef OH_OUTPUT_COORD_T oh_output_coord_t;
-typedef OH::Point2b oh_output_point_t;
diff --git a/lib/hands/hand_interface.hpp b/lib/hands/hand_interface.hpp
index 34dca343..ee079c5a 100644
--- a/lib/hands/hand_interface.hpp
+++ b/lib/hands/hand_interface.hpp
@@ -5,8 +5,16 @@
 
 namespace SenseShift::Body {
     namespace Hands {
-        typedef std::uint8_t HandPositionIndex_t;
-        typedef enum class HandPosition : HandPositionIndex_t { Left, Right } HandPosition_t;
+        typedef std::uint8_t HandSideIndex_t;
+        typedef enum class HandSide : HandSideIndex_t { Left, Right } HandSide_t;
+        typedef std::uint8_t FingerIndex_t;
+        typedef enum class Finger : FingerIndex_t {
+            Thumb,
+            Index,
+            Middle,
+            Ring,
+            Little,
+        } Finger_t;
 
         namespace Haptics {
             /**
diff --git a/lib/haptics/haptic_body.hpp b/lib/haptics/haptic_body.hpp
index 088bad0e..eb1b06f9 100644
--- a/lib/haptics/haptic_body.hpp
+++ b/lib/haptics/haptic_body.hpp
@@ -23,11 +23,13 @@ namespace SenseShift::Body::Haptics {
 
         void addTarget(const Target_t, VibroPlane* plane);
 
-        const PlaneTargetMap_t* getTargets() const { return &allTargets; }
+        [[nodiscard]] const PlaneTargetMap_t* getTargets() const
+        {
+            return &allTargets;
+        }
 
       private:
         PlaneTargetMap_t allTargets{};
         VibroTargetMap_t vibroTargets{};
-
     };
 } // namespace SenseShift::Body::Haptics
diff --git a/lib/haptics/haptic_plane.hpp b/lib/haptics/haptic_plane.hpp
index 66c47818..193fb633 100644
--- a/lib/haptics/haptic_plane.hpp
+++ b/lib/haptics/haptic_plane.hpp
@@ -34,6 +34,8 @@ namespace SenseShift::Body::Haptics {
         typedef OH::AbstractActuator Actuator_t;
         typedef std::map<Position_t, Actuator_t*> ActuatorMap_t;
 
+        ActuativePlane() = default;
+
         ActuativePlane(const ActuatorMap_t& actuators)
         {
             this->setActuators(actuators);
@@ -87,7 +89,7 @@ namespace SenseShift::Body::Haptics {
     class PlaneMapper_Margin {
       public:
         template<typename _Tp>
-        [[nodiscard]] static constexpr inline std::map<oh_output_point_t, _Tp*>
+        [[nodiscard]] static constexpr inline std::map<Position_t, _Tp*>
           mapMatrixCoordinates(std::vector<std::vector<_Tp*>> map2d)
         {
             std::map<Position_t, _Tp*> points{};
diff --git a/lib/haptics/haptics_interface.hpp b/lib/haptics/haptics_interface.hpp
index ca926f21..5afc9ffd 100644
--- a/lib/haptics/haptics_interface.hpp
+++ b/lib/haptics/haptics_interface.hpp
@@ -32,7 +32,7 @@ namespace SenseShift::Body::Haptics {
         HandLeftMiddle,
         HandLeftRing,
         HandLeftLittle,
-        HandLeftVolar, // Palm
+        HandLeftVolar,  // Palm
         HandLeftDorsal, // Back
 
         HandRightThumb,
@@ -40,7 +40,7 @@ namespace SenseShift::Body::Haptics {
         HandRightMiddle,
         HandRightRing,
         HandRightLittle,
-        HandRightVolar, // Palm
+        HandRightVolar,  // Palm
         HandRightDorsal, // Back
 
         // TODO: arms, legs, etc.
diff --git a/test/test_bhaptics_encoding/main.cpp b/test/test_bhaptics_encoding/main.cpp
index 332fb25d..94457355 100644
--- a/test/test_bhaptics_encoding/main.cpp
+++ b/test/test_bhaptics_encoding/main.cpp
@@ -1,12 +1,10 @@
-#include <bh_devices.hpp>
-#include <bh_encoding.hpp>
-#include <bh_types.hpp>
-
+#include <senseshift/bh/devices.hpp>
+#include <senseshift/bh/encoding.hpp>
 #include <unity.h>
 
 using namespace SenseShift::Body::Haptics;
+using namespace SenseShift::BH;
 using namespace OH;
-using namespace BH;
 
 class TestActuator : public OH::AbstractActuator {
   public:
@@ -27,7 +25,7 @@ class TestActuator : public OH::AbstractActuator {
 void test_layout_tactsuitx16(void)
 {
     static const size_t bhLayoutSize = BH_LAYOUT_TACTSUITX16_SIZE;
-    static const oh_output_point_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTSUITX16;
+    static const OutputLayout_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTSUITX16;
 
     static const size_t layoutGroupsSize = BH_LAYOUT_TACTSUITX16_GROUPS_SIZE;
     static const uint8_t layoutGroups[layoutGroupsSize] = BH_LAYOUT_TACTSUITX16_GROUPS;
@@ -95,7 +93,7 @@ void test_layout_tactsuitx16(void)
 void test_layout_tactsuitx40(void)
 {
     static const size_t bhLayoutSize = BH_LAYOUT_TACTSUITX40_SIZE;
-    static const oh_output_point_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTSUITX40;
+    static const OutputLayout_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTSUITX40;
 
     auto body = new HapticBody();
 
@@ -178,7 +176,7 @@ void test_layout_tactsuitx40(void)
 void test_layout_tactal(void)
 {
     static const size_t bhLayoutSize = BH_LAYOUT_TACTAL_SIZE;
-    static const oh_output_point_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTAL;
+    static const ::SenseShift::Body::Haptics::Position_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTAL;
 
     auto body = new HapticBody();
 
@@ -222,25 +220,21 @@ void test_layout_tactglove(void)
     TestActuator* actuatorLittle = new TestActuator();
     TestActuator* actuatorWrist = new TestActuator();
 
-    const auto& bhLayout = BH::TactGloveLeftLayout;
-
-    auto thumb = new VibroPlane({ { std::get<2>(bhLayout[0]), actuatorThumb } });
-    auto index = new VibroPlane({ { std::get<2>(bhLayout[1]), actuatorIndex } });
-    auto middle = new VibroPlane({ { std::get<2>(bhLayout[2]), actuatorMiddle } });
-    auto ring = new VibroPlane({ { std::get<2>(bhLayout[3]), actuatorRing } });
-    auto little = new VibroPlane({ { std::get<2>(bhLayout[4]), actuatorLittle } });
-    auto wrist = new VibroPlane({ { std::get<2>(bhLayout[5]), actuatorWrist } });
-
     auto body = new HapticBody();
+    const auto& bhLayout = TactGloveLeftLayout;
 
-    body->addTarget(Target::HandLeftThumb, thumb);
-    body->addTarget(Target::HandLeftIndex, index);
-    body->addTarget(Target::HandLeftMiddle, middle);
-    body->addTarget(Target::HandLeftRing, ring);
-    body->addTarget(Target::HandLeftLittle, little);
-    body->addTarget(Target::HandLeftDorsal, wrist);
+    addTactGloveActuators(
+      body,
+      ::SenseShift::Body::Hands::HandSide::Left,
+      actuatorThumb,
+      actuatorIndex,
+      actuatorMiddle,
+      actuatorRing,
+      actuatorLittle,
+      actuatorWrist
+    );
 
-    Decoder::applyPlain(body, { 0x64, 0x00, 0x00, 0x00, 0x00, 0x00 }, bhLayout);
+    Decoder::applyPlain(body, { 0x64, 0x00, 0x00, 0x00, 0x00, 0x00 }, bhLayout, Effect::Vibro);
     TEST_ASSERT_EQUAL_INT(4095, actuatorThumb->intensity);
     TEST_ASSERT_EQUAL_INT(0, actuatorIndex->intensity);
     TEST_ASSERT_EQUAL_INT(0, actuatorMiddle->intensity);
@@ -248,7 +242,7 @@ void test_layout_tactglove(void)
     TEST_ASSERT_EQUAL_INT(0, actuatorLittle->intensity);
     TEST_ASSERT_EQUAL_INT(0, actuatorWrist->intensity);
 
-    Decoder::applyPlain(body, { 0x10, 0x20, 0x30, 0x40, 0x50, 0x60 }, bhLayout);
+    Decoder::applyPlain(body, { 0x10, 0x20, 0x30, 0x40, 0x50, 0x60 }, bhLayout, Effect::Vibro);
     TEST_ASSERT_EQUAL_INT(655, actuatorThumb->intensity);
     TEST_ASSERT_EQUAL_INT(1310, actuatorIndex->intensity);
     TEST_ASSERT_EQUAL_INT(1965, actuatorMiddle->intensity);
@@ -264,6 +258,7 @@ int process(void)
     RUN_TEST(test_layout_tactsuitx16);
     RUN_TEST(test_layout_tactsuitx40);
     RUN_TEST(test_layout_tactal);
+    RUN_TEST(test_layout_tactglove);
 
     return UNITY_END();
 }

From 0b7a1ecfafb2653930d1a394ca6259c8b1b8c4ec Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Mon, 21 Aug 2023 21:56:50 +0400
Subject: [PATCH 08/82] refactor(Output): generic actuators

---
 firmware/mode_configs/bhaptics/tactal.cpp     | 10 +++--
 firmware/mode_configs/bhaptics/tactglove.cpp  | 12 ++---
 firmware/mode_configs/bhaptics/tactosy2.cpp   | 10 +++--
 firmware/mode_configs/bhaptics/tactosyf.cpp   | 10 +++--
 firmware/mode_configs/bhaptics/tactosyh.cpp   | 10 +++--
 .../mode_configs/bhaptics/tactsuit_x16.cpp    | 16 ++++---
 .../bhaptics/tactsuit_x16_pca9685.cpp         | 14 +++---
 .../mode_configs/bhaptics/tactsuit_x40.cpp    | 16 ++++---
 firmware/mode_configs/bhaptics/tactvisor.cpp  | 10 +++--
 .../mode_configs/opengloves/opengloves.cpp    | 12 ++---
 lib/actuators/senseshift/output/actuator.hpp  | 22 ++++++++++
 lib/arduino/output_writers/pwm.cpp            | 29 ------------
 lib/arduino/output_writers/pwm.hpp            | 20 ---------
 lib/arduino/output_writers/servo.hpp          | 30 -------------
 lib/arduino/senseshift/arduino/output/pwm.hpp | 44 +++++++++++++++++++
 .../senseshift/arduino/output/servo.hpp       | 32 ++++++++++++++
 lib/bhaptics/senseshift/bh/devices.hpp        | 12 ++---
 lib/core/types.hpp                            |  6 ---
 lib/haptics/haptic_body.hpp                   |  1 -
 lib/haptics/haptic_plane.cpp                  | 28 ++++++------
 lib/haptics/haptic_plane.hpp                  | 30 ++++++-------
 lib/opengloves/og_ffb.hpp                     | 12 ++---
 lib/output/abstract_actuator.hpp              | 17 -------
 lib/pca9685/output_writers/pca9685.cpp        |  7 ---
 lib/pca9685/output_writers/pca9685.hpp        | 20 ---------
 .../senseshift/arduino/output/pca9685.hpp     | 26 +++++++++++
 test/test_bhaptics_encoding/main.cpp          | 26 +++++------
 test/test_haptics_body/main.cpp               |  9 ++--
 test/test_haptics_plane/main.cpp              |  9 ++--
 29 files changed, 257 insertions(+), 243 deletions(-)
 create mode 100644 lib/actuators/senseshift/output/actuator.hpp
 delete mode 100644 lib/arduino/output_writers/pwm.cpp
 delete mode 100644 lib/arduino/output_writers/pwm.hpp
 delete mode 100644 lib/arduino/output_writers/servo.hpp
 create mode 100644 lib/arduino/senseshift/arduino/output/pwm.hpp
 create mode 100644 lib/arduino/senseshift/arduino/output/servo.hpp
 delete mode 100644 lib/core/types.hpp
 delete mode 100644 lib/output/abstract_actuator.hpp
 delete mode 100644 lib/pca9685/output_writers/pca9685.cpp
 delete mode 100644 lib/pca9685/output_writers/pca9685.hpp
 create mode 100644 lib/pca9685/senseshift/arduino/output/pca9685.hpp

diff --git a/firmware/mode_configs/bhaptics/tactal.cpp b/firmware/mode_configs/bhaptics/tactal.cpp
index de7f9560..ba2a271e 100644
--- a/firmware/mode_configs/bhaptics/tactal.cpp
+++ b/firmware/mode_configs/bhaptics/tactal.cpp
@@ -6,7 +6,7 @@
 
 #include "senseshift.h"
 
-#include <output_writers/pwm.hpp>
+#include <senseshift/arduino/output/pwm.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
@@ -17,6 +17,8 @@
 
 using namespace OH;
 using namespace SenseShift;
+using namespace SenseShift::Arduino::Output;
+using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
 extern SenseShift::SenseShift App;
@@ -28,7 +30,7 @@ static const Position_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTAL;
 void setupMode()
 {
     // Configure PWM pins to their positions on the face
-    const auto faceOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
+    const auto faceOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       // clang-format off
       { new PWMOutputWriter(32), new PWMOutputWriter(33), new PWMOutputWriter(25), new PWMOutputWriter(26), new PWMOutputWriter(27), new PWMOutputWriter(14) },
       // clang-format on
@@ -38,14 +40,14 @@ void setupMode()
 
     app->getHapticBody()->setup();
 
-    auto* bhBleConnection = new BH::BLE::Connection(
+    auto* bhBleConnection = new BLE::Connection(
       {
         .deviceName = BLUETOOTH_NAME,
         .appearance = BH_BLE_APPEARANCE,
         .serialNumber = BH_SERIAL_NUMBER,
       },
       [](std::string& value) -> void {
-          BH::Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect::Vibro, Target::FaceFront);
+          Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect::Vibro, Target::FaceFront);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactglove.cpp b/firmware/mode_configs/bhaptics/tactglove.cpp
index 14acadd8..6ff9d7c4 100644
--- a/firmware/mode_configs/bhaptics/tactglove.cpp
+++ b/firmware/mode_configs/bhaptics/tactglove.cpp
@@ -8,7 +8,7 @@
 
 #include "senseshift.h"
 
-#include <output_writers/pwm.hpp>
+#include <senseshift/arduino/output/pwm.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
@@ -19,6 +19,8 @@
 
 using namespace OH;
 using namespace SenseShift;
+using namespace SenseShift::Arduino::Output;
+using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
 extern SenseShift::SenseShift App;
@@ -27,14 +29,14 @@ SenseShift::SenseShift* app = &App;
 static const Body::Hands::HandSide_t handSide = Body::Hands::HandSide::SENSESHIFT_HAND_SIDE;
 static const size_t bhLayoutSize = BH_LAYOUT_TACTGLOVE_SIZE;
 // clang-format off
-static const BH::OutputLayout_t (&bhLayout)[bhLayoutSize] = handSide == Body::Hands::HandSide::Left ? BH::TactGloveLeftLayout : BH::TactGloveRightLayout;
+static const OutputLayout_t (&bhLayout)[bhLayoutSize] = handSide == Body::Hands::HandSide::Left ? BH::TactGloveLeftLayout : BH::TactGloveRightLayout;
 // clang-format on
 
 void setupMode()
 {
     // Configure PWM pins to their positions on the glove
     // Replace `new PWMOutputWriter(...)` with `nullptr` to disable a specific actuator
-    BH::addTactGloveActuators(
+    addTactGloveActuators(
       app->getHapticBody(),
       handSide,
       new PWMOutputWriter(32), // Thumb
@@ -47,14 +49,14 @@ void setupMode()
 
     app->getHapticBody()->setup();
 
-    auto* bhBleConnection = new BH::BLE::Connection(
+    auto* bhBleConnection = new BLE::Connection(
       {
         .deviceName = BLUETOOTH_NAME,
         .appearance = BH_BLE_APPEARANCE,
         .serialNumber = BH_SERIAL_NUMBER,
       },
       [](std::string& value) -> void {
-          BH::Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect_t::Vibro);
+          Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect_t::Vibro);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactosy2.cpp b/firmware/mode_configs/bhaptics/tactosy2.cpp
index fb6bd77f..6a4af3a6 100644
--- a/firmware/mode_configs/bhaptics/tactosy2.cpp
+++ b/firmware/mode_configs/bhaptics/tactosy2.cpp
@@ -6,7 +6,7 @@
 
 #include "senseshift.h"
 
-#include <output_writers/pwm.hpp>
+#include <senseshift/arduino/output/pwm.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
@@ -17,6 +17,8 @@
 
 using namespace OH;
 using namespace SenseShift;
+using namespace SenseShift::Arduino::Output;
+using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
 extern SenseShift::SenseShift App;
@@ -28,7 +30,7 @@ static const Position_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTOSY2;
 void setupMode()
 {
     // Configure PWM pins to their positions on the forearm
-    auto forearmOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
+    auto forearmOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       // clang-format off
       { new PWMOutputWriter(32), new PWMOutputWriter(33), new PWMOutputWriter(25) },
       { new PWMOutputWriter(26), new PWMOutputWriter(27), new PWMOutputWriter(14) },
@@ -39,14 +41,14 @@ void setupMode()
 
     app->getHapticBody()->setup();
 
-    auto* bhBleConnection = new BH::BLE::Connection(
+    auto* bhBleConnection = new BLE::Connection(
       {
         .deviceName = BLUETOOTH_NAME,
         .appearance = BH_BLE_APPEARANCE,
         .serialNumber = BH_SERIAL_NUMBER,
       },
       [](std::string& value) -> void {
-          BH::Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect::Vibro, Target::Accessory);
+          Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect::Vibro, Target::Accessory);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactosyf.cpp b/firmware/mode_configs/bhaptics/tactosyf.cpp
index 277377ae..4595b904 100644
--- a/firmware/mode_configs/bhaptics/tactosyf.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyf.cpp
@@ -6,7 +6,7 @@
 
 #include "senseshift.h"
 
-#include <output_writers/pwm.hpp>
+#include <senseshift/arduino/output/pwm.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
@@ -17,6 +17,8 @@
 
 using namespace OH;
 using namespace SenseShift;
+using namespace SenseShift::Arduino::Output;
+using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
 extern SenseShift::SenseShift App;
@@ -28,7 +30,7 @@ static const Position_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTOSYF;
 void setupMode()
 {
     // Configure PWM pins to their positions on the feet
-    auto footOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
+    auto footOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       // clang-format off
       { new PWMOutputWriter(32) },
       { new PWMOutputWriter(33) },
@@ -40,14 +42,14 @@ void setupMode()
 
     app->getHapticBody()->setup();
 
-    auto* bhBleConnection = new BH::BLE::Connection(
+    auto* bhBleConnection = new BLE::Connection(
       {
         .deviceName = BLUETOOTH_NAME,
         .appearance = BH_BLE_APPEARANCE,
         .serialNumber = BH_SERIAL_NUMBER,
       },
       [](std::string& value) -> void {
-          BH::Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect::Vibro, Target::Accessory);
+          Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect::Vibro, Target::Accessory);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactosyh.cpp b/firmware/mode_configs/bhaptics/tactosyh.cpp
index 543f2275..c2904352 100644
--- a/firmware/mode_configs/bhaptics/tactosyh.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyh.cpp
@@ -6,7 +6,7 @@
 
 #include "senseshift.h"
 
-#include <output_writers/pwm.hpp>
+#include <senseshift/arduino/output/pwm.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
@@ -17,6 +17,8 @@
 
 using namespace OH;
 using namespace SenseShift;
+using namespace SenseShift::Arduino::Output;
+using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
 extern SenseShift::SenseShift App;
@@ -28,7 +30,7 @@ static const Position_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTOSYH;
 void setupMode()
 {
     // Configure PWM pins to their positions on the hands
-    auto handOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
+    auto handOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       // clang-format off
       { new PWMOutputWriter(32) },
       { new PWMOutputWriter(33) },
@@ -40,14 +42,14 @@ void setupMode()
 
     app->getHapticBody()->setup();
 
-    auto* bhBleConnection = new BH::BLE::Connection(
+    auto* bhBleConnection = new BLE::Connection(
       {
         .deviceName = BLUETOOTH_NAME,
         .appearance = BH_BLE_APPEARANCE,
         .serialNumber = BH_SERIAL_NUMBER,
       },
       [](std::string& value) -> void {
-          BH::Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect::Vibro, Target::Accessory);
+          Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect::Vibro, Target::Accessory);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
index 4b4cf7d2..0fb4875d 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
@@ -6,7 +6,7 @@
 
 #include "senseshift.h"
 
-#include <output_writers/pwm.hpp>
+#include <senseshift/arduino/output/pwm.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
@@ -17,13 +17,15 @@
 
 using namespace OH;
 using namespace SenseShift;
+using namespace SenseShift::Arduino::Output;
+using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
 extern SenseShift::SenseShift App;
 SenseShift::SenseShift* app = &App;
 
 static const size_t bhLayoutSize = BH_LAYOUT_TACTSUITX16_SIZE;
-static const BH::OutputLayout_t bhLayout[BH_LAYOUT_TACTSUITX16_SIZE] = BH_LAYOUT_TACTSUITX16;
+static const OutputLayout_t bhLayout[BH_LAYOUT_TACTSUITX16_SIZE] = BH_LAYOUT_TACTSUITX16;
 
 // Ouput indices, responsible for x40 => x16 grouping
 static const size_t layoutGroupsSize = BH_LAYOUT_TACTSUITX16_GROUPS_SIZE;
@@ -32,16 +34,16 @@ static const uint8_t layoutGroups[layoutGroupsSize] = BH_LAYOUT_TACTSUITX16_GROU
 void setupMode()
 {
     // Configure PWM pins to their positions on the vest
-    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
+    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       // clang-format off
       { new PWMOutputWriter(32), new PWMOutputWriter(33), new PWMOutputWriter(25), new PWMOutputWriter(26) },
       { new PWMOutputWriter(27), new PWMOutputWriter(14), new PWMOutputWriter(12), new PWMOutputWriter(13) },
       // clang-format on
     });
-    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
+    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       // clang-format off
       { new PWMOutputWriter(19), new PWMOutputWriter(18), new PWMOutputWriter(5), new PWMOutputWriter(17) },
-      { new PWMOutputWriter(16), new PWMOutputWriter(4), new PWMOutputWriter(2), new PWMOutputWriter(15) },
+      { new PWMOutputWriter(16), new PWMOutputWriter(4), new PWMOutputWriter(2), new PWMOutputWriter(15)  },
       // clang-format on
     });
 
@@ -50,14 +52,14 @@ void setupMode()
 
     app->getHapticBody()->setup();
 
-    auto* bhBleConnection = new BH::BLE::Connection(
+    auto* bhBleConnection = new BLE::Connection(
       {
         .deviceName = BLUETOOTH_NAME,
         .appearance = BH_BLE_APPEARANCE,
         .serialNumber = BH_SERIAL_NUMBER,
       },
       [](std::string& value) -> void {
-          BH::Decoder::applyVestGrouped(app->getHapticBody(), value, bhLayout, layoutGroups);
+          Decoder::applyVestGrouped(app->getHapticBody(), value, bhLayout, layoutGroups);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
index b9051e9e..74df3252 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
@@ -6,7 +6,7 @@
 
 #include "senseshift.h"
 
-#include <output_writers/pca9685.hpp>
+#include <senseshift/arduino/output/pca9685.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
@@ -17,13 +17,15 @@
 
 using namespace OH;
 using namespace SenseShift;
+using namespace SenseShift::Arduino::Output;
+using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
 extern SenseShift::SenseShift App;
 SenseShift::SenseShift* app = &App;
 
 static const size_t bhLayoutSize = BH_LAYOUT_TACTSUITX16_SIZE;
-static const BH::OutputLayout_t bhLayout[BH_LAYOUT_TACTSUITX16_SIZE] = BH_LAYOUT_TACTSUITX16;
+static const OutputLayout_t bhLayout[BH_LAYOUT_TACTSUITX16_SIZE] = BH_LAYOUT_TACTSUITX16;
 
 // Ouput indices, responsible for x40 => x16 grouping
 static const size_t layoutGroupsSize = BH_LAYOUT_TACTSUITX16_GROUPS_SIZE;
@@ -37,13 +39,13 @@ void setupMode()
     pwm->setPWMFreq(PWM_FREQUENCY);
 
     // Assign the pins on the configured PCA9685 to positions on the vest
-    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
+    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       // clang-format off
       { new PCA9685OutputWriter(pwm, 0), new PCA9685OutputWriter(pwm, 1), new PCA9685OutputWriter(pwm, 2), new PCA9685OutputWriter(pwm, 3) },
       { new PCA9685OutputWriter(pwm, 4), new PCA9685OutputWriter(pwm, 5), new PCA9685OutputWriter(pwm, 6), new PCA9685OutputWriter(pwm, 7) },
       // clang-format on
     });
-    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
+    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       // clang-format off
       { new PCA9685OutputWriter(pwm, 8),  new PCA9685OutputWriter(pwm, 9),  new PCA9685OutputWriter(pwm, 10), new PCA9685OutputWriter(pwm, 11) },
       { new PCA9685OutputWriter(pwm, 12), new PCA9685OutputWriter(pwm, 13), new PCA9685OutputWriter(pwm, 14), new PCA9685OutputWriter(pwm, 15) },
@@ -55,14 +57,14 @@ void setupMode()
 
     app->getHapticBody()->setup();
 
-    auto* bhBleConnection = new BH::BLE::Connection(
+    auto* bhBleConnection = new BLE::Connection(
       {
         .deviceName = BLUETOOTH_NAME,
         .appearance = BH_BLE_APPEARANCE,
         .serialNumber = BH_SERIAL_NUMBER,
       },
       [](std::string& value) -> void {
-          BH::Decoder::applyVestGrouped(app->getHapticBody(), value, bhLayout, layoutGroups);
+          Decoder::applyVestGrouped(app->getHapticBody(), value, bhLayout, layoutGroups);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
index 6ec93001..13a0689f 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
@@ -6,8 +6,8 @@
 
 #include "senseshift.h"
 
-#include <output_writers/pca9685.hpp>
-#include <output_writers/pwm.hpp>
+#include <senseshift/arduino/output/pca9685.hpp>
+#include <senseshift/arduino/output/pwm.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
@@ -18,13 +18,15 @@
 
 using namespace OH;
 using namespace SenseShift;
+using namespace SenseShift::Arduino::Output;
+using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
 extern SenseShift::SenseShift App;
 SenseShift::SenseShift* app = &App;
 
 static const size_t bhLayoutSize = BH_LAYOUT_TACTSUITX40_SIZE;
-static const BH::OutputLayout_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTSUITX40;
+static const OutputLayout_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTSUITX40;
 
 void setupMode()
 {
@@ -39,7 +41,7 @@ void setupMode()
 
     // Assign the pins on the configured PCA9685s and PWM pins to locations on the
     // vest
-    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
+    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       // clang-format off
       { new PCA9685OutputWriter(pwm0, 0),  new PCA9685OutputWriter(pwm0, 1),  new PCA9685OutputWriter(pwm0, 2),  new PCA9685OutputWriter(pwm0, 3)  },
       { new PCA9685OutputWriter(pwm0, 4),  new PCA9685OutputWriter(pwm0, 5),  new PCA9685OutputWriter(pwm0, 6),  new PCA9685OutputWriter(pwm0, 7)  },
@@ -48,7 +50,7 @@ void setupMode()
       { new PWMOutputWriter(32),           new PWMOutputWriter(33),           new PWMOutputWriter(25),           new PWMOutputWriter(26)           },
       // clang-format on
     });
-    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
+    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       // clang-format off
       { new PCA9685OutputWriter(pwm1, 0),  new PCA9685OutputWriter(pwm1, 1),  new PCA9685OutputWriter(pwm1, 2),  new PCA9685OutputWriter(pwm1, 3)  },
       { new PCA9685OutputWriter(pwm1, 4),  new PCA9685OutputWriter(pwm1, 5),  new PCA9685OutputWriter(pwm1, 6),  new PCA9685OutputWriter(pwm1, 7)  },
@@ -63,14 +65,14 @@ void setupMode()
 
     app->getHapticBody()->setup();
 
-    auto* bhBleConnection = new BH::BLE::Connection(
+    auto* bhBleConnection = new BLE::Connection(
       {
         .deviceName = BLUETOOTH_NAME,
         .appearance = BH_BLE_APPEARANCE,
         .serialNumber = BH_SERIAL_NUMBER,
       },
       [](std::string& value) -> void {
-          BH::Decoder::applyVest(app->getHapticBody(), value, bhLayout);
+          Decoder::applyVest(app->getHapticBody(), value, bhLayout);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactvisor.cpp b/firmware/mode_configs/bhaptics/tactvisor.cpp
index 9e2bb10d..73c88463 100644
--- a/firmware/mode_configs/bhaptics/tactvisor.cpp
+++ b/firmware/mode_configs/bhaptics/tactvisor.cpp
@@ -6,7 +6,7 @@
 
 #include "senseshift.h"
 
-#include <output_writers/pwm.hpp>
+#include <senseshift/arduino/output/pwm.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
@@ -17,6 +17,8 @@
 
 using namespace OH;
 using namespace SenseShift;
+using namespace SenseShift::Arduino::Output;
+using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
 extern SenseShift::SenseShift App;
@@ -28,7 +30,7 @@ static const Position_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTVISOR;
 void setupMode()
 {
     // Configure PWM pins to their positions on the face
-    auto faceOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
+    auto faceOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       // clang-format off
       { new PWMOutputWriter(32), new PWMOutputWriter(33), new PWMOutputWriter(25), new PWMOutputWriter(26) },
       // clang-format on
@@ -38,14 +40,14 @@ void setupMode()
 
     app->getHapticBody()->setup();
 
-    auto* bhBleConnection = new BH::BLE::Connection(
+    auto* bhBleConnection = new BLE::Connection(
       {
         .deviceName = BLUETOOTH_NAME,
         .appearance = BH_BLE_APPEARANCE,
         .serialNumber = BH_SERIAL_NUMBER,
       },
       [](std::string& value) -> void {
-          BH::Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect::Vibro, Target::FaceFront);
+          Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect::Vibro, Target::FaceFront);
       },
       app
     );
diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp
index d575a4cb..b1b78eb1 100644
--- a/firmware/mode_configs/opengloves/opengloves.cpp
+++ b/firmware/mode_configs/opengloves/opengloves.cpp
@@ -2,7 +2,7 @@
 #include <og_constants.hpp>
 #include <og_serial_communication.hpp>
 #include <opengloves_task.hpp>
-#include <output_writers/servo.hpp>
+#include <senseshift/arduino/output/servo.hpp>
 #include <sensor.hpp>
 #include <sensor/analog.hpp>
 #include <sensor/digital.hpp>
@@ -285,23 +285,23 @@ OpenGlovesTrackingTask* trackingTask;
 #if FFB_ENABLED
 HandActuators handActuators = {
 #if FFB_THUMB_ENABLED
-    .thumb = new OH::ServoActuator(PIN_FFB_THUMB),
+    .thumb = new SenseShift::Arduino::Output::ServoActuator(PIN_FFB_THUMB),
 #endif
 
 #if FFB_INDEX_ENABLED
-    .index = new OH::ServoActuator(PIN_FFB_INDEX),
+    .index = new SenseShift::Arduino::Output::ServoActuator(PIN_FFB_INDEX),
 #endif
 
 #if FFB_MIDDLE_ENABLED
-    .middle = new OH::ServoActuator(PIN_FFB_MIDDLE),
+    .middle = new SenseShift::Arduino::Output::ServoActuator(PIN_FFB_MIDDLE),
 #endif
 
 #if FFB_RING_ENABLED
-    .ring = new OH::ServoActuator(PIN_FFB_RING),
+    .ring = new SenseShift::Arduino::Output::ServoActuator(PIN_FFB_RING),
 #endif
 
 #if FFB_PINKY_ENABLED
-    .pinky = new OH::ServoActuator(PIN_FFB_PINKY),
+    .pinky = new SenseShift::Arduino::Output::ServoActuator(PIN_FFB_PINKY),
 #endif
 };
 OpenGlovesForceFeedbackTask* ffbTask;
diff --git a/lib/actuators/senseshift/output/actuator.hpp b/lib/actuators/senseshift/output/actuator.hpp
new file mode 100644
index 00000000..5f56db6b
--- /dev/null
+++ b/lib/actuators/senseshift/output/actuator.hpp
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <cstdint>
+
+#include <logging.hpp>
+
+namespace SenseShift::Output {
+    // Singular output point (e.g. vibration motor)
+    template<typename T>
+    class IActuator {
+      public:
+        virtual void setup(){};
+        virtual void writeOutput(T) = 0;
+    };
+
+    template<>
+    class IActuator<std::uint16_t> {
+      public:
+        virtual void setup(){};
+        virtual void writeOutput(std::uint16_t) = 0;
+    };
+} // namespace SenseShift::Output
diff --git a/lib/arduino/output_writers/pwm.cpp b/lib/arduino/output_writers/pwm.cpp
deleted file mode 100644
index 344cce1f..00000000
--- a/lib/arduino/output_writers/pwm.cpp
+++ /dev/null
@@ -1,29 +0,0 @@
-#include <Arduino.h>
-
-#include <output_writers/pwm.hpp>
-#include <utility.hpp>
-
-namespace OH {
-    uint8_t PWMOutputWriter::CHANNELS = 0;
-
-    void PWMOutputWriter::setup()
-    {
-        this->chan = PWMOutputWriter::CHANNELS++;
-
-#if defined(ESP32)
-        ledcSetup(this->chan, this->freq, this->resolution);
-        ledcAttachPin(this->pin, this->chan);
-#else
-        pinMode(this->pin, OUTPUT);
-#endif
-    };
-
-    void PWMOutputWriter::writeOutput(oh_output_intensity_t intensity)
-    {
-#if defined(ESP32)
-        ledcWrite(chan, simpleMap<uint16_t>(intensity, OH_OUTPUT_INTENSITY_MAX, (1 << this->resolution) - 1));
-#else
-        analogWrite(this->pin, simpleMap<uint16_t>(intensity, OH_OUTPUT_INTENSITY_MAX, 255));
-#endif
-    };
-}; // namespace OH
diff --git a/lib/arduino/output_writers/pwm.hpp b/lib/arduino/output_writers/pwm.hpp
deleted file mode 100644
index eecdccbf..00000000
--- a/lib/arduino/output_writers/pwm.hpp
+++ /dev/null
@@ -1,20 +0,0 @@
-#pragma once
-
-#include <abstract_actuator.hpp>
-
-namespace OH {
-    class PWMOutputWriter : public OH::AbstractActuator {
-      private:
-        static uint8_t CHANNELS;
-        uint8_t pin, chan;
-        double freq;
-        uint8_t resolution;
-
-      public:
-        PWMOutputWriter(const uint8_t pin, const double freq = 60, const uint8_t resolution = 12) :
-          pin(pin), freq(freq), resolution(resolution){};
-
-        void setup() override;
-        void writeOutput(oh_output_intensity_t intensity) override;
-    };
-}; // namespace OH
diff --git a/lib/arduino/output_writers/servo.hpp b/lib/arduino/output_writers/servo.hpp
deleted file mode 100644
index 6223d265..00000000
--- a/lib/arduino/output_writers/servo.hpp
+++ /dev/null
@@ -1,30 +0,0 @@
-#pragma once
-
-#include <abstract_actuator.hpp>
-#include <utility.hpp>
-
-#include <ESP32Servo.h>
-
-namespace OH {
-    class ServoActuator : public AbstractActuator {
-      public:
-        ServoActuator(const uint8_t pin, const uint16_t min = 500, const uint16_t max = 2400) :
-          pin(pin), min(min), max(max){};
-
-        void setup() override
-        {
-            servo.attach(this->pin, this->min, this->max);
-            this->writeOutput(0);
-        };
-
-        void writeOutput(oh_output_intensity_t intensity) override
-        {
-            servo.writeMicroseconds(accurateMap<uint16_t>(intensity, 0, OH_OUTPUT_INTENSITY_MAX, this->min, this->max));
-        };
-
-      protected:
-        Servo servo = Servo();
-        uint8_t pin;
-        uint16_t min, max;
-    };
-} // namespace OH
diff --git a/lib/arduino/senseshift/arduino/output/pwm.hpp b/lib/arduino/senseshift/arduino/output/pwm.hpp
new file mode 100644
index 00000000..a8145565
--- /dev/null
+++ b/lib/arduino/senseshift/arduino/output/pwm.hpp
@@ -0,0 +1,44 @@
+#pragma once
+
+#include <senseshift/output/actuator.hpp>
+#include <utility.hpp>
+
+#include <Arduino.h>
+
+namespace SenseShift::Arduino::Output {
+    class PWMOutputWriter : public ::SenseShift::Output::IActuator<std::uint16_t> {
+      public:
+        static inline constexpr const std::uint16_t MAX_INTENSITY = 4095;
+
+        PWMOutputWriter(const std::uint8_t pin, const double freq = 60, const std::uint8_t resolution = 12) :
+          pin(pin), freq(freq), resolution(resolution){};
+
+        void setup() override
+        {
+            this->chan = CHANNELS++;
+
+#if defined(ESP32)
+            ledcSetup(this->chan, this->freq, this->resolution);
+            ledcAttachPin(this->pin, this->chan);
+#else
+            pinMode(this->pin, OUTPUT);
+#endif
+        }
+
+        void writeOutput(std::uint16_t intensity) override
+        {
+#if defined(ESP32)
+            ledcWrite(chan, OH::simpleMap<std::uint16_t>(intensity, MAX_INTENSITY, (1 << this->resolution) - 1));
+#else
+            // Arduino only supports 8-bit PWM
+            analogWrite(this->pin, OH::simpleMap<std::uint16_t>(intensity, MAX_INTENSITY, (1 << 8) - 1));
+#endif
+        }
+
+      private:
+        static inline std::uint8_t CHANNELS = 0;
+        std::uint8_t pin, chan;
+        double freq;
+        std::uint8_t resolution;
+    };
+}; // namespace SenseShift::Arduino::Output
diff --git a/lib/arduino/senseshift/arduino/output/servo.hpp b/lib/arduino/senseshift/arduino/output/servo.hpp
new file mode 100644
index 00000000..88fa6c85
--- /dev/null
+++ b/lib/arduino/senseshift/arduino/output/servo.hpp
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <senseshift/output/actuator.hpp>
+#include <utility.hpp>
+
+#include <ESP32Servo.h>
+
+namespace SenseShift::Arduino::Output {
+    class ServoActuator : public ::SenseShift::Output::IActuator<std::uint16_t> {
+      public:
+        static inline constexpr const std::uint16_t MAX_INTENSITY = 4095;
+
+        ServoActuator(const uint8_t pin, const std::uint16_t min = 500, const std::uint16_t max = 2400) :
+          pin(pin), min(min), max(max){};
+
+        void setup() override
+        {
+            servo.attach(this->pin, this->min, this->max);
+            this->writeOutput(0);
+        };
+
+        void writeOutput(std::uint16_t intensity) override
+        {
+            servo.writeMicroseconds(OH::accurateMap<std::uint16_t>(intensity, 0, MAX_INTENSITY, this->min, this->max));
+        };
+
+      private:
+        Servo servo = Servo();
+        uint8_t pin;
+        std::uint16_t min, max;
+    };
+} // namespace SenseShift::Arduino::Output
diff --git a/lib/bhaptics/senseshift/bh/devices.hpp b/lib/bhaptics/senseshift/bh/devices.hpp
index 7c5531f0..d3c24d1b 100644
--- a/lib/bhaptics/senseshift/bh/devices.hpp
+++ b/lib/bhaptics/senseshift/bh/devices.hpp
@@ -297,12 +297,12 @@ namespace SenseShift::BH {
     inline void addTactGloveActuators(
       HapticBody* hapticBody,
       const HandSide_t side,
-      OH::AbstractActuator* const thumbActuator,
-      OH::AbstractActuator* const indexActuator,
-      OH::AbstractActuator* const middleActuator,
-      OH::AbstractActuator* const ringActuator,
-      OH::AbstractActuator* const littleActuator,
-      OH::AbstractActuator* const wristActuator
+      VibroPlane::Actuator_t* const thumbActuator,
+      VibroPlane::Actuator_t* const indexActuator,
+      VibroPlane::Actuator_t* const middleActuator,
+      VibroPlane::Actuator_t* const ringActuator,
+      VibroPlane::Actuator_t* const littleActuator,
+      VibroPlane::Actuator_t* const wristActuator
     )
     {
         const OutputLayout_t(&layout)[6] = (side == HandSide_t::Left) ? TactGloveLeftLayout : TactGloveRightLayout;
diff --git a/lib/core/types.hpp b/lib/core/types.hpp
deleted file mode 100644
index 49a7c3c3..00000000
--- a/lib/core/types.hpp
+++ /dev/null
@@ -1,6 +0,0 @@
-#pragma once
-
-#include <abstract_actuator.hpp>
-#include <point2.hpp>
-
-#include <map>
diff --git a/lib/haptics/haptic_body.hpp b/lib/haptics/haptic_body.hpp
index eb1b06f9..351e31eb 100644
--- a/lib/haptics/haptic_body.hpp
+++ b/lib/haptics/haptic_body.hpp
@@ -3,7 +3,6 @@
 #include "haptic_plane.hpp"
 #include "haptics_interface.hpp"
 
-#include <types.hpp>
 #include <utility.hpp>
 
 #include <map>
diff --git a/lib/haptics/haptic_plane.cpp b/lib/haptics/haptic_plane.cpp
index 6c10861f..d22c6f55 100644
--- a/lib/haptics/haptic_plane.cpp
+++ b/lib/haptics/haptic_plane.cpp
@@ -7,8 +7,8 @@
 #include "haptic_plane.hpp"
 
 namespace SenseShift::Body::Haptics {
-    template<typename _Tp>
-    void ActuativePlane<_Tp>::setActuators(const ActuatorMap_t& actuators)
+    template<typename _Tp, typename _Ta>
+    void ActuativePlane<_Tp, _Ta>::setActuators(const ActuatorMap_t& actuators)
     {
         this->actuators.clear();
         for (const auto& [point, actuator] : actuators) {
@@ -26,16 +26,16 @@ namespace SenseShift::Body::Haptics {
         }
     }
 
-    template<typename _Tp>
-    void ActuativePlane<_Tp>::setup()
+    template<typename _Tp, typename _Ta>
+    void ActuativePlane<_Tp, _Ta>::setup()
     {
         for (const auto& [point, actuator] : this->actuators) {
             actuator->setup();
         }
     }
 
-    template<typename _Tp>
-    void ActuativePlane<_Tp>::effect(const Position_t& pos, const Value_t& val)
+    template<typename _Tp, typename _Ta>
+    void ActuativePlane<_Tp, _Ta>::effect(const Position_t& pos, const Value_t& val)
     {
         auto it = this->actuators.find(pos);
         if (it == this->actuators.end()) {
@@ -47,16 +47,16 @@ namespace SenseShift::Body::Haptics {
         this->states[pos] = val;
     }
 
-    template<typename _Tp>
-    void ActuativePlane_Closest<_Tp>::effect(const Position_t& pos, const Value_t& val)
+    template<typename _Tp, typename _Ta>
+    void ActuativePlane_Closest<_Tp, _Ta>::effect(const Position_t& pos, const Value_t& val)
     {
         auto& closest = this->findClosestPoint(*this->getAvailablePoints(), pos);
-        ActuativePlane<_Tp>::effect(closest, val);
+        ActuativePlane<_Tp, _Ta>::effect(closest, val);
     }
 
-    template<typename _Tp>
+    template<typename _Tp, typename _Ta>
     const Position_t&
-      ActuativePlane_Closest<_Tp>::findClosestPoint(const PositionSet_t& pts, const Position_t& target) const
+      ActuativePlane_Closest<_Tp, _Ta>::findClosestPoint(const PositionSet_t& pts, const Position_t& target) const
     {
         // check if exact point exists
         auto it = pts.find(target);
@@ -75,6 +75,8 @@ namespace SenseShift::Body::Haptics {
         return nearest->second;
     }
 
-    template class ActuativePlane<VibroEffectData_t>;
-    template class ActuativePlane_Closest<VibroEffectData_t>;
+    template class ActuativePlane<VibroEffectData_t, ::SenseShift::Output::IActuator<VibroEffectData_t::Intensity_t>>;
+    template class ActuativePlane_Closest<
+      VibroEffectData_t,
+      ::SenseShift::Output::IActuator<VibroEffectData_t::Intensity_t>>;
 } // namespace SenseShift::Body::Haptics
diff --git a/lib/haptics/haptic_plane.hpp b/lib/haptics/haptic_plane.hpp
index 193fb633..97aac1c9 100644
--- a/lib/haptics/haptic_plane.hpp
+++ b/lib/haptics/haptic_plane.hpp
@@ -2,8 +2,8 @@
 
 #include "haptics_interface.hpp"
 
-#include <abstract_actuator.hpp>
-#include <types.hpp>
+#include <senseshift/output/actuator.hpp>
+
 #include <utility.hpp>
 
 #include <list>
@@ -19,20 +19,16 @@ namespace SenseShift::Body::Haptics {
      *
      * @tparam _Tp The type of the output value.
      */
-    template<typename _Tp>
+    template<typename _Tp, typename _Ta>
     class ActuativePlane {
         static_assert(std::is_same<_Tp, VibroEffectData_t>());
 
       public:
-        typedef _Tp Value_t;
-        typedef std::map<Position_t, Value_t> PositionStateMap_t;
+        using Value_t = _Tp;
+        using Actuator_t = _Ta;
 
-        /**
-         * The type of the actuator.
-         * @TODO: Make this a template parameter
-         */
-        typedef OH::AbstractActuator Actuator_t;
         typedef std::map<Position_t, Actuator_t*> ActuatorMap_t;
+        typedef std::map<Position_t, Value_t> PositionStateMap_t;
 
         ActuativePlane() = default;
 
@@ -61,19 +57,20 @@ namespace SenseShift::Body::Haptics {
         void setActuators(const ActuatorMap_t&);
     };
 
-    typedef ActuativePlane<VibroEffectData_t> VibroPlane;
+    typedef ActuativePlane<VibroEffectData_t, ::SenseShift::Output::IActuator<VibroEffectData_t::Intensity_t>>
+      VibroPlane;
 
     /**
      * Output plane, finds the closest actuator for the given point.
      * @deprecated We should guarantee on the driver level, that the actuator is always exists
      */
-    template<typename _Tp>
-    class ActuativePlane_Closest : public ActuativePlane<_Tp> {
+    template<typename _Tp, typename _Ta>
+    class ActuativePlane_Closest : public ActuativePlane<_Tp, _Ta> {
       public:
         typedef _Tp Value_t;
 
-        ActuativePlane_Closest(const typename ActuativePlane<_Tp>::ActuatorMap_t& actuators) :
-          ActuativePlane<_Tp>(actuators)
+        ActuativePlane_Closest(const typename ActuativePlane<_Tp, _Ta>::ActuatorMap_t& actuators) :
+          ActuativePlane<_Tp, _Ta>(actuators)
         {
         }
 
@@ -83,7 +80,8 @@ namespace SenseShift::Body::Haptics {
         [[nodiscard]] const Position_t& findClosestPoint(const PositionSet_t&, const Position_t&) const;
     };
 
-    typedef ActuativePlane_Closest<VibroEffectData_t> VibroPlane_Closest;
+    typedef ActuativePlane_Closest<VibroEffectData_t, ::SenseShift::Output::IActuator<VibroEffectData_t::Intensity_t>>
+      VibroPlane_Closest;
 
     // TODO: configurable margin
     class PlaneMapper_Margin {
diff --git a/lib/opengloves/og_ffb.hpp b/lib/opengloves/og_ffb.hpp
index c1a8b42a..ca2c5b9d 100644
--- a/lib/opengloves/og_ffb.hpp
+++ b/lib/opengloves/og_ffb.hpp
@@ -1,15 +1,15 @@
 #pragma once
 
-#include <abstract_actuator.hpp>
+#include <senseshift/output/actuator.hpp>
 
 #include <optional>
 
 namespace OpenGloves {
     struct HandActuators {
-        std::optional<OH::AbstractActuator*> thumb = std::nullopt;
-        std::optional<OH::AbstractActuator*> index = std::nullopt;
-        std::optional<OH::AbstractActuator*> middle = std::nullopt;
-        std::optional<OH::AbstractActuator*> ring = std::nullopt;
-        std::optional<OH::AbstractActuator*> pinky = std::nullopt;
+        std::optional<::SenseShift::Output::IActuator<uint16_t>*> thumb = std::nullopt;
+        std::optional<::SenseShift::Output::IActuator<uint16_t>*> index = std::nullopt;
+        std::optional<::SenseShift::Output::IActuator<uint16_t>*> middle = std::nullopt;
+        std::optional<::SenseShift::Output::IActuator<uint16_t>*> ring = std::nullopt;
+        std::optional<::SenseShift::Output::IActuator<uint16_t>*> pinky = std::nullopt;
     };
 } // namespace OpenGloves
diff --git a/lib/output/abstract_actuator.hpp b/lib/output/abstract_actuator.hpp
deleted file mode 100644
index 50df1752..00000000
--- a/lib/output/abstract_actuator.hpp
+++ /dev/null
@@ -1,17 +0,0 @@
-#pragma once
-
-#include <stdint.h>
-
-#define OH_OUTPUT_INTENSITY_T uint16_t
-#define OH_OUTPUT_INTENSITY_MAX 4095
-
-typedef OH_OUTPUT_INTENSITY_T oh_output_intensity_t;
-
-namespace OH {
-    //! Singular output point (e.g. vibration motor)
-    class AbstractActuator {
-      public:
-        virtual void setup(){};
-        virtual void writeOutput(oh_output_intensity_t intensity) = 0;
-    };
-} // namespace OH
diff --git a/lib/pca9685/output_writers/pca9685.cpp b/lib/pca9685/output_writers/pca9685.cpp
deleted file mode 100644
index 4e20d609..00000000
--- a/lib/pca9685/output_writers/pca9685.cpp
+++ /dev/null
@@ -1,7 +0,0 @@
-#include "output_writers/pca9685.hpp"
-#include <utility.hpp>
-
-void OH::PCA9685OutputWriter::writeOutput(oh_output_intensity_t intensity)
-{
-    this->driver->setPin(this->num, simpleMap<uint16_t>(intensity, OH_OUTPUT_INTENSITY_MAX, 4095));
-}
diff --git a/lib/pca9685/output_writers/pca9685.hpp b/lib/pca9685/output_writers/pca9685.hpp
deleted file mode 100644
index 69db3fcf..00000000
--- a/lib/pca9685/output_writers/pca9685.hpp
+++ /dev/null
@@ -1,20 +0,0 @@
-#pragma once
-
-#include <Adafruit_PWMServoDriver.h>
-#include <Arduino.h>
-#include <Wire.h>
-
-#include <abstract_actuator.hpp>
-
-namespace OH {
-    class PCA9685OutputWriter : public OH::AbstractActuator {
-      private:
-        Adafruit_PWMServoDriver* driver;
-        uint8_t num;
-
-      public:
-        PCA9685OutputWriter(Adafruit_PWMServoDriver* driver, const uint8_t num) : driver(driver), num(num){};
-
-        void writeOutput(oh_output_intensity_t intensity) override;
-    };
-} // namespace OH
diff --git a/lib/pca9685/senseshift/arduino/output/pca9685.hpp b/lib/pca9685/senseshift/arduino/output/pca9685.hpp
new file mode 100644
index 00000000..a6529521
--- /dev/null
+++ b/lib/pca9685/senseshift/arduino/output/pca9685.hpp
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <senseshift/output/actuator.hpp>
+#include <utility.hpp>
+
+#include <Adafruit_PWMServoDriver.h>
+#include <Arduino.h>
+#include <Wire.h>
+
+namespace SenseShift::Arduino::Output {
+    class PCA9685OutputWriter : public ::SenseShift::Output::IActuator<std::uint16_t> {
+      public:
+        static inline constexpr const std::uint16_t MAX_INTENSITY = 4095;
+
+        PCA9685OutputWriter(Adafruit_PWMServoDriver* driver, const std::uint8_t num) : driver(driver), num(num){};
+
+        void writeOutput(std::uint16_t intensity) override
+        {
+            this->driver->setPin(this->num, OH::simpleMap<std::uint16_t>(intensity, MAX_INTENSITY, 4095));
+        }
+
+      private:
+        Adafruit_PWMServoDriver* driver;
+        std::uint8_t num;
+    };
+} // namespace SenseShift::Arduino::Output
diff --git a/test/test_bhaptics_encoding/main.cpp b/test/test_bhaptics_encoding/main.cpp
index 94457355..2de6e52e 100644
--- a/test/test_bhaptics_encoding/main.cpp
+++ b/test/test_bhaptics_encoding/main.cpp
@@ -2,21 +2,21 @@
 #include <senseshift/bh/encoding.hpp>
 #include <unity.h>
 
-using namespace SenseShift::Body::Haptics;
 using namespace SenseShift::BH;
-using namespace OH;
+using namespace SenseShift::Body::Haptics;
+using namespace SenseShift::Output;
 
-class TestActuator : public OH::AbstractActuator {
+class TestActuator : public IActuator<uint16_t> {
   public:
     bool isSetup = false;
-    oh_output_intensity_t intensity = 0;
+    uint16_t intensity = 0;
 
-    TestActuator() : AbstractActuator() {}
+    TestActuator() : IActuator<uint16_t>() {}
     void setup() override
     {
         this->isSetup = true;
     }
-    void writeOutput(oh_output_intensity_t intensity) override
+    void writeOutput(uint16_t intensity) override
     {
         this->intensity = intensity;
     }
@@ -49,11 +49,11 @@ void test_layout_tactsuitx16(void)
     TestActuator* actuator14 = new TestActuator();
     TestActuator* actuator15 = new TestActuator();
 
-    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<OH::AbstractActuator>({
+    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       { actuator0, actuator1, actuator2, actuator3 },
       { actuator4, actuator5, actuator6, actuator7 },
     });
-    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
+    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       { actuator8, actuator9, actuator10, actuator11 },
       { actuator12, actuator13, actuator14, actuator15 },
     });
@@ -97,14 +97,14 @@ void test_layout_tactsuitx40(void)
 
     auto body = new HapticBody();
 
-    std::vector<std::vector<AbstractActuator*>> frontMatrix = {
+    std::vector<std::vector<VibroPlane::Actuator_t*>> frontMatrix = {
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
     };
-    std::vector<std::vector<AbstractActuator*>> backMatrix = {
+    std::vector<std::vector<VibroPlane::Actuator_t*>> backMatrix = {
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
@@ -112,8 +112,8 @@ void test_layout_tactsuitx40(void)
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
     };
 
-    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>(frontMatrix);
-    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>(backMatrix);
+    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>(frontMatrix);
+    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>(backMatrix);
 
     auto frontPlane = new VibroPlane(frontOutputs);
     auto backPlane = new VibroPlane(backOutputs);
@@ -187,7 +187,7 @@ void test_layout_tactal(void)
     TestActuator* actuator4 = new TestActuator();
     TestActuator* actuator5 = new TestActuator();
 
-    auto outputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
+    auto outputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       { actuator0, actuator1, actuator2, actuator3, actuator4, actuator5 },
     });
     auto plane = new VibroPlane(outputs);
diff --git a/test/test_haptics_body/main.cpp b/test/test_haptics_body/main.cpp
index f5ceb76f..27b8b56e 100644
--- a/test/test_haptics_body/main.cpp
+++ b/test/test_haptics_body/main.cpp
@@ -2,18 +2,19 @@
 #include <unity.h>
 
 using namespace SenseShift::Body::Haptics;
+using namespace SenseShift::Output;
 
-class TestActuator : public OH::AbstractActuator {
+class TestActuator : public IActuator<uint16_t> {
   public:
     bool isSetup = false;
-    oh_output_intensity_t intensity = 0;
+    uint16_t intensity = 0;
 
-    TestActuator() : AbstractActuator() {}
+    TestActuator() : IActuator<uint16_t>() {}
     void setup() override
     {
         this->isSetup = true;
     }
-    void writeOutput(oh_output_intensity_t intensity) override
+    void writeOutput(uint16_t intensity) override
     {
         this->intensity = intensity;
     }
diff --git a/test/test_haptics_plane/main.cpp b/test/test_haptics_plane/main.cpp
index ec1af818..38e4594b 100644
--- a/test/test_haptics_plane/main.cpp
+++ b/test/test_haptics_plane/main.cpp
@@ -2,18 +2,19 @@
 #include <unity.h>
 
 using namespace SenseShift::Body::Haptics;
+using namespace SenseShift::Output;
 
-class TestActuator : public OH::AbstractActuator {
+class TestActuator : public IActuator<uint16_t> {
   public:
     bool isSetup = false;
-    oh_output_intensity_t intensity = 0;
+    uint16_t intensity = 0;
 
-    TestActuator() : AbstractActuator() {}
+    TestActuator() : IActuator<uint16_t>() {}
     void setup() override
     {
         this->isSetup = true;
     }
-    void writeOutput(oh_output_intensity_t intensity) override
+    void writeOutput(uint16_t intensity) override
     {
         this->intensity = intensity;
     }

From 4977fd0e3be1560aaaff1459ec9baebe0ae543db Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Mon, 21 Aug 2023 22:50:45 +0400
Subject: [PATCH 09/82] refactor(Core): move to `SenseShift` namespace and
 directory

---
 firmware/mode_configs/bhaptics/tactglove.cpp  |  3 +-
 .../mode_configs/opengloves/opengloves.cpp    |  2 +-
 firmware/senseshift.cpp                       |  6 ++--
 include/senseshift.h                          | 11 ++++----
 lib/actuators/senseshift/output/actuator.hpp  |  2 +-
 lib/arduino/battery/adc_naive.cpp             |  4 +--
 lib/arduino/senseshift/arduino/output/pwm.hpp |  9 ++++--
 .../senseshift/arduino/output/servo.hpp       |  6 ++--
 lib/battery/abstract_battery.hpp              | 13 +++++----
 lib/bhaptics/senseshift/bh/encoding.hpp       | 10 ++++---
 .../senseshift/bh/ble/connection.cpp          | 13 ++++-----
 .../senseshift/bh/ble/connection.hpp          | 20 +++++++------
 lib/calibration/calibration.hpp               | 28 +++++++++++++------
 lib/core/abstract_connection.hpp              |  9 ------
 lib/core/{ => senseshift}/events.hpp          |  4 +--
 lib/core/{ => senseshift}/logging.hpp         |  4 +++
 lib/core/{ => senseshift}/utility.hpp         |  6 ++--
 lib/freertos/task.hpp                         |  2 +-
 lib/haptics/haptic_body.cpp                   |  2 +-
 lib/haptics/haptic_body.hpp                   |  2 +-
 lib/haptics/haptic_plane.cpp                  |  4 +--
 lib/haptics/haptic_plane.hpp                  |  7 ++---
 lib/opengloves/og_alpha_encoding.hpp          |  5 ++--
 lib/opengloves_task/opengloves_task.hpp       |  2 +-
 .../senseshift/arduino/output/pca9685.hpp     |  4 +--
 lib/sensor/sensor.hpp                         |  2 +-
 test/test_core_utility/main.cpp               |  4 +--
 27 files changed, 100 insertions(+), 84 deletions(-)
 delete mode 100644 lib/core/abstract_connection.hpp
 rename lib/core/{ => senseshift}/events.hpp (92%)
 rename lib/core/{ => senseshift}/logging.hpp (99%)
 rename lib/core/{ => senseshift}/utility.hpp (94%)

diff --git a/firmware/mode_configs/bhaptics/tactglove.cpp b/firmware/mode_configs/bhaptics/tactglove.cpp
index 6ff9d7c4..8f663e62 100644
--- a/firmware/mode_configs/bhaptics/tactglove.cpp
+++ b/firmware/mode_configs/bhaptics/tactglove.cpp
@@ -4,14 +4,13 @@
 #include <Arduino.h>
 #include <Wire.h>
 
-#include <utility.hpp>
-
 #include "senseshift.h"
 
 #include <senseshift/arduino/output/pwm.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
+#include <senseshift/utility.hpp>
 
 #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
 #include <battery/adc_naive.hpp>
diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp
index b1b78eb1..6fadafdc 100644
--- a/firmware/mode_configs/opengloves/opengloves.cpp
+++ b/firmware/mode_configs/opengloves/opengloves.cpp
@@ -3,13 +3,13 @@
 #include <og_serial_communication.hpp>
 #include <opengloves_task.hpp>
 #include <senseshift/arduino/output/servo.hpp>
+#include <senseshift/utility.hpp>
 #include <sensor.hpp>
 #include <sensor/analog.hpp>
 #include <sensor/digital.hpp>
 #include <sensor/joystick.hpp>
 #include <sensor/og_finger.hpp>
 #include <sensor/og_gesture.hpp>
-#include <utility.hpp>
 
 #pragma region Communication
 
diff --git a/firmware/senseshift.cpp b/firmware/senseshift.cpp
index 2605ce3e..3ba2b1f7 100644
--- a/firmware/senseshift.cpp
+++ b/firmware/senseshift.cpp
@@ -6,7 +6,7 @@
 #include <Arduino.h>
 #endif
 
-#include <logging.hpp>
+#include <senseshift/logging.hpp>
 
 namespace SenseShift {
     SenseShift::SenseShift()
@@ -14,7 +14,7 @@ namespace SenseShift {
         this->pHapticBody = new Body::Haptics::HapticBody();
     }
 
-    void SenseShift::postEvent(const OH::IEvent* event)
+    void SenseShift::postEvent(const IEvent* event)
     {
         log_i("Event dispatched at %u: %s (%p)", millis(), event->eventName.c_str(), event);
 
@@ -25,7 +25,7 @@ namespace SenseShift {
         delete event;
     }
 
-    void SenseShift::addEventListener(const OH::IEventListener* listener)
+    void SenseShift::addEventListener(const IEventListener* listener)
     {
         this->eventListeners.push_back(listener);
     }
diff --git a/include/senseshift.h b/include/senseshift.h
index f973af46..f2c94937 100644
--- a/include/senseshift.h
+++ b/include/senseshift.h
@@ -2,9 +2,8 @@
 
 #include "config/all.h"
 
-#include <abstract_connection.hpp>
-#include <events.hpp>
 #include <haptic_body.hpp>
+#include <senseshift/events.hpp>
 
 #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
 #include <abstract_battery.hpp>
@@ -13,9 +12,9 @@
 #include <vector>
 
 namespace SenseShift {
-    class SenseShift final : public OH::IEventDispatcher {
+    class SenseShift final : public IEventDispatcher {
       private:
-        std::vector<const OH::IEventListener*> eventListeners{};
+        std::vector<const IEventListener*> eventListeners{};
         Body::Haptics::HapticBody* pHapticBody;
 
 #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
@@ -30,7 +29,7 @@ namespace SenseShift {
             return this->pHapticBody;
         };
 
-        void postEvent(const OH::IEvent* event) override;
-        void addEventListener(const OH::IEventListener* listener) override;
+        void postEvent(const IEvent* event) override;
+        void addEventListener(const IEventListener* listener) override;
     };
 } // namespace SenseShift
diff --git a/lib/actuators/senseshift/output/actuator.hpp b/lib/actuators/senseshift/output/actuator.hpp
index 5f56db6b..3938c65f 100644
--- a/lib/actuators/senseshift/output/actuator.hpp
+++ b/lib/actuators/senseshift/output/actuator.hpp
@@ -2,7 +2,7 @@
 
 #include <cstdint>
 
-#include <logging.hpp>
+#include <senseshift/logging.hpp>
 
 namespace SenseShift::Output {
     // Singular output point (e.g. vibration motor)
diff --git a/lib/arduino/battery/adc_naive.cpp b/lib/arduino/battery/adc_naive.cpp
index 6ee9487f..d9991715 100644
--- a/lib/arduino/battery/adc_naive.cpp
+++ b/lib/arduino/battery/adc_naive.cpp
@@ -1,7 +1,7 @@
 #include "battery/adc_naive.hpp"
 
 #include <Arduino.h>
-#include <utility.hpp>
+#include <senseshift/utility.hpp>
 
 namespace OH {
     void ADCNaiveBattery::setup()
@@ -11,6 +11,6 @@ namespace OH {
 
     BatteryState ADCNaiveBattery::getValue()
     {
-        return { .level = static_cast<uint8_t>(simpleMap<uint16_t>(analogRead(this->pin), 4095, 255)) };
+        return { .level = static_cast<uint8_t>(::SenseShift::simpleMap<uint16_t>(analogRead(this->pin), 4095, 255)) };
     }
 } // namespace OH
diff --git a/lib/arduino/senseshift/arduino/output/pwm.hpp b/lib/arduino/senseshift/arduino/output/pwm.hpp
index a8145565..50605e5b 100644
--- a/lib/arduino/senseshift/arduino/output/pwm.hpp
+++ b/lib/arduino/senseshift/arduino/output/pwm.hpp
@@ -1,7 +1,7 @@
 #pragma once
 
 #include <senseshift/output/actuator.hpp>
-#include <utility.hpp>
+#include <senseshift/utility.hpp>
 
 #include <Arduino.h>
 
@@ -28,10 +28,13 @@ namespace SenseShift::Arduino::Output {
         void writeOutput(std::uint16_t intensity) override
         {
 #if defined(ESP32)
-            ledcWrite(chan, OH::simpleMap<std::uint16_t>(intensity, MAX_INTENSITY, (1 << this->resolution) - 1));
+            ledcWrite(
+              chan,
+              ::SenseShift::simpleMap<std::uint16_t>(intensity, MAX_INTENSITY, (1 << this->resolution) - 1)
+            );
 #else
             // Arduino only supports 8-bit PWM
-            analogWrite(this->pin, OH::simpleMap<std::uint16_t>(intensity, MAX_INTENSITY, (1 << 8) - 1));
+            analogWrite(this->pin, ::SenseShift::simpleMap<std::uint16_t>(intensity, MAX_INTENSITY, (1 << 8) - 1));
 #endif
         }
 
diff --git a/lib/arduino/senseshift/arduino/output/servo.hpp b/lib/arduino/senseshift/arduino/output/servo.hpp
index 88fa6c85..027c0b05 100644
--- a/lib/arduino/senseshift/arduino/output/servo.hpp
+++ b/lib/arduino/senseshift/arduino/output/servo.hpp
@@ -1,7 +1,7 @@
 #pragma once
 
 #include <senseshift/output/actuator.hpp>
-#include <utility.hpp>
+#include <senseshift/utility.hpp>
 
 #include <ESP32Servo.h>
 
@@ -21,7 +21,9 @@ namespace SenseShift::Arduino::Output {
 
         void writeOutput(std::uint16_t intensity) override
         {
-            servo.writeMicroseconds(OH::accurateMap<std::uint16_t>(intensity, 0, MAX_INTENSITY, this->min, this->max));
+            servo.writeMicroseconds(
+              ::SenseShift::accurateMap<std::uint16_t>(intensity, 0, MAX_INTENSITY, this->min, this->max)
+            );
         };
 
       private:
diff --git a/lib/battery/abstract_battery.hpp b/lib/battery/abstract_battery.hpp
index 94c6c71f..feb3cdb1 100644
--- a/lib/battery/abstract_battery.hpp
+++ b/lib/battery/abstract_battery.hpp
@@ -1,6 +1,6 @@
 #pragma once
 
-#include <events.hpp>
+#include <senseshift/events.hpp>
 #include <sensor.hpp>
 #include <task.hpp>
 
@@ -45,10 +45,10 @@ namespace OH {
         uint8_t level;
     };
 
-    class BatteryLevelEvent : public IEvent {
+    class BatteryLevelEvent : public ::SenseShift::IEvent {
       public:
         const BatteryState state;
-        BatteryLevelEvent(const BatteryState state) : IEvent(OH_EVENT_BATTERY_LEVEL), state(state){};
+        BatteryLevelEvent(const BatteryState state) : ::SenseShift::IEvent(OH_EVENT_BATTERY_LEVEL), state(state){};
     };
 
     struct BatteryConfig {
@@ -65,11 +65,14 @@ namespace OH {
         friend class TaskedSensor<BatteryState>;
 
       private:
-        IEventDispatcher* eventDispatcher;
+        ::SenseShift::IEventDispatcher* eventDispatcher;
 
       public:
         BatterySensor(
-          IBatterySensor* sensor, IEventDispatcher* eventDispatcher, BatteryConfig config, TaskConfig taskConfig
+          IBatterySensor* sensor,
+          ::SenseShift::IEventDispatcher* eventDispatcher,
+          BatteryConfig config,
+          TaskConfig taskConfig
         ) :
           TaskedSensor<BatteryState>(sensor, taskConfig, config.sampleRate), eventDispatcher(eventDispatcher){};
 
diff --git a/lib/bhaptics/senseshift/bh/encoding.hpp b/lib/bhaptics/senseshift/bh/encoding.hpp
index 83575f8c..e2500cd9 100644
--- a/lib/bhaptics/senseshift/bh/encoding.hpp
+++ b/lib/bhaptics/senseshift/bh/encoding.hpp
@@ -171,7 +171,7 @@ namespace SenseShift::BH {
 
             for (uint8_t i = 0; i < VEST_LAYOUT_SIZE; i++) {
                 // take only meaningful values
-                if (!OH::contains(layoutGroups, VEST_LAYOUT_SIZE, i)) {
+                if (!::SenseShift::contains(layoutGroups, VEST_LAYOUT_SIZE, i)) {
                     continue;
                 }
 
@@ -208,9 +208,11 @@ namespace SenseShift::BH {
         {
             switch (effect) {
                 case Effect_t::Vibro:
-                    return VibroEffectData_t(
-                      OH::simpleMap<VibroEffectData_t::Intensity_t>(byte, maxValue, VibroEffectData_t::INTENSITY_MAX)
-                    );
+                    return VibroEffectData_t(::SenseShift::simpleMap<VibroEffectData_t::Intensity_t>(
+                      byte,
+                      maxValue,
+                      VibroEffectData_t::INTENSITY_MAX
+                    ));
                 default:
                     throw std::runtime_error("Unknown effect");
             }
diff --git a/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp b/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
index 68de48b5..7e54500e 100644
--- a/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
+++ b/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
@@ -1,9 +1,8 @@
 #include "senseshift/bh/ble/connection.hpp"
 
-#include <senseshift/bh/constants.hpp>
-
-#include <events.hpp>
 #include <haptic_body.hpp>
+#include <senseshift/bh/constants.hpp>
+#include <senseshift/events.hpp>
 
 #include <Arduino.h>
 
@@ -30,19 +29,19 @@
 namespace SenseShift::BH::BLE {
     class BHServerCallbacks final : public BLEServerCallbacks {
       private:
-        OH::IEventDispatcher* dispatcher;
+        ::SenseShift::IEventDispatcher* dispatcher;
 
       public:
-        BHServerCallbacks(OH::IEventDispatcher* eventDispatcher) : dispatcher(eventDispatcher) {}
+        BHServerCallbacks(::SenseShift::IEventDispatcher* eventDispatcher) : dispatcher(eventDispatcher) {}
 
         void onConnect(BLEServer* pServer)
         {
-            this->dispatcher->postEvent(new OH::IEvent(OH_EVENT_CONNECTED));
+            this->dispatcher->postEvent(new ::SenseShift::IEvent(OH_EVENT_CONNECTED));
         }
 
         void onDisconnect(BLEServer* pServer)
         {
-            this->dispatcher->postEvent(new OH::IEvent(OH_EVENT_DISCONNECTED));
+            this->dispatcher->postEvent(new ::SenseShift::IEvent(OH_EVENT_DISCONNECTED));
             pServer->startAdvertising();
         }
     };
diff --git a/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp b/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
index 485e0212..90247d74 100644
--- a/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
+++ b/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
@@ -1,10 +1,9 @@
 #pragma once
 
-#include <abstract_connection.hpp>
-#include <utility.hpp>
-
 #include <senseshift/bh/ble/constants.hpp>
 #include <senseshift/bh/constants.hpp>
+#include <senseshift/events.hpp>
+#include <senseshift/utility.hpp>
 
 #include <Arduino.h>
 #include <esp_wifi.h>
@@ -37,12 +36,12 @@ namespace SenseShift::BH::BLE {
     };
     static ConnectionCallbacks defaultCallback;
 
-    class Connection final : public OH::AbstractConnection, public OH::IEventListener {
+    class Connection final : public ::SenseShift::IEventListener {
       public:
         typedef std::function<void(std::string&)> MotorHandler_t;
 
         Connection(
-          const ConnectionConfig_t& config, MotorHandler_t motorHandler, OH::IEventDispatcher* eventDispatcher
+          const ConnectionConfig_t& config, MotorHandler_t motorHandler, ::SenseShift::IEventDispatcher* eventDispatcher
         ) :
           config(config), motorHandler(motorHandler), eventDispatcher(eventDispatcher)
         {
@@ -50,12 +49,15 @@ namespace SenseShift::BH::BLE {
         };
 
         void begin(void);
-        void handleEvent(const OH::IEvent* event) const override
+        void handleEvent(const ::SenseShift::IEvent* event) const override
         {
 #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
             if (event->eventName == OH_EVENT_BATTERY_LEVEL) {
-                uint16_t level =
-                  OH::simpleMap<uint8_t>(static_cast<const OH::BatteryLevelEvent*>(event)->state.level, 255, 100);
+                uint16_t level = ::SenseShift::simpleMap<uint8_t>(
+                  static_cast<const OH::BatteryLevelEvent*>(event)->state.level,
+                  255,
+                  100
+                );
 
                 this->batteryChar->setValue(level);
                 this->batteryChar->notify();
@@ -77,7 +79,7 @@ namespace SenseShift::BH::BLE {
       private:
         const ConnectionConfig_t& config;
         MotorHandler_t motorHandler;
-        OH::IEventDispatcher* eventDispatcher;
+        ::SenseShift::IEventDispatcher* eventDispatcher;
 
         BLEServer* bleServer = nullptr;
         BLEService* motorService = nullptr;
diff --git a/lib/calibration/calibration.hpp b/lib/calibration/calibration.hpp
index 9654a1ce..fa5faa86 100644
--- a/lib/calibration/calibration.hpp
+++ b/lib/calibration/calibration.hpp
@@ -5,7 +5,7 @@
 
 #pragma once
 
-#include <utility.hpp>
+#include <senseshift/utility.hpp>
 
 namespace OH {
     struct ICalibrated {
@@ -76,7 +76,7 @@ namespace OH {
             }
 
             // Map the input range to the output range.
-            _Tp output = accurateMap<_Tp>(input, value_min, value_max, output_min, output_max);
+            _Tp output = ::SenseShift::accurateMap<_Tp>(input, value_min, value_max, output_min, output_max);
 
             // Lock the range to the output.
             return std::clamp(output, output_min, output_max);
@@ -105,9 +105,9 @@ namespace OH {
         {
             // Update the min and the max.
             if (input < range_min)
-                range_min = accurateMap<_Tp>(input, output_min, output_max, 0, sensor_max);
+                range_min = ::SenseShift::accurateMap<_Tp>(input, output_min, output_max, 0, sensor_max);
             if (input > range_max)
-                range_max = accurateMap<_Tp>(input, output_min, output_max, 0, sensor_max);
+                range_max = ::SenseShift::accurateMap<_Tp>(input, output_min, output_max, 0, sensor_max);
         }
 
         _Tp calibrate(_Tp input) const
@@ -116,13 +116,19 @@ namespace OH {
             _Tp center = (range_min + range_max) / 2.0f;
 
             // Map the input to the sensor range of motion.
-            int output = accurateMap<_Tp>(input, output_min, output_max, 0, sensor_max);
+            int output = ::SenseShift::accurateMap<_Tp>(input, output_min, output_max, 0, sensor_max);
 
             // Find the deviation from the center and clamp it to the maximum that the driver supports.
             output = std::clamp<int>(output - center, -driver_max_deviation, driver_max_deviation);
 
             // Finally map the deviation from the center back to the output range.
-            return (_Tp) accurateMap<int>(output, -driver_max_deviation, driver_max_deviation, output_min, output_max);
+            return (_Tp)::SenseShift::accurateMap<int>(
+              output,
+              -driver_max_deviation,
+              driver_max_deviation,
+              output_min,
+              output_max
+            );
         }
 
       private:
@@ -142,13 +148,19 @@ namespace OH {
             _Tp center = sensor_max / 2.0f;
 
             // Map the input to the sensor range of motion.
-            int output = accurateMap<_Tp>(input, output_min, output_max, 0, sensor_max);
+            int output = ::SenseShift::accurateMap<_Tp>(input, output_min, output_max, 0, sensor_max);
 
             // Find the deviation from the center and clamp it to the maximum that the driver supports.
             output = std::clamp<int>(output - center, -driver_max_deviation, driver_max_deviation);
 
             // Finally map the deviation from the center back to the output range.
-            return (_Tp) accurateMap<int>(output, -driver_max_deviation, driver_max_deviation, output_min, output_max);
+            return (_Tp)::SenseShift::accurateMap<int>(
+              output,
+              -driver_max_deviation,
+              driver_max_deviation,
+              output_min,
+              output_max
+            );
         }
     };
 } // namespace OH
diff --git a/lib/core/abstract_connection.hpp b/lib/core/abstract_connection.hpp
deleted file mode 100644
index 07a0523e..00000000
--- a/lib/core/abstract_connection.hpp
+++ /dev/null
@@ -1,9 +0,0 @@
-#pragma once
-
-#include "events.hpp"
-namespace OH {
-    class AbstractConnection {
-      public:
-        virtual void begin(void){};
-    };
-} // namespace OH
diff --git a/lib/core/events.hpp b/lib/core/senseshift/events.hpp
similarity index 92%
rename from lib/core/events.hpp
rename to lib/core/senseshift/events.hpp
index 0740dc6d..bf09ec22 100644
--- a/lib/core/events.hpp
+++ b/lib/core/senseshift/events.hpp
@@ -6,7 +6,7 @@
 #define OH_EVENT_CONNECTED "connected"
 #define OH_EVENT_DISCONNECTED "disconnected"
 
-namespace OH {
+namespace SenseShift {
     enum Event {
 
     };
@@ -27,4 +27,4 @@ namespace OH {
         virtual void postEvent(const IEvent* event) = 0;
         virtual void addEventListener(const IEventListener* handler) = 0;
     };
-} // namespace OH
+} // namespace SenseShift
diff --git a/lib/core/logging.hpp b/lib/core/senseshift/logging.hpp
similarity index 99%
rename from lib/core/logging.hpp
rename to lib/core/senseshift/logging.hpp
index 8af17aa8..d3cbc94b 100644
--- a/lib/core/logging.hpp
+++ b/lib/core/senseshift/logging.hpp
@@ -1,6 +1,7 @@
 #pragma once
 
 #if defined(ESP32)
+
 #include <esp32-hal-log.h>
 #elif defined(UNITY_INCLUDE_PRINT_FORMATTED)
 #define log_e(...) TEST_PRINTF(__VA_ARGS__)
@@ -8,10 +9,13 @@
 #define log_i(...) TEST_PRINTF(__VA_ARGS__)
 #define log_d(...) TEST_PRINTF(__VA_ARGS__)
 #define log_t(...) TEST_PRINTF(__VA_ARGS__)
+
 #else
+
 #define log_e(...)
 #define log_w(...)
 #define log_i(...)
 #define log_d(...)
 #define log_t(...)
+
 #endif
diff --git a/lib/core/utility.hpp b/lib/core/senseshift/utility.hpp
similarity index 94%
rename from lib/core/utility.hpp
rename to lib/core/senseshift/utility.hpp
index 3d40497e..227748f4 100644
--- a/lib/core/utility.hpp
+++ b/lib/core/senseshift/utility.hpp
@@ -2,9 +2,9 @@
 
 #include <algorithm>
 #include <iterator>
-#include <logging.hpp>
+#include <senseshift/logging.hpp>
 
-namespace OH {
+namespace SenseShift {
     /**
      * Checks if a container contains a value.
      *
@@ -48,4 +48,4 @@ namespace OH {
     {
         return x * out_max / in_max;
     }
-} // namespace OH
+} // namespace SenseShift
diff --git a/lib/freertos/task.hpp b/lib/freertos/task.hpp
index e31fb0d0..6a40f33a 100644
--- a/lib/freertos/task.hpp
+++ b/lib/freertos/task.hpp
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "logging.hpp"
+#include <senseshift/logging.hpp>
 
 extern "C" void delay(uint32_t ms);
 
diff --git a/lib/haptics/haptic_body.cpp b/lib/haptics/haptic_body.cpp
index fd4cf2cb..ddde0702 100644
--- a/lib/haptics/haptic_body.cpp
+++ b/lib/haptics/haptic_body.cpp
@@ -1,6 +1,6 @@
 #include "haptic_body.hpp"
 
-#include <logging.hpp>
+#include <senseshift/logging.hpp>
 
 namespace SenseShift::Body::Haptics {
     void HapticBody::setup()
diff --git a/lib/haptics/haptic_body.hpp b/lib/haptics/haptic_body.hpp
index 351e31eb..ff05a1e4 100644
--- a/lib/haptics/haptic_body.hpp
+++ b/lib/haptics/haptic_body.hpp
@@ -3,7 +3,7 @@
 #include "haptic_plane.hpp"
 #include "haptics_interface.hpp"
 
-#include <utility.hpp>
+#include <senseshift/utility.hpp>
 
 #include <map>
 
diff --git a/lib/haptics/haptic_plane.cpp b/lib/haptics/haptic_plane.cpp
index d22c6f55..47af5ae6 100644
--- a/lib/haptics/haptic_plane.cpp
+++ b/lib/haptics/haptic_plane.cpp
@@ -1,8 +1,8 @@
 #include "haptic_plane.hpp"
 
 #include <algorithm>
-#include <logging.hpp>
-#include <math.h>
+#include <cmath>
+#include <senseshift/logging.hpp>
 
 #include "haptic_plane.hpp"
 
diff --git a/lib/haptics/haptic_plane.hpp b/lib/haptics/haptic_plane.hpp
index 97aac1c9..88227f4f 100644
--- a/lib/haptics/haptic_plane.hpp
+++ b/lib/haptics/haptic_plane.hpp
@@ -3,8 +3,7 @@
 #include "haptics_interface.hpp"
 
 #include <senseshift/output/actuator.hpp>
-
-#include <utility.hpp>
+#include <senseshift/utility.hpp>
 
 #include <list>
 #include <map>
@@ -120,8 +119,8 @@ namespace SenseShift::Body::Haptics {
         {
             using Point_t = OH::Point2<_Tp>;
             return Point_t(
-              OH::accurateMap<_Tp>(x + 1, 0, x_max + 2, Point_t::MIN, Point_t::MAX),
-              OH::accurateMap<_Tp>(y + 1, 0, y_max + 2, Point_t::MIN, Point_t::MAX)
+              ::SenseShift::accurateMap<_Tp>(x + 1, 0, x_max + 2, Point_t::MIN, Point_t::MAX),
+              ::SenseShift::accurateMap<_Tp>(y + 1, 0, y_max + 2, Point_t::MIN, Point_t::MAX)
             );
         }
     };
diff --git a/lib/opengloves/og_alpha_encoding.hpp b/lib/opengloves/og_alpha_encoding.hpp
index acc3744f..8ca21f07 100644
--- a/lib/opengloves/og_alpha_encoding.hpp
+++ b/lib/opengloves/og_alpha_encoding.hpp
@@ -1,18 +1,19 @@
 #pragma once
 
+#include <cstdint>
 #include <functional>
 #include <map>
-#include <stdint.h>
 #include <string>
 #include <vector>
 
-#include <logging.hpp>
 #include <og_protocol.hpp>
+#include <senseshift/logging.hpp>
 
 namespace OpenGloves {
     class AlphaEncodingService {
       public:
         inline static const std::string valueSymbols = "0123456789";
+        // TODO: use Frozen library to save memory
         inline static const std::map<std::string, Command> commandMap = {
             // clang-format off
             { "A", Command::ThumbCurl },
diff --git a/lib/opengloves_task/opengloves_task.hpp b/lib/opengloves_task/opengloves_task.hpp
index d52f5cd4..89eb9805 100644
--- a/lib/opengloves_task/opengloves_task.hpp
+++ b/lib/opengloves_task/opengloves_task.hpp
@@ -8,6 +8,7 @@
 #include <og_alpha_encoding.hpp>
 #include <og_ffb.hpp>
 #include <og_serial_communication.hpp>
+#include <senseshift/utility.hpp>
 #include <sensor.hpp>
 #include <sensor/analog.hpp>
 #include <sensor/digital.hpp>
@@ -15,7 +16,6 @@
 #include <sensor/og_finger.hpp>
 #include <sensor/og_gesture.hpp>
 #include <task.hpp>
-#include <utility.hpp>
 
 namespace OpenGloves {
     struct OpenGlovesTrackingTaskConfig {
diff --git a/lib/pca9685/senseshift/arduino/output/pca9685.hpp b/lib/pca9685/senseshift/arduino/output/pca9685.hpp
index a6529521..aa8b4ce2 100644
--- a/lib/pca9685/senseshift/arduino/output/pca9685.hpp
+++ b/lib/pca9685/senseshift/arduino/output/pca9685.hpp
@@ -1,7 +1,7 @@
 #pragma once
 
 #include <senseshift/output/actuator.hpp>
-#include <utility.hpp>
+#include <senseshift/utility.hpp>
 
 #include <Adafruit_PWMServoDriver.h>
 #include <Arduino.h>
@@ -16,7 +16,7 @@ namespace SenseShift::Arduino::Output {
 
         void writeOutput(std::uint16_t intensity) override
         {
-            this->driver->setPin(this->num, OH::simpleMap<std::uint16_t>(intensity, MAX_INTENSITY, 4095));
+            this->driver->setPin(this->num, ::SenseShift::simpleMap<std::uint16_t>(intensity, MAX_INTENSITY, 4095));
         }
 
       private:
diff --git a/lib/sensor/sensor.hpp b/lib/sensor/sensor.hpp
index 714a0a09..8f95ef5a 100644
--- a/lib/sensor/sensor.hpp
+++ b/lib/sensor/sensor.hpp
@@ -3,7 +3,7 @@
 #include <type_traits>
 
 #include <calibration.hpp>
-#include <logging.hpp>
+#include <senseshift/logging.hpp>
 
 #if defined(__AVR__)
 #define ANALOG_MAX 1023
diff --git a/test/test_core_utility/main.cpp b/test/test_core_utility/main.cpp
index e4539512..a468a49a 100644
--- a/test/test_core_utility/main.cpp
+++ b/test/test_core_utility/main.cpp
@@ -1,7 +1,7 @@
+#include <senseshift/utility.hpp>
 #include <unity.h>
-#include <utility.hpp>
 
-using namespace OH;
+using namespace SenseShift;
 
 void test_contains_container(void)
 {

From 169f50e0c6430ea988c572237dcfe815da3fa049 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Mon, 21 Aug 2023 23:49:54 +0400
Subject: [PATCH 10/82] refactor(Input): move to `SenseShift` namespace and
 directory

---
 .../mode_configs/opengloves/opengloves.cpp    | 49 ++++++++++++-------
 .../arduino/input}/sensor/analog.hpp          |  8 +--
 .../arduino/input}/sensor/digital.hpp         |  9 ++--
 lib/battery/abstract_battery.cpp              |  1 -
 lib/battery/abstract_battery.hpp              | 10 ++--
 lib/opengloves/sensor/og_finger.hpp           | 26 +++++-----
 lib/opengloves/sensor/og_gesture.hpp          |  2 +-
 lib/opengloves/sensor/og_sensor.hpp           | 10 ++--
 lib/opengloves_task/opengloves_task.hpp       |  8 +--
 lib/sensor/{ => senseshift/input}/sensor.hpp  | 11 +++--
 .../senseshift/input}/sensor/joystick.hpp     |  8 ++-
 test/test_opengloves/main.cpp                 |  6 +--
 test/test_opengloves_finger/main.cpp          | 10 ++--
 test/test_sensor/main.cpp                     |  3 +-
 14 files changed, 90 insertions(+), 71 deletions(-)
 rename lib/arduino/{ => senseshift/arduino/input}/sensor/analog.hpp (73%)
 rename lib/arduino/{ => senseshift/arduino/input}/sensor/digital.hpp (76%)
 delete mode 100644 lib/battery/abstract_battery.cpp
 rename lib/sensor/{ => senseshift/input}/sensor.hpp (90%)
 rename lib/{arduino => sensor/senseshift/input}/sensor/joystick.hpp (90%)

diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp
index 6fadafdc..2ac3d7d0 100644
--- a/firmware/mode_configs/opengloves/opengloves.cpp
+++ b/firmware/mode_configs/opengloves/opengloves.cpp
@@ -2,12 +2,12 @@
 #include <og_constants.hpp>
 #include <og_serial_communication.hpp>
 #include <opengloves_task.hpp>
+#include <senseshift/arduino/input/sensor/analog.hpp>
+#include <senseshift/arduino/input/sensor/digital.hpp>
 #include <senseshift/arduino/output/servo.hpp>
+#include <senseshift/input/sensor.hpp>
+#include <senseshift/input/sensor/joystick.hpp>
 #include <senseshift/utility.hpp>
-#include <sensor.hpp>
-#include <sensor/analog.hpp>
-#include <sensor/digital.hpp>
-#include <sensor/joystick.hpp>
 #include <sensor/og_finger.hpp>
 #include <sensor/og_gesture.hpp>
 
@@ -53,10 +53,13 @@
 #define FINGER_MIDDLE_ENABLED (defined(PIN_FINGER_MIDDLE) && (PIN_FINGER_MIDDLE != -1))
 #define FINGER_RING_ENABLED (defined(PIN_FINGER_RING) && (PIN_FINGER_RING != -1))
 #define FINGER_PINKY_ENABLED (defined(PIN_FINGER_PINKY) && (PIN_FINGER_PINKY != -1))
-#define FINGER_CLASS(type, curl_pin, curl_invert, curl_calib)                                            \
-    FingerSensor(                                                                                        \
-      new OH::CalibratedSensor<uint16_t>(new OH::AnalogSensor<curl_invert>(curl_pin), new curl_calib()), \
-      type                                                                                               \
+#define FINGER_CLASS(type, curl_pin, curl_invert, curl_calib)                  \
+    FingerSensor(                                                              \
+      new ::SenseShift::Input::CalibratedSensor<uint16_t>(                     \
+        new ::SenseShift::Arduino::Input::AnalogSensor<curl_invert>(curl_pin), \
+        new curl_calib()                                                       \
+      ),                                                                       \
+      type                                                                     \
     )
 #define FINGER_THUMB_SPLAY (FINGER_THUMB_ENABLED && defined(PIN_FINGER_THUMB_SPLAY) && (PIN_FINGER_THUMB_SPLAY != -1))
 #define FINGER_INDEX_SPLAY (FINGER_INDEX_ENABLED && defined(PIN_FINGER_INDEX_SPLAY) && (PIN_FINGER_INDEX_SPLAY != -1))
@@ -64,11 +67,17 @@
     (FINGER_MIDDLE_ENABLED && defined(PIN_FINGER_MIDDLE_SPLAY) && (PIN_FINGER_MIDDLE_SPLAY != -1))
 #define FINGER_RING_SPLAY (FINGER_RING_ENABLED && defined(PIN_FINGER_RING_SPLAY) && (PIN_FINGER_RING_SPLAY != -1))
 #define FINGER_PINKY_SPLAY (FINGER_PINKY_ENABLED && defined(PIN_FINGER_PINKY_SPLAY) && (PIN_FINGER_PINKY_SPLAY != -1))
-#define FINGER_SPLAY_CLASS(type, curl_pin, curl_invert, curl_calib, splay_pin, splay_invert, splay_calib)   \
-    FingerSensor(                                                                                           \
-      new OH::CalibratedSensor<uint16_t>(new OH::AnalogSensor<curl_invert>(curl_pin), new curl_calib()),    \
-      new OH::CalibratedSensor<uint16_t>(new OH::AnalogSensor<splay_invert>(splay_pin), new splay_calib()), \
-      type                                                                                                  \
+#define FINGER_SPLAY_CLASS(type, curl_pin, curl_invert, curl_calib, splay_pin, splay_invert, splay_calib) \
+    FingerSensor(                                                                                         \
+      new ::SenseShift::Input::CalibratedSensor<uint16_t>(                                                \
+        new ::SenseShift::Arduino::Input::AnalogSensor<curl_invert>(curl_pin),                            \
+        new curl_calib()                                                                                  \
+      ),                                                                                                  \
+      new ::SenseShift::Input::CalibratedSensor<uint16_t>(                                                \
+        new ::SenseShift::Arduino::Input::AnalogSensor<splay_invert>(splay_pin),                          \
+        new splay_calib()                                                                                 \
+      ),                                                                                                  \
+      type                                                                                                \
     )
 
 #pragma endregion
@@ -78,10 +87,13 @@
 #define JOYSTICK_ENABLED \
     (defined(PIN_JOYSTICK_X) && defined(PIN_JOYSTICK_Y) && (PIN_JOYSTICK_X != -1) && (PIN_JOYSTICK_Y != -1))
 
-#define JOYSTICK_CLASS(type, pin, invert, deadzone)                                      \
-    StringEncodedMemoizedSensor<uint16_t>(                                               \
-      new OH::JoystickAxisSensor<uint16_t>(new OH::AnalogSensor<invert>(pin), deadzone), \
-      type                                                                               \
+#define JOYSTICK_CLASS(type, pin, invert, deadzone)                  \
+    StringEncodedMemoizedSensor<uint16_t>(                           \
+      new ::SenseShift::Input::JoystickAxisSensor<uint16_t>(         \
+        new ::SenseShift::Arduino::Input::AnalogSensor<invert>(pin), \
+        deadzone                                                     \
+      ),                                                             \
+      type                                                           \
     )
 
 #pragma endregion
@@ -97,7 +109,8 @@
 #define BUTTON_GRAB_ENABLED (!GESTURE_GRAB_ENABLED && defined(PIN_BUTTON_GRAB) && (PIN_BUTTON_GRAB != -1))
 #define BUTTON_PINCH_ENABLED (!GESTURE_PINCH_ENABLED && defined(PIN_BUTTON_PINCH) && (PIN_BUTTON_PINCH != -1))
 
-#define BUTTON_CLASS(type, pin, invert) StringEncodedMemoizedSensor<bool>(new OH::DigitalSensor<invert>(pin), type)
+#define BUTTON_CLASS(type, pin, invert) \
+    StringEncodedMemoizedSensor<bool>(new ::SenseShift::Arduino::Input::DigitalSensor<invert>(pin), type)
 
 #pragma endregion
 
diff --git a/lib/arduino/sensor/analog.hpp b/lib/arduino/senseshift/arduino/input/sensor/analog.hpp
similarity index 73%
rename from lib/arduino/sensor/analog.hpp
rename to lib/arduino/senseshift/arduino/input/sensor/analog.hpp
index 9f8a33a7..d55fc37e 100644
--- a/lib/arduino/sensor/analog.hpp
+++ b/lib/arduino/senseshift/arduino/input/sensor/analog.hpp
@@ -1,12 +1,12 @@
 #pragma once
 
-#include <sensor.hpp>
+#include <senseshift/input/sensor.hpp>
 
 #include <Arduino.h>
 
-namespace OH {
+namespace SenseShift::Arduino::Input {
     template<bool invert = false>
-    class AnalogSensor : public ISensor<uint16_t> {
+    class AnalogSensor : public ::SenseShift::Input::ISensor<uint16_t> {
       private:
         uint8_t pin;
 
@@ -32,4 +32,4 @@ namespace OH {
     {
         return ANALOG_MAX - analogRead(this->pin);
     }
-} // namespace OH
+} // namespace SenseShift::Arduino::Input
diff --git a/lib/arduino/sensor/digital.hpp b/lib/arduino/senseshift/arduino/input/sensor/digital.hpp
similarity index 76%
rename from lib/arduino/sensor/digital.hpp
rename to lib/arduino/senseshift/arduino/input/sensor/digital.hpp
index 90b517c1..5a9c2f63 100644
--- a/lib/arduino/sensor/digital.hpp
+++ b/lib/arduino/senseshift/arduino/input/sensor/digital.hpp
@@ -1,10 +1,11 @@
 #pragma once
 
+#include <senseshift/input/sensor.hpp>
+
 #include <Arduino.h>
-#include <sensor.hpp>
 
-namespace OH {
-    typedef ISensor<bool> IDigitalSensor;
+namespace SenseShift::Arduino::Input {
+    typedef ::SenseShift::Input::ISensor<bool> IDigitalSensor;
 
     template<bool invert = false>
     class DigitalSensor : public IDigitalSensor {
@@ -33,4 +34,4 @@ namespace OH {
     {
         return digitalRead(this->pin) == HIGH;
     }
-} // namespace OH
+} // namespace SenseShift::Arduino::Input
diff --git a/lib/battery/abstract_battery.cpp b/lib/battery/abstract_battery.cpp
deleted file mode 100644
index 326b3507..00000000
--- a/lib/battery/abstract_battery.cpp
+++ /dev/null
@@ -1 +0,0 @@
-#include "abstract_battery.hpp"
diff --git a/lib/battery/abstract_battery.hpp b/lib/battery/abstract_battery.hpp
index feb3cdb1..0e64c2ad 100644
--- a/lib/battery/abstract_battery.hpp
+++ b/lib/battery/abstract_battery.hpp
@@ -1,7 +1,7 @@
 #pragma once
 
 #include <senseshift/events.hpp>
-#include <sensor.hpp>
+#include <senseshift/input/sensor.hpp>
 #include <task.hpp>
 
 #include <stdint.h>
@@ -15,7 +15,7 @@ namespace OH {
      * Tasked sensor decorator
      */
     template<typename _Tp>
-    class TaskedSensor : public Task<TaskedSensor<_Tp>>, public MemoizedSensor<_Tp> {
+    class TaskedSensor : public Task<TaskedSensor<_Tp>>, public ::SenseShift::Input::MemoizedSensor<_Tp> {
         friend class Task<TaskedSensor<_Tp>>;
 
       private:
@@ -31,8 +31,8 @@ namespace OH {
         uint32_t rate;
 
       public:
-        TaskedSensor(ISensor<_Tp>* sensor, TaskConfig taskConfig, uint32_t rate) :
-          MemoizedSensor<_Tp>(sensor), Task<TaskedSensor<_Tp>>(taskConfig), rate(rate){};
+        TaskedSensor(::SenseShift::Input::ISensor<_Tp>* sensor, TaskConfig taskConfig, uint32_t rate) :
+          ::SenseShift::Input::MemoizedSensor<_Tp>(sensor), Task<TaskedSensor<_Tp>>(taskConfig), rate(rate){};
 
         void begin() override
         {
@@ -58,7 +58,7 @@ namespace OH {
     /**
      * Abstract battery sensor
      */
-    typedef ISensor<BatteryState> IBatterySensor;
+    typedef ::SenseShift::Input::ISensor<BatteryState> IBatterySensor;
 
     class BatterySensor : public TaskedSensor<BatteryState> {
         friend class Task<TaskedSensor<BatteryState>>;
diff --git a/lib/opengloves/sensor/og_finger.hpp b/lib/opengloves/sensor/og_finger.hpp
index 1b335718..1b5de1a9 100644
--- a/lib/opengloves/sensor/og_finger.hpp
+++ b/lib/opengloves/sensor/og_finger.hpp
@@ -6,17 +6,19 @@
 
 namespace OpenGloves {
     struct FingerSensors {
-        std::vector<OH::CalibratedSensor<uint16_t>*> curl = std::vector<OH::CalibratedSensor<uint16_t>*>();
-        std::optional<OH::CalibratedSensor<uint16_t>*> splay = std::nullopt;
+        std::vector<SenseShift::Input::CalibratedSensor<uint16_t>*> curl =
+          std::vector<SenseShift::Input::CalibratedSensor<uint16_t>*>();
+        std::optional<SenseShift::Input::CalibratedSensor<uint16_t>*> splay = std::nullopt;
 
         FingerSensors(
-          std::vector<OH::CalibratedSensor<uint16_t>*> curl,
-          std::optional<OH::CalibratedSensor<uint16_t>*> splay = std::nullopt
+          std::vector<SenseShift::Input::CalibratedSensor<uint16_t>*> curl,
+          std::optional<SenseShift::Input::CalibratedSensor<uint16_t>*> splay = std::nullopt
         ) :
           curl(curl), splay(splay){};
 
         FingerSensors(
-          OH::CalibratedSensor<uint16_t>* curl1, std::optional<OH::CalibratedSensor<uint16_t>*> splay = std::nullopt
+          SenseShift::Input::CalibratedSensor<uint16_t>* curl1,
+          std::optional<SenseShift::Input::CalibratedSensor<uint16_t>*> splay = std::nullopt
         ) :
           curl({ curl1 }), splay(splay){};
     };
@@ -29,14 +31,15 @@ namespace OpenGloves {
         virtual uint16_t getCurl() = 0;
     };
 
-    typedef OH::ISensor<FingerValue> IFingerSensor;
+    typedef SenseShift::Input::ISensor<FingerValue> IFingerSensor;
 
     class SimpleFingerSensor : public IFingerSensor, public ICurl {
       public:
         SimpleFingerSensor(FingerSensors sensors) : sensors(sensors){};
 
         SimpleFingerSensor(
-          OH::CalibratedSensor<uint16_t>* curl1, std::optional<OH::CalibratedSensor<uint16_t>*> splay = std::nullopt
+          SenseShift::Input::CalibratedSensor<uint16_t>* curl1,
+          std::optional<SenseShift::Input::CalibratedSensor<uint16_t>*> splay = std::nullopt
         ) :
           sensors(curl1, splay){};
 
@@ -82,7 +85,8 @@ namespace OpenGloves {
         CalibratedFingerSensor(FingerSensors sensors) : SimpleFingerSensor(sensors){};
 
         CalibratedFingerSensor(
-          OH::CalibratedSensor<uint16_t>* curl1, std::optional<OH::CalibratedSensor<uint16_t>*> splay = std::nullopt
+          SenseShift::Input::CalibratedSensor<uint16_t>* curl1,
+          std::optional<SenseShift::Input::CalibratedSensor<uint16_t>*> splay = std::nullopt
         ) :
           SimpleFingerSensor(curl1, splay){};
 
@@ -123,13 +127,13 @@ namespace OpenGloves {
           StringEncodedMemoizedSensor<FingerValue>(sensor, type){};
 
         FingerSensor(
-          OH::CalibratedSensor<uint16_t>* curl1,
-          std::optional<OH::CalibratedSensor<uint16_t>*> splay,
+          SenseShift::Input::CalibratedSensor<uint16_t>* curl1,
+          std::optional<SenseShift::Input::CalibratedSensor<uint16_t>*> splay,
           IEncodedInput::Type type
         ) :
           StringEncodedMemoizedSensor<FingerValue>(new CalibratedFingerSensor(curl1, splay), type){};
 
-        FingerSensor(OH::CalibratedSensor<uint16_t>* curl1, IEncodedInput::Type type) :
+        FingerSensor(SenseShift::Input::CalibratedSensor<uint16_t>* curl1, IEncodedInput::Type type) :
           StringEncodedMemoizedSensor<FingerValue>(new CalibratedFingerSensor(curl1, std::nullopt), type){};
 
         void resetCalibration() override
diff --git a/lib/opengloves/sensor/og_gesture.hpp b/lib/opengloves/sensor/og_gesture.hpp
index 9a35aa8d..3c7e0944 100644
--- a/lib/opengloves/sensor/og_gesture.hpp
+++ b/lib/opengloves/sensor/og_gesture.hpp
@@ -3,7 +3,7 @@
 #include <sensor/og_finger.hpp>
 
 namespace OpenGloves {
-    class Gesture : public OH::ISensor<bool> {};
+    class Gesture : public SenseShift::Input::ISensor<bool> {};
 
     class GrabGesture : public Gesture {
       private:
diff --git a/lib/opengloves/sensor/og_sensor.hpp b/lib/opengloves/sensor/og_sensor.hpp
index 1ab097c9..60e099d7 100644
--- a/lib/opengloves/sensor/og_sensor.hpp
+++ b/lib/opengloves/sensor/og_sensor.hpp
@@ -2,7 +2,7 @@
 
 #include <calibration.hpp>
 #include <og_protocol.hpp>
-#include <sensor.hpp>
+#include <senseshift/input/sensor.hpp>
 
 #include <optional>
 #include <vector>
@@ -27,10 +27,12 @@ namespace OpenGloves {
     };
 
     template<typename _Tp>
-    class StringEncodedMemoizedSensor : public IStringEncodedMemoizedSensor, public OH::MemoizedSensor<_Tp> {
+    class StringEncodedMemoizedSensor :
+      public IStringEncodedMemoizedSensor,
+      public SenseShift::Input::MemoizedSensor<_Tp> {
       public:
-        StringEncodedMemoizedSensor(OH::ISensor<_Tp>* sensor, IEncodedInput::Type type) :
-          IStringEncodedMemoizedSensor(type), OH::MemoizedSensor<_Tp>(sensor){};
+        StringEncodedMemoizedSensor(SenseShift::Input::ISensor<_Tp>* sensor, IEncodedInput::Type type) :
+          IStringEncodedMemoizedSensor(type), SenseShift::Input::MemoizedSensor<_Tp>(sensor){};
 
         void setup() override
         {
diff --git a/lib/opengloves_task/opengloves_task.hpp b/lib/opengloves_task/opengloves_task.hpp
index 89eb9805..a5dd8b3b 100644
--- a/lib/opengloves_task/opengloves_task.hpp
+++ b/lib/opengloves_task/opengloves_task.hpp
@@ -8,11 +8,11 @@
 #include <og_alpha_encoding.hpp>
 #include <og_ffb.hpp>
 #include <og_serial_communication.hpp>
+#include <senseshift/arduino/input/sensor/analog.hpp>
+#include <senseshift/arduino/input/sensor/digital.hpp>
+#include <senseshift/input/sensor.hpp>
+#include <senseshift/input/sensor/joystick.hpp>
 #include <senseshift/utility.hpp>
-#include <sensor.hpp>
-#include <sensor/analog.hpp>
-#include <sensor/digital.hpp>
-#include <sensor/joystick.hpp>
 #include <sensor/og_finger.hpp>
 #include <sensor/og_gesture.hpp>
 #include <task.hpp>
diff --git a/lib/sensor/sensor.hpp b/lib/sensor/senseshift/input/sensor.hpp
similarity index 90%
rename from lib/sensor/sensor.hpp
rename to lib/sensor/senseshift/input/sensor.hpp
index 8f95ef5a..1b08ec60 100644
--- a/lib/sensor/sensor.hpp
+++ b/lib/sensor/senseshift/input/sensor.hpp
@@ -16,7 +16,7 @@
 // #define ANALOG_MAX 4095
 #endif
 
-namespace OH {
+namespace SenseShift::Input {
     /**
      * Abstract hardware sensor (e.g. potentiometer, flex sensor, etc.)
      * @tparam _Tp Type of the sensor value
@@ -90,10 +90,10 @@ namespace OH {
      * @tparam _Tp Type of the sensor value
      */
     template<typename _Tp>
-    class CalibratedSensor : public ISensor<_Tp>, public Calibrated {
+    class CalibratedSensor : public ISensor<_Tp>, public OH::Calibrated {
       protected:
         ISensor<_Tp>* sensor;
-        ICalibrator<_Tp>* calibrator;
+        OH::ICalibrator<_Tp>* calibrator;
 
         _Tp getCalibratedValue()
         {
@@ -111,7 +111,8 @@ namespace OH {
          * @param sensor Sensor to be decorated
          * @param calibrator ICalibrator algorithm to be used
          */
-        CalibratedSensor(ISensor<_Tp>* sensor, ICalibrator<_Tp>* calibrator) : sensor(sensor), calibrator(calibrator){};
+        CalibratedSensor(ISensor<_Tp>* sensor, OH::ICalibrator<_Tp>* calibrator) :
+          sensor(sensor), calibrator(calibrator){};
 
         void setup() override
         {
@@ -128,4 +129,4 @@ namespace OH {
             this->calibrator->reset();
         };
     };
-} // namespace OH
+} // namespace SenseShift::Input
diff --git a/lib/arduino/sensor/joystick.hpp b/lib/sensor/senseshift/input/sensor/joystick.hpp
similarity index 90%
rename from lib/arduino/sensor/joystick.hpp
rename to lib/sensor/senseshift/input/sensor/joystick.hpp
index 858f36cd..307d49ea 100644
--- a/lib/arduino/sensor/joystick.hpp
+++ b/lib/sensor/senseshift/input/sensor/joystick.hpp
@@ -1,10 +1,8 @@
 #pragma once
 
-#include <sensor.hpp>
+#include "senseshift/input/sensor.hpp"
 
-#include <Arduino.h>
-
-namespace OH {
+namespace SenseShift::Input {
     /**
      * Joystick axis sensor decorator
      */
@@ -38,4 +36,4 @@ namespace OH {
             return value;
         }
     };
-}; // namespace OH
+}; // namespace SenseShift::Input
diff --git a/test/test_opengloves/main.cpp b/test/test_opengloves/main.cpp
index 704515b8..646a9785 100644
--- a/test/test_opengloves/main.cpp
+++ b/test/test_opengloves/main.cpp
@@ -3,7 +3,7 @@
 
 using namespace OpenGloves;
 
-class TestAnalogSensor : public OH::ISensor<uint16_t> {
+class TestAnalogSensor : public SenseShift::Input::ISensor<uint16_t> {
   private:
     uint16_t count = 0;
 
@@ -21,7 +21,7 @@ class TestAnalogSensor : public OH::ISensor<uint16_t> {
     };
 };
 
-class TestBinarySensor : public OH::ISensor<bool> {
+class TestBinarySensor : public SenseShift::Input::ISensor<bool> {
   public:
     bool value = false;
     int setupCounter = 0;
@@ -37,7 +37,7 @@ class TestBinarySensor : public OH::ISensor<bool> {
     };
 };
 
-class TestFingerSensor : public OH::ISensor<FingerValue> {
+class TestFingerSensor : public SenseShift::Input::ISensor<FingerValue> {
   public:
     FingerValue value;
     int setupCounter = 0;
diff --git a/test/test_opengloves_finger/main.cpp b/test/test_opengloves_finger/main.cpp
index 4dd5a74e..0a3a9978 100644
--- a/test/test_opengloves_finger/main.cpp
+++ b/test/test_opengloves_finger/main.cpp
@@ -3,7 +3,7 @@
 
 using namespace OpenGloves;
 
-class TestAnalogSensor : public OH::ISensor<uint16_t> {
+class TestAnalogSensor : public SenseShift::Input::ISensor<uint16_t> {
   private:
     uint16_t count = 0;
 
@@ -45,7 +45,7 @@ void test_simple_finger_sensor_curl(void)
 {
     auto* inner = new TestAnalogSensor();
     auto* calibrator = new DummyCalibrator();
-    auto* calibrated = new OH::CalibratedSensor<uint16_t>(inner, calibrator);
+    auto* calibrated = new SenseShift::Input::CalibratedSensor<uint16_t>(inner, calibrator);
     auto* sensor = new SimpleFingerSensor(calibrated);
 
     TEST_ASSERT_EQUAL_INT(0, inner->setupCounter);
@@ -71,11 +71,11 @@ void test_simple_finger_sensor_curl_flex(void)
 {
     auto* inner_curl = new TestAnalogSensor();
     auto* calibrator_curl = new DummyCalibrator();
-    auto* calibrated_curl = new OH::CalibratedSensor<uint16_t>(inner_curl, calibrator_curl);
+    auto* calibrated_curl = new SenseShift::Input::CalibratedSensor<uint16_t>(inner_curl, calibrator_curl);
 
     auto* inner_flex = new TestAnalogSensor();
     auto* calibrator_flex = new DummyCalibrator();
-    auto* calibrated_flex = new OH::CalibratedSensor<uint16_t>(inner_flex, calibrator_flex);
+    auto* calibrated_flex = new SenseShift::Input::CalibratedSensor<uint16_t>(inner_flex, calibrator_flex);
 
     auto* sensor = new SimpleFingerSensor(calibrated_curl, calibrated_flex);
 
@@ -111,7 +111,7 @@ void test_finger_sensor_curl(void)
 {
     auto* inner = new TestAnalogSensor();
     auto* calibrator = new DummyCalibrator();
-    auto* calibrated = new OH::CalibratedSensor<uint16_t>(inner, calibrator);
+    auto* calibrated = new SenseShift::Input::CalibratedSensor<uint16_t>(inner, calibrator);
     auto* sensor = new FingerSensor(calibrated, IEncodedInput::Type::INDEX);
 
     TEST_ASSERT_EQUAL_INT(0, inner->setupCounter);
diff --git a/test/test_sensor/main.cpp b/test/test_sensor/main.cpp
index d026d2a7..7309e65c 100644
--- a/test/test_sensor/main.cpp
+++ b/test/test_sensor/main.cpp
@@ -1,6 +1,7 @@
-#include <sensor.hpp>
+#include <senseshift/input/sensor.hpp>
 #include <unity.h>
 
+using namespace SenseShift::Input;
 using namespace OH;
 
 class TestAnalogSensor : public ISensor<int> {

From b9c791ec12e7463f7a2c75d4364f806a7eb6c89e Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Tue, 22 Aug 2023 01:20:57 +0400
Subject: [PATCH 11/82] refactor(Battery): use `SenseShift` lib namespace and
 dir

---
 .github/scripts/get_firmware_name.sh          |  2 +-
 .github/workflows/ci.yml                      |  8 +-
 .github/workflows/codeql-analysis.yml         |  2 +-
 .github/workflows/release.yml                 |  2 +-
 firmware/mode_configs/bhaptics/tactal.cpp     | 16 ++--
 firmware/mode_configs/bhaptics/tactglove.cpp  | 16 ++--
 firmware/mode_configs/bhaptics/tactosy2.cpp   | 16 ++--
 firmware/mode_configs/bhaptics/tactosyf.cpp   | 16 ++--
 firmware/mode_configs/bhaptics/tactosyh.cpp   | 16 ++--
 .../mode_configs/bhaptics/tactsuit_x16.cpp    | 16 ++--
 .../bhaptics/tactsuit_x16_pca9685.cpp         | 16 ++--
 .../mode_configs/bhaptics/tactsuit_x40.cpp    | 16 ++--
 firmware/mode_configs/bhaptics/tactvisor.cpp  | 16 ++--
 include/config/battery.h                      | 16 ++--
 include/senseshift.h                          | 10 +-
 lib/arduino/battery/adc_naive.cpp             | 16 ----
 lib/arduino/battery/adc_naive.hpp             | 16 ----
 lib/battery/senseshift/battery.hpp            | 17 ++++
 .../battery/sensor.hpp}                       | 92 ++++++++++---------
 .../senseshift/bh/ble/connection.cpp          |  4 -
 .../senseshift/bh/ble/connection.hpp          | 23 ++---
 lib/ina219/battery/ina219.cpp                 | 25 -----
 lib/ina219/battery/ina219.hpp                 | 19 ----
 .../senseshift/arduino/battery/ina219.hpp     | 35 +++++++
 lib/max17048/battery/max17048.cpp             | 32 -------
 lib/max17048/battery/max17048.hpp             | 20 ----
 .../senseshift/arduino/battery/max17048.hpp   | 45 +++++++++
 platformio.ini                                |  2 +-
 28 files changed, 243 insertions(+), 287 deletions(-)
 delete mode 100644 lib/arduino/battery/adc_naive.cpp
 delete mode 100644 lib/arduino/battery/adc_naive.hpp
 create mode 100644 lib/battery/senseshift/battery.hpp
 rename lib/battery/{abstract_battery.hpp => senseshift/battery/sensor.hpp} (50%)
 delete mode 100644 lib/ina219/battery/ina219.cpp
 delete mode 100644 lib/ina219/battery/ina219.hpp
 create mode 100644 lib/ina219/senseshift/arduino/battery/ina219.hpp
 delete mode 100644 lib/max17048/battery/max17048.cpp
 delete mode 100644 lib/max17048/battery/max17048.hpp
 create mode 100644 lib/max17048/senseshift/arduino/battery/max17048.hpp

diff --git a/.github/scripts/get_firmware_name.sh b/.github/scripts/get_firmware_name.sh
index 199e38aa..cd7a2b6d 100755
--- a/.github/scripts/get_firmware_name.sh
+++ b/.github/scripts/get_firmware_name.sh
@@ -13,7 +13,7 @@ getBhapticsName() {
         target="$target+nimble"
     fi
 
-    if [[ $flags =~ BATTERY_ENABLED=true ]]; then
+    if [[ $flags =~ SENSESHIFT_BATTERY_ENABLED=true ]]; then
         echo "::debug::Battery is enabled, appending +battery to the target"
         target="$target+battery"
     fi
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index aa9c5153..fefeed9d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -32,7 +32,7 @@ jobs:
           - bhaptics_tactal
           - bhaptics_tactvisor
           - bhaptics_tactglove_right
-        battery_flag: [ BATTERY_ENABLED=true ]
+        battery_flag: [ SENSESHIFT_BATTERY_ENABLED=true ]
         serial_plotter_flag: [ SERIAL_PLOTTER=false ]
         nimble_flag: [ BLUETOOTH_USE_NIMBLE=false ]
         coverage: [ false ]
@@ -42,19 +42,19 @@ jobs:
           - target: bhaptics_tactsuit_x40
             os: ubuntu-latest
             coverage: true
-            battery_flag: BATTERY_ENABLED=true
+            battery_flag: SENSESHIFT_BATTERY_ENABLED=true
             serial_plotter_flag: SERIAL_PLOTTER=true
             nimble_flag: BLUETOOTH_USE_NIMBLE=true
           # - target: bhaptics_tactsuit_x40
           #   os: ubuntu-latest
           #   coverage: true
-          #   battery_flag: BATTERY_ENABLED=true
+          #   battery_flag: SENSESHIFT_BATTERY_ENABLED=true
           #   serial_plotter_flag: SERIAL_PLOTTER=true
           #   nimble_flag: BLUETOOTH_USE_NIMBLE=false
           - target: bhaptics_tactsuit_x40
             os: ubuntu-latest
             coverage: false
-            battery_flag: BATTERY_ENABLED=true
+            battery_flag: SENSESHIFT_BATTERY_ENABLED=true
             serial_plotter_flag: SERIAL_PLOTTER=false
             nimble_flag: BLUETOOTH_USE_NIMBLE=true
 
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 7e624290..6f82bad4 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -31,7 +31,7 @@ jobs:
 
         # Enabling all flags to test build for every feature
         battery_flag:
-          - BATTERY_ENABLED=true
+          - SENSESHIFT_BATTERY_ENABLED=true
         serial_plotter_flag:
           - SERIAL_PLOTTER=true
 
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 65ceb54b..9b11b4a5 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -33,7 +33,7 @@ jobs:
           - bhaptics_tactglove_left
           - bhaptics_tactglove_right
         battery_flag:
-          - BATTERY_ENABLED=true
+          - SENSESHIFT_BATTERY_ENABLED=true
         nimble_flag:
           - BLUETOOTH_USE_NIMBLE=true
 
diff --git a/firmware/mode_configs/bhaptics/tactal.cpp b/firmware/mode_configs/bhaptics/tactal.cpp
index ba2a271e..991fdce5 100644
--- a/firmware/mode_configs/bhaptics/tactal.cpp
+++ b/firmware/mode_configs/bhaptics/tactal.cpp
@@ -6,18 +6,18 @@
 
 #include "senseshift.h"
 
+#include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/pwm.hpp>
+#include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
 
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
-#include "battery/adc_naive.hpp"
-#endif
-
 using namespace OH;
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
+using namespace SenseShift::Arduino::Input;
+using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
@@ -53,12 +53,12 @@ void setupMode()
     );
     bhBleConnection->begin();
 
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
+#if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
     auto* battery = new BatterySensor(
-      new ADCNaiveBattery(36),
+      new NaiveBatterySensor(new AnalogSensor(36)),
       &App,
-      { .sampleRate = BATTERY_SAMPLE_RATE },
-      { "ADC Battery", 4096, BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+      { .sampleRate = SENSESHIFT_BATTERY_SAMPLE_RATE },
+      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     battery->begin();
 #endif
diff --git a/firmware/mode_configs/bhaptics/tactglove.cpp b/firmware/mode_configs/bhaptics/tactglove.cpp
index 8f663e62..c7fd1924 100644
--- a/firmware/mode_configs/bhaptics/tactglove.cpp
+++ b/firmware/mode_configs/bhaptics/tactglove.cpp
@@ -6,19 +6,19 @@
 
 #include "senseshift.h"
 
+#include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/pwm.hpp>
+#include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
 #include <senseshift/utility.hpp>
 
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
-#include <battery/adc_naive.hpp>
-#endif
-
 using namespace OH;
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
+using namespace SenseShift::Arduino::Input;
+using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
@@ -61,12 +61,12 @@ void setupMode()
     );
     bhBleConnection->begin();
 
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
+#if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
     auto* battery = new BatterySensor(
-      new ADCNaiveBattery(36),
+      new NaiveBatterySensor(new AnalogSensor(36)),
       &App,
-      { .sampleRate = BATTERY_SAMPLE_RATE },
-      { "ADC Battery", 4096, BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+      { .sampleRate = SENSESHIFT_BATTERY_SAMPLE_RATE },
+      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     battery->begin();
 #endif
diff --git a/firmware/mode_configs/bhaptics/tactosy2.cpp b/firmware/mode_configs/bhaptics/tactosy2.cpp
index 6a4af3a6..26937edb 100644
--- a/firmware/mode_configs/bhaptics/tactosy2.cpp
+++ b/firmware/mode_configs/bhaptics/tactosy2.cpp
@@ -6,18 +6,18 @@
 
 #include "senseshift.h"
 
+#include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/pwm.hpp>
+#include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
 
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
-#include <battery/adc_naive.hpp>
-#endif
-
 using namespace OH;
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
+using namespace SenseShift::Arduino::Input;
+using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
@@ -54,12 +54,12 @@ void setupMode()
     );
     bhBleConnection->begin();
 
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
+#if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
     auto* battery = new BatterySensor(
-      new ADCNaiveBattery(36),
+      new NaiveBatterySensor(new AnalogSensor(36)),
       &App,
-      { .sampleRate = BATTERY_SAMPLE_RATE },
-      { "ADC Battery", 4096, BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+      { .sampleRate = SENSESHIFT_BATTERY_SAMPLE_RATE },
+      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     battery->begin();
 #endif
diff --git a/firmware/mode_configs/bhaptics/tactosyf.cpp b/firmware/mode_configs/bhaptics/tactosyf.cpp
index 4595b904..33aea620 100644
--- a/firmware/mode_configs/bhaptics/tactosyf.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyf.cpp
@@ -6,18 +6,18 @@
 
 #include "senseshift.h"
 
+#include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/pwm.hpp>
+#include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
 
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
-#include <battery/adc_naive.hpp>
-#endif
-
 using namespace OH;
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
+using namespace SenseShift::Arduino::Input;
+using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
@@ -55,12 +55,12 @@ void setupMode()
     );
     bhBleConnection->begin();
 
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
+#if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
     auto* battery = new BatterySensor(
-      new ADCNaiveBattery(36),
+      new NaiveBatterySensor(new AnalogSensor(36)),
       &App,
-      { .sampleRate = BATTERY_SAMPLE_RATE },
-      { "ADC Battery", 4096, BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+      { .sampleRate = SENSESHIFT_BATTERY_SAMPLE_RATE },
+      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     battery->begin();
 #endif
diff --git a/firmware/mode_configs/bhaptics/tactosyh.cpp b/firmware/mode_configs/bhaptics/tactosyh.cpp
index c2904352..068add9e 100644
--- a/firmware/mode_configs/bhaptics/tactosyh.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyh.cpp
@@ -6,18 +6,18 @@
 
 #include "senseshift.h"
 
+#include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/pwm.hpp>
+#include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
 
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
-#include <battery/adc_naive.hpp>
-#endif
-
 using namespace OH;
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
+using namespace SenseShift::Arduino::Input;
+using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
@@ -55,12 +55,12 @@ void setupMode()
     );
     bhBleConnection->begin();
 
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
+#if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
     auto* battery = new BatterySensor(
-      new ADCNaiveBattery(36),
+      new NaiveBatterySensor(new AnalogSensor(36)),
       &App,
-      { .sampleRate = BATTERY_SAMPLE_RATE },
-      { "ADC Battery", 4096, BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+      { .sampleRate = SENSESHIFT_BATTERY_SAMPLE_RATE },
+      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     battery->begin();
 #endif
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
index 0fb4875d..300d9535 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
@@ -6,18 +6,18 @@
 
 #include "senseshift.h"
 
+#include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/pwm.hpp>
+#include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
 
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
-#include <battery/adc_naive.hpp>
-#endif
-
 using namespace OH;
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
+using namespace SenseShift::Arduino::Input;
+using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
@@ -65,12 +65,12 @@ void setupMode()
     );
     bhBleConnection->begin();
 
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
+#if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
     auto* battery = new BatterySensor(
-      new ADCNaiveBattery(36),
+      new NaiveBatterySensor(new AnalogSensor(36)),
       &App,
-      { .sampleRate = BATTERY_SAMPLE_RATE },
-      { "ADC Battery", 4096, BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+      { .sampleRate = SENSESHIFT_BATTERY_SAMPLE_RATE },
+      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     battery->begin();
 #endif
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
index 74df3252..39bb6835 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
@@ -6,18 +6,18 @@
 
 #include "senseshift.h"
 
+#include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/pca9685.hpp>
+#include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
 
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
-#include <battery/adc_naive.hpp>
-#endif
-
 using namespace OH;
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
+using namespace SenseShift::Arduino::Input;
+using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
@@ -70,12 +70,12 @@ void setupMode()
     );
     bhBleConnection->begin();
 
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
+#if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
     auto* battery = new BatterySensor(
-      new ADCNaiveBattery(36),
+      new NaiveBatterySensor(new AnalogSensor(36)),
       &App,
-      { .sampleRate = BATTERY_SAMPLE_RATE },
-      { "ADC Battery", 4096, BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+      { .sampleRate = SENSESHIFT_BATTERY_SAMPLE_RATE },
+      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     battery->begin();
 #endif
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
index 13a0689f..6fe41fc7 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
@@ -6,19 +6,19 @@
 
 #include "senseshift.h"
 
+#include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/pca9685.hpp>
 #include <senseshift/arduino/output/pwm.hpp>
+#include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
 
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
-#include <battery/adc_naive.hpp>
-#endif
-
 using namespace OH;
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
+using namespace SenseShift::Arduino::Input;
+using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
@@ -78,12 +78,12 @@ void setupMode()
     );
     bhBleConnection->begin();
 
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
+#if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
     auto* battery = new BatterySensor(
-      new ADCNaiveBattery(36),
+      new NaiveBatterySensor(new AnalogSensor(36)),
       &App,
-      { .sampleRate = BATTERY_SAMPLE_RATE },
-      { "ADC Battery", 4096, BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+      { .sampleRate = SENSESHIFT_BATTERY_SAMPLE_RATE },
+      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     battery->begin();
 #endif
diff --git a/firmware/mode_configs/bhaptics/tactvisor.cpp b/firmware/mode_configs/bhaptics/tactvisor.cpp
index 73c88463..f6e063e0 100644
--- a/firmware/mode_configs/bhaptics/tactvisor.cpp
+++ b/firmware/mode_configs/bhaptics/tactvisor.cpp
@@ -6,18 +6,18 @@
 
 #include "senseshift.h"
 
+#include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/pwm.hpp>
+#include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
 
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
-#include "battery/adc_naive.hpp"
-#endif
-
 using namespace OH;
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
+using namespace SenseShift::Arduino::Input;
+using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
@@ -53,12 +53,12 @@ void setupMode()
     );
     bhBleConnection->begin();
 
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
+#if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
     auto* battery = new BatterySensor(
-      new ADCNaiveBattery(36),
+      new NaiveBatterySensor(new AnalogSensor(36)),
       &App,
-      { .sampleRate = BATTERY_SAMPLE_RATE },
-      { "ADC Battery", 4096, BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+      { .sampleRate = SENSESHIFT_BATTERY_SAMPLE_RATE },
+      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     battery->begin();
 #endif
diff --git a/include/config/battery.h b/include/config/battery.h
index 9c34c5e8..67747fa3 100644
--- a/include/config/battery.h
+++ b/include/config/battery.h
@@ -1,17 +1,17 @@
 #pragma once
 
-#ifndef BATTERY_ENABLED
-#define BATTERY_ENABLED false
+#ifndef SENSESHIFT_BATTERY_ENABLED
+#define SENSESHIFT_BATTERY_ENABLED false
 #endif
 
-#ifndef BATTERY_SAMPLE_RATE
-#define BATTERY_SAMPLE_RATE 10000
+#ifndef SENSESHIFT_BATTERY_SAMPLE_RATE
+#define SENSESHIFT_BATTERY_SAMPLE_RATE 10000
 #endif
 
-#ifndef BATTERY_THRESHOLD_PERCENTAGE
-#define BATTERY_THRESHOLD_PERCENTAGE 20
+#ifndef SENSESHIFT_BATTERY_THRESHOLD_PERCENTAGE
+#define SENSESHIFT_BATTERY_THRESHOLD_PERCENTAGE 20
 #endif
 
-#ifndef BATTERY_TASK_PRIORITY
-#define BATTERY_TASK_PRIORITY 1
+#ifndef SENSESHIFT_BATTERY_TASK_PRIORITY
+#define SENSESHIFT_BATTERY_TASK_PRIORITY 1
 #endif
diff --git a/include/senseshift.h b/include/senseshift.h
index f2c94937..d48c99bb 100644
--- a/include/senseshift.h
+++ b/include/senseshift.h
@@ -3,12 +3,9 @@
 #include "config/all.h"
 
 #include <haptic_body.hpp>
+#include <senseshift/battery/sensor.hpp>
 #include <senseshift/events.hpp>
 
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
-#include <abstract_battery.hpp>
-#endif
-
 #include <vector>
 
 namespace SenseShift {
@@ -16,10 +13,7 @@ namespace SenseShift {
       private:
         std::vector<const IEventListener*> eventListeners{};
         Body::Haptics::HapticBody* pHapticBody;
-
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
-        OH::BatterySensor* battery;
-#endif
+        Battery::BatterySensor* battery;
 
       public:
         SenseShift();
diff --git a/lib/arduino/battery/adc_naive.cpp b/lib/arduino/battery/adc_naive.cpp
deleted file mode 100644
index d9991715..00000000
--- a/lib/arduino/battery/adc_naive.cpp
+++ /dev/null
@@ -1,16 +0,0 @@
-#include "battery/adc_naive.hpp"
-
-#include <Arduino.h>
-#include <senseshift/utility.hpp>
-
-namespace OH {
-    void ADCNaiveBattery::setup()
-    {
-        pinMode(this->pin, INPUT);
-    }
-
-    BatteryState ADCNaiveBattery::getValue()
-    {
-        return { .level = static_cast<uint8_t>(::SenseShift::simpleMap<uint16_t>(analogRead(this->pin), 4095, 255)) };
-    }
-} // namespace OH
diff --git a/lib/arduino/battery/adc_naive.hpp b/lib/arduino/battery/adc_naive.hpp
deleted file mode 100644
index c54ced7b..00000000
--- a/lib/arduino/battery/adc_naive.hpp
+++ /dev/null
@@ -1,16 +0,0 @@
-#pragma once
-
-#include <abstract_battery.hpp>
-
-namespace OH {
-    class ADCNaiveBattery : public OH::IBatterySensor {
-      private:
-        uint8_t pin;
-
-      public:
-        ADCNaiveBattery(const uint8_t pin) : pin(pin){};
-
-        BatteryState getValue() override;
-        void setup();
-    };
-} // namespace OH
diff --git a/lib/battery/senseshift/battery.hpp b/lib/battery/senseshift/battery.hpp
new file mode 100644
index 00000000..10e51896
--- /dev/null
+++ b/lib/battery/senseshift/battery.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <senseshift/events.hpp>
+
+#include <cstdint>
+
+namespace SenseShift::Battery {
+    typedef struct BatteryState {
+        uint8_t level;
+    } BatteryState_t;
+
+    class BatteryLevelEvent : public IEvent {
+      public:
+        const BatteryState_t& state;
+        BatteryLevelEvent(const BatteryState_t& state) : IEvent(OH_EVENT_BATTERY_LEVEL), state(state){};
+    };
+} // namespace SenseShift::Battery
diff --git a/lib/battery/abstract_battery.hpp b/lib/battery/senseshift/battery/sensor.hpp
similarity index 50%
rename from lib/battery/abstract_battery.hpp
rename to lib/battery/senseshift/battery/sensor.hpp
index 0e64c2ad..bd9106d7 100644
--- a/lib/battery/abstract_battery.hpp
+++ b/lib/battery/senseshift/battery/sensor.hpp
@@ -1,58 +1,48 @@
 #pragma once
 
-#include <senseshift/events.hpp>
+#include "senseshift/battery.hpp"
+
 #include <senseshift/input/sensor.hpp>
+#include <senseshift/utility.hpp>
 #include <task.hpp>
 
-#include <stdint.h>
-
-#ifndef BATTERY_TASK_PRIORITY
-#define BATTERY_TASK_PRIORITY 1
+#ifndef SENSESHIFT_BATTERY_TASK_PRIORITY
+#define SENSESHIFT_BATTERY_TASK_PRIORITY 1
 #endif
 
-namespace OH {
+namespace SenseShift::Battery {
+    typedef struct BatteryConfig {
+        uint sampleRate;
+    } BatteryConfig_t;
+
     /**
      * Tasked sensor decorator
      */
     template<typename _Tp>
-    class TaskedSensor : public Task<TaskedSensor<_Tp>>, public ::SenseShift::Input::MemoizedSensor<_Tp> {
-        friend class Task<TaskedSensor<_Tp>>;
-
-      private:
-        virtual void run(void)
-        {
-            while (true) {
-                this->updateValue();
-                delay(this->rate);
-            }
-        };
-
-      protected:
-        uint32_t rate;
+    class TaskedSensor : public OH::Task<TaskedSensor<_Tp>>, public ::SenseShift::Input::MemoizedSensor<_Tp> {
+        friend class OH::Task<TaskedSensor<_Tp>>;
 
       public:
-        TaskedSensor(::SenseShift::Input::ISensor<_Tp>* sensor, TaskConfig taskConfig, uint32_t rate) :
-          ::SenseShift::Input::MemoizedSensor<_Tp>(sensor), Task<TaskedSensor<_Tp>>(taskConfig), rate(rate){};
+        TaskedSensor(::SenseShift::Input::ISensor<_Tp>* sensor, OH::TaskConfig taskConfig, const uint32_t rate) :
+          ::SenseShift::Input::MemoizedSensor<_Tp>(sensor), OH::Task<TaskedSensor<_Tp>>(taskConfig), rate(rate){};
 
         void begin() override
         {
             this->setup();
-            this->Task<TaskedSensor<_Tp>>::begin();
+            this->OH::Task<TaskedSensor<_Tp>>::begin();
         };
-    };
-
-    struct BatteryState {
-        uint8_t level;
-    };
 
-    class BatteryLevelEvent : public ::SenseShift::IEvent {
-      public:
-        const BatteryState state;
-        BatteryLevelEvent(const BatteryState state) : ::SenseShift::IEvent(OH_EVENT_BATTERY_LEVEL), state(state){};
-    };
+      protected:
+        const uint32_t rate;
 
-    struct BatteryConfig {
-        uint sampleRate;
+      private:
+        virtual void run(void)
+        {
+            while (true) {
+                this->updateValue();
+                delay(this->rate);
+            }
+        };
     };
 
     /**
@@ -61,18 +51,15 @@ namespace OH {
     typedef ::SenseShift::Input::ISensor<BatteryState> IBatterySensor;
 
     class BatterySensor : public TaskedSensor<BatteryState> {
-        friend class Task<TaskedSensor<BatteryState>>;
+        friend class OH::Task<TaskedSensor<BatteryState>>;
         friend class TaskedSensor<BatteryState>;
 
-      private:
-        ::SenseShift::IEventDispatcher* eventDispatcher;
-
       public:
         BatterySensor(
           IBatterySensor* sensor,
           ::SenseShift::IEventDispatcher* eventDispatcher,
-          BatteryConfig config,
-          TaskConfig taskConfig
+          const BatteryConfig_t& config,
+          OH::TaskConfig taskConfig
         ) :
           TaskedSensor<BatteryState>(sensor, taskConfig, config.sampleRate), eventDispatcher(eventDispatcher){};
 
@@ -84,5 +71,26 @@ namespace OH {
                 delay(this->rate);
             }
         }
+
+      private:
+        ::SenseShift::IEventDispatcher* eventDispatcher;
+    };
+
+    class NaiveBatterySensor : public IBatterySensor {
+      public:
+        NaiveBatterySensor(::SenseShift::Input::ISensor<uint16_t>* sensor) : sensor(sensor){};
+
+        BatteryState getValue() override
+        {
+            return { .level =
+                       static_cast<uint8_t>(::SenseShift::simpleMap<uint16_t>(this->sensor->getValue(), 4095, 255)) };
+        };
+        void setup()
+        {
+            this->sensor->setup();
+        }
+
+      private:
+        ::SenseShift::Input::ISensor<uint16_t>* sensor;
     };
-} // namespace OH
+} // namespace SenseShift::Battery
diff --git a/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp b/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
index 7e54500e..fd16ec75 100644
--- a/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
+++ b/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
@@ -185,11 +185,7 @@ namespace SenseShift::BH::BLE {
 #endif
 
             // original bHaptics Player require non-null value for battery level, otherwise it crashes
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
             uint16_t defaultLevel = 0;
-#else
-            uint16_t defaultLevel = 100;
-#endif
 
             this->batteryChar->setValue(defaultLevel);
             // this->batteryChar->notify();
diff --git a/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp b/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
index 90247d74..49068f52 100644
--- a/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
+++ b/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <senseshift/battery.hpp>
 #include <senseshift/bh/ble/constants.hpp>
 #include <senseshift/bh/constants.hpp>
 #include <senseshift/events.hpp>
@@ -14,10 +15,6 @@
 #include <BLEDevice.h>
 #endif
 
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
-#include <abstract_battery.hpp>
-#endif
-
 namespace SenseShift::BH::BLE {
     typedef struct ConnectionConfig {
         static constexpr size_t SN_LENGTH = 10;
@@ -36,25 +33,22 @@ namespace SenseShift::BH::BLE {
     };
     static ConnectionCallbacks defaultCallback;
 
-    class Connection final : public ::SenseShift::IEventListener {
+    class Connection final : public IEventListener {
       public:
         typedef std::function<void(std::string&)> MotorHandler_t;
 
-        Connection(
-          const ConnectionConfig_t& config, MotorHandler_t motorHandler, ::SenseShift::IEventDispatcher* eventDispatcher
-        ) :
+        Connection(const ConnectionConfig_t& config, MotorHandler_t motorHandler, IEventDispatcher* eventDispatcher) :
           config(config), motorHandler(motorHandler), eventDispatcher(eventDispatcher)
         {
             this->eventDispatcher->addEventListener(this);
         };
 
         void begin(void);
-        void handleEvent(const ::SenseShift::IEvent* event) const override
+        void handleEvent(const IEvent* event) const override
         {
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
             if (event->eventName == OH_EVENT_BATTERY_LEVEL) {
-                uint16_t level = ::SenseShift::simpleMap<uint8_t>(
-                  static_cast<const OH::BatteryLevelEvent*>(event)->state.level,
+                uint16_t level = simpleMap<uint8_t>(
+                  static_cast<const ::SenseShift::Battery::BatteryLevelEvent*>(event)->state.level,
                   255,
                   100
                 );
@@ -64,7 +58,6 @@ namespace SenseShift::BH::BLE {
 
                 return;
             }
-#endif
         };
 
         void setCallbacks(ConnectionCallbacks* pCallbacks)
@@ -86,9 +79,5 @@ namespace SenseShift::BH::BLE {
         BLECharacteristic* batteryChar = nullptr;
 
         ConnectionCallbacks* callbacks = &defaultCallback;
-
-#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true
-        void handleBatteryChange(const OH::BatteryLevelEvent* event) const;
-#endif
     };
 } // namespace SenseShift::BH::BLE
diff --git a/lib/ina219/battery/ina219.cpp b/lib/ina219/battery/ina219.cpp
deleted file mode 100644
index 06ac6fb1..00000000
--- a/lib/ina219/battery/ina219.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-#include "battery/ina219.hpp"
-#include <utility.hpp>
-
-namespace OH {
-    void OH::INA219_Battery::setup()
-    {
-        this->active = this->sensor->begin();
-    }
-
-    BatteryState OH::INA219_Battery::getValue()
-    {
-        if (!this->active) {
-            return { 0 };
-        }
-
-        auto batteryVoltage = this->sensor->getBusVoltage_V();
-        // TODO: change this linear transformation to smth more useful
-        auto batteryPercentage = (batteryVoltage - 3.0) / 0.96;
-
-        return
-        {
-            simpleMap<float>(batteryPercentage, 1.0f, 255.0f);
-        };
-    }
-} // namespace OH
diff --git a/lib/ina219/battery/ina219.hpp b/lib/ina219/battery/ina219.hpp
deleted file mode 100644
index 109400de..00000000
--- a/lib/ina219/battery/ina219.hpp
+++ /dev/null
@@ -1,19 +0,0 @@
-#pragma once
-
-#include <abstract_battery.hpp>
-
-#include <Adafruit_INA219.h>
-
-namespace OH {
-    class INA219_Battery : public OH::IBatterySensor {
-      private:
-        bool active = false;
-        Adafruit_INA219* sensor;
-
-      public:
-        INA219_Battery(Adafruit_INA219* sensor) : sensor(sensor){};
-
-        void setup() override;
-        BatteryState getValue() override;
-    };
-} // namespace OH
diff --git a/lib/ina219/senseshift/arduino/battery/ina219.hpp b/lib/ina219/senseshift/arduino/battery/ina219.hpp
new file mode 100644
index 00000000..0d3487bf
--- /dev/null
+++ b/lib/ina219/senseshift/arduino/battery/ina219.hpp
@@ -0,0 +1,35 @@
+#pragma once
+
+#include <senseshift/battery/sensor.hpp>
+
+#include <Adafruit_INA219.h>
+
+namespace SenseShift::Arduino::Battery {
+    class INA219_Battery : public ::SenseShift::Battery::IBatterySensor {
+      private:
+        bool active = false;
+        Adafruit_INA219* sensor;
+
+      public:
+        INA219_Battery(Adafruit_INA219* sensor) : sensor(sensor){};
+
+        void setup() override
+        {
+            this->active = this->sensor->begin();
+        }
+        ::SenseShift::Battery::BatteryState getValue() override
+        {
+            if (!this->active) {
+                return { 0 };
+            }
+
+            auto batteryVoltage = this->sensor->getBusVoltage_V();
+            // TODO: change this linear transformation to smth more useful
+            auto batteryPercentage = (batteryVoltage - 3.0) / 0.96;
+
+            return {
+                simpleMap<float>(batteryPercentage, 1.0f, 255.0f),
+            };
+        }
+    };
+} // namespace SenseShift::Arduino::Battery
diff --git a/lib/max17048/battery/max17048.cpp b/lib/max17048/battery/max17048.cpp
deleted file mode 100644
index d8d89383..00000000
--- a/lib/max17048/battery/max17048.cpp
+++ /dev/null
@@ -1,32 +0,0 @@
-#include "battery/max17048.hpp"
-
-namespace OH {
-    /**
-     * @see
-     * https://github.com/sparkfun/SparkFun_MAX1704x_Fuel_Gauge_Arduino_Library/blob/main/examples/Example1_Simple/Example1_Simple.ino
-     */
-    void MAX1704_Battery::setup()
-    {
-        // Set up the MAX17043 LiPo fuel gauge:
-        this->active = this->gauge->begin();
-
-        if (this->active) {
-            // Quick start restarts the MAX17043 in hopes of getting a more accurate
-            // guess for the SOC.
-            this->gauge->quickStart();
-
-            // We can set an interrupt to alert when the battery SoC gets too low.
-            // We can alert at anywhere between 1% - 32%:
-            // this->gauge->setThreshold(BATTERY_THRESHOLD_PERCENTAGE);
-        }
-    }
-
-    BatteryState MAX1704_Battery::getValue()
-    {
-        if (!this->active) {
-            return { 0 };
-        }
-
-        return { .level = this->gauge->getSOC() };
-    }
-} // namespace OH
diff --git a/lib/max17048/battery/max17048.hpp b/lib/max17048/battery/max17048.hpp
deleted file mode 100644
index 3c43fbcc..00000000
--- a/lib/max17048/battery/max17048.hpp
+++ /dev/null
@@ -1,20 +0,0 @@
-#pragma once
-
-#include <abstract_battery.hpp>
-
-#include <SparkFun_MAX1704x_Fuel_Gauge_Arduino_Library.h>
-
-namespace OH {
-    class MAX1704_Battery : public OH::IBatterySensor {
-      private:
-        bool active = false;
-        SFE_MAX1704X* gauge;
-
-      protected:
-      public:
-        MAX1704_Battery(SFE_MAX1704X* gauge) : gauge(gauge){};
-
-        void setup() override;
-        BatteryState getValue() override;
-    };
-} // namespace OH
diff --git a/lib/max17048/senseshift/arduino/battery/max17048.hpp b/lib/max17048/senseshift/arduino/battery/max17048.hpp
new file mode 100644
index 00000000..c95c8e86
--- /dev/null
+++ b/lib/max17048/senseshift/arduino/battery/max17048.hpp
@@ -0,0 +1,45 @@
+#pragma once
+
+#include <senseshift/battery/sensor.hpp>
+
+#include <SparkFun_MAX1704x_Fuel_Gauge_Arduino_Library.h>
+
+namespace SenseShift::Arduino::Battery {
+    class MAX1704_Battery : public ::SenseShift::Battery::IBatterySensor {
+      public:
+        MAX1704_Battery(SFE_MAX1704X* gauge) : gauge(gauge){};
+
+        /**
+         * @see
+         * https://github.com/sparkfun/SparkFun_MAX1704x_Fuel_Gauge_Arduino_Library/blob/main/examples/Example1_Simple/Example1_Simple.ino
+         */
+        void setup() override
+        {
+            // Set up the MAX17043 LiPo fuel gauge:
+            this->active = this->gauge->begin();
+
+            if (this->active) {
+                // Quick start restarts the MAX17043 in hopes of getting a more accurate
+                // guess for the SOC.
+                this->gauge->quickStart();
+
+                // We can set an interrupt to alert when the battery SoC gets too low.
+                // We can alert at anywhere between 1% - 32%:
+                // this->gauge->setThreshold(SENSESHIFT_BATTERY_THRESHOLD_PERCENTAGE);
+            }
+        }
+
+        ::SenseShift::Battery::BatteryState getValue() override
+        {
+            if (!this->active) {
+                return { 0 };
+            }
+
+            return { .level = this->gauge->getSOC() };
+        }
+
+      private:
+        bool active = false;
+        SFE_MAX1704X* gauge;
+    };
+} // namespace SenseShift::Arduino::Battery
diff --git a/platformio.ini b/platformio.ini
index c11d81a2..6d9b15a4 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -26,7 +26,7 @@ build_flags       =
 ;	-D DEBUG_MODE=0
 ;	-D DEBUG_ESP_PORT=Serial
 ;	-D SERIAL_PLOTTER=true
-;	-D BATTERY_ENABLED=true
+;	-D SENSESHIFT_BATTERY_ENABLED=true
 	-D BLUETOOTH_USE_NIMBLE=true
 
 build_src_filter  =

From bfb53df79de2e0d18f41263db9cf77b6b12ffcf1 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Tue, 22 Aug 2023 01:42:45 +0400
Subject: [PATCH 12/82] refactor(Core): use `SenseShift` namespace and dir for
 math

---
 lib/haptics/haptic_plane.hpp              | 6 ++++--
 lib/haptics/haptics_interface.hpp         | 4 ++--
 lib/math/{ => senseshift/math}/point2.hpp | 8 +++++---
 test/test_math_point2/main.cpp            | 4 ++--
 4 files changed, 13 insertions(+), 9 deletions(-)
 rename lib/math/{ => senseshift/math}/point2.hpp (86%)

diff --git a/lib/haptics/haptic_plane.hpp b/lib/haptics/haptic_plane.hpp
index 88227f4f..4441b03a 100644
--- a/lib/haptics/haptic_plane.hpp
+++ b/lib/haptics/haptic_plane.hpp
@@ -115,9 +115,11 @@ namespace SenseShift::Body::Haptics {
          * @tparam _Tp The type of the point index.
          */
         template<typename _Tp>
-        [[nodiscard]] static constexpr inline OH::Point2<_Tp> mapPoint(_Tp x, _Tp y, _Tp x_max, _Tp y_max)
+        [[nodiscard]] static constexpr inline ::SenseShift::Math::Point2<_Tp>
+          mapPoint(_Tp x, _Tp y, _Tp x_max, _Tp y_max)
         {
-            using Point_t = OH::Point2<_Tp>;
+            using Point_t = ::SenseShift::Math::Point2<_Tp>;
+
             return Point_t(
               ::SenseShift::accurateMap<_Tp>(x + 1, 0, x_max + 2, Point_t::MIN, Point_t::MAX),
               ::SenseShift::accurateMap<_Tp>(y + 1, 0, y_max + 2, Point_t::MIN, Point_t::MAX)
diff --git a/lib/haptics/haptics_interface.hpp b/lib/haptics/haptics_interface.hpp
index 5afc9ffd..5b5a13bf 100644
--- a/lib/haptics/haptics_interface.hpp
+++ b/lib/haptics/haptics_interface.hpp
@@ -3,7 +3,7 @@
 #include <stdint.h>
 #include <variant>
 
-#include <point2.hpp>
+#include <senseshift/math/point2.hpp>
 
 namespace SenseShift::Body::Haptics {
     typedef uint8_t EffectIntex_t;
@@ -47,7 +47,7 @@ namespace SenseShift::Body::Haptics {
     } Target_t;
 
     typedef uint8_t Coordinate_t;
-    typedef OH::Point2<Coordinate_t> Position_t;
+    typedef ::SenseShift::Math::Point2<Coordinate_t> Position_t;
 
     // Vibration intensity.
     typedef struct VibroEffectData {
diff --git a/lib/math/point2.hpp b/lib/math/senseshift/math/point2.hpp
similarity index 86%
rename from lib/math/point2.hpp
rename to lib/math/senseshift/math/point2.hpp
index 0cf151b8..6f8131d5 100644
--- a/lib/math/point2.hpp
+++ b/lib/math/senseshift/math/point2.hpp
@@ -3,10 +3,12 @@
 #include <cmath>
 #include <tuple>
 
-namespace OH {
+namespace SenseShift::Math {
     template<typename _Tp>
     struct Point2 {
-        static_assert(std::is_arithmetic<_Tp>::value, "OH::Point2 only can be used with arithmetic types");
+        static_assert(
+          std::is_arithmetic<_Tp>::value, "::SenseShift::Math::Point2 only can be used with arithmetic types"
+        );
 
         typedef _Tp Value_t;
 
@@ -46,4 +48,4 @@ namespace OH {
     typedef Point2<int> Point2i;
     typedef Point2<short> Point2s;
     typedef Point2<unsigned short> Point2w;
-}; // namespace OH
+}; // namespace SenseShift::Math
diff --git a/test/test_math_point2/main.cpp b/test/test_math_point2/main.cpp
index 8da8b3dd..a4fa7baf 100644
--- a/test/test_math_point2/main.cpp
+++ b/test/test_math_point2/main.cpp
@@ -1,7 +1,7 @@
-#include <point2.hpp>
+#include <senseshift/math/point2.hpp>
 #include <unity.h>
 
-using namespace OH;
+using namespace SenseShift::Math;
 
 void test_operator_equal(void)
 {

From ccf35e75f31b895296040fe3ec58a59f95c67d72 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Tue, 22 Aug 2023 01:55:30 +0400
Subject: [PATCH 13/82] refactor(Core): use `SenseShift` namespace and dir for
 calibration

---
 .github/scripts/get_firmware_name.sh             | 6 +++---
 firmware/mode_configs/opengloves/opengloves.cpp  | 6 +++---
 lib/calibration/{ => senseshift}/calibration.hpp | 6 ++++--
 lib/opengloves/sensor/og_finger.hpp              | 7 +++++--
 lib/opengloves/sensor/og_sensor.hpp              | 1 -
 lib/opengloves_task/opengloves_task.hpp          | 5 +++--
 lib/sensor/senseshift/input/sensor.hpp           | 8 ++++----
 test/test_calibration/main.cpp                   | 2 +-
 8 files changed, 23 insertions(+), 18 deletions(-)
 rename lib/calibration/{ => senseshift}/calibration.hpp (96%)

diff --git a/.github/scripts/get_firmware_name.sh b/.github/scripts/get_firmware_name.sh
index cd7a2b6d..988e2d6c 100755
--- a/.github/scripts/get_firmware_name.sh
+++ b/.github/scripts/get_firmware_name.sh
@@ -50,17 +50,17 @@ getOpenGlovesName() {
         target="$target+bluetooth"
     fi
 
-    if [[ $flags =~ CALIBRATION_CURL=OH::MinMaxCalibrator ]]; then
+    if [[ $flags =~ CALIBRATION_CURL=::SenseShift::Calibration::MinMaxCalibrator ]]; then
         echo "::debug::MinMaxCalibrator is enabled, appending +curl_minmax to the target"
         target="$target+curl_minmaxcalib"
     fi
 
-    if [[ $flags =~ CALIBRATION_CURL=OH::CenterPointDeviationCalibrator ]]; then
+    if [[ $flags =~ CALIBRATION_CURL=::SenseShift::Calibration::CenterPointDeviationCalibrator ]]; then
         echo "::debug::CenterPointDeviationCalibrator is enabled, appending +curl_cpcalib to the target"
         target="$target+curl_cpcalib"
     fi
 
-    if [[ $flags =~ CALIBRATION_CURL=OH::FixedCenterPointDeviationCalibrator ]]; then
+    if [[ $flags =~ CALIBRATION_CURL=::SenseShift::Calibration::FixedCenterPointDeviationCalibrator ]]; then
         echo "::debug::FixedCenterPointDeviationCalibrator is enabled, appending +curl_fcpcalib to the target"
         target="$target+curl_fcpcalib"
     fi
diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp
index 2ac3d7d0..47d8e289 100644
--- a/firmware/mode_configs/opengloves/opengloves.cpp
+++ b/firmware/mode_configs/opengloves/opengloves.cpp
@@ -1,10 +1,10 @@
-#include <calibration.hpp>
 #include <og_constants.hpp>
 #include <og_serial_communication.hpp>
 #include <opengloves_task.hpp>
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/input/sensor/digital.hpp>
 #include <senseshift/arduino/output/servo.hpp>
+#include <senseshift/calibration.hpp>
 #include <senseshift/input/sensor.hpp>
 #include <senseshift/input/sensor/joystick.hpp>
 #include <senseshift/utility.hpp>
@@ -34,10 +34,10 @@
 #pragma region Calibration
 
 #ifndef CALIBRATION_CURL
-#define CALIBRATION_CURL OH::MinMaxCalibrator<uint16_t, 0, ANALOG_MAX>
+#define CALIBRATION_CURL ::SenseShift::Calibration::MinMaxCalibrator<uint16_t, 0, ANALOG_MAX>
 #endif
 #ifndef CALIBRATION_SPLAY
-#define CALIBRATION_SPLAY OH::MinMaxCalibrator<uint16_t, 0, ANALOG_MAX>
+#define CALIBRATION_SPLAY ::SenseShift::Calibration::MinMaxCalibrator<uint16_t, 0, ANALOG_MAX>
 #endif
 
 #ifndef CALIBRATION_DURATION
diff --git a/lib/calibration/calibration.hpp b/lib/calibration/senseshift/calibration.hpp
similarity index 96%
rename from lib/calibration/calibration.hpp
rename to lib/calibration/senseshift/calibration.hpp
index fa5faa86..fb48f060 100644
--- a/lib/calibration/calibration.hpp
+++ b/lib/calibration/senseshift/calibration.hpp
@@ -7,7 +7,7 @@
 
 #include <senseshift/utility.hpp>
 
-namespace OH {
+namespace SenseShift::Calibration {
     struct ICalibrated {
         virtual void resetCalibration() = 0;
         virtual void enableCalibration() = 0;
@@ -34,6 +34,8 @@ namespace OH {
 
     template<typename _Tp>
     struct ICalibrator {
+        static_assert(std::is_arithmetic<_Tp>::value, "ICalibrator only can be used with arithmetic types");
+
         virtual void reset() = 0;
         virtual void update(_Tp input) = 0;
         virtual _Tp calibrate(_Tp input) const = 0;
@@ -163,4 +165,4 @@ namespace OH {
             );
         }
     };
-} // namespace OH
+} // namespace SenseShift::Calibration
diff --git a/lib/opengloves/sensor/og_finger.hpp b/lib/opengloves/sensor/og_finger.hpp
index 1b5de1a9..62f4b244 100644
--- a/lib/opengloves/sensor/og_finger.hpp
+++ b/lib/opengloves/sensor/og_finger.hpp
@@ -80,7 +80,7 @@ namespace OpenGloves {
     /**
      * Simple finger sensor that only provides calibration.
      */
-    class CalibratedFingerSensor : public SimpleFingerSensor, public OH::ICalibrated {
+    class CalibratedFingerSensor : public SimpleFingerSensor, public SenseShift::Calibration::ICalibrated {
       public:
         CalibratedFingerSensor(FingerSensors sensors) : SimpleFingerSensor(sensors){};
 
@@ -121,7 +121,10 @@ namespace OpenGloves {
         }
     };
 
-    class FingerSensor : public StringEncodedMemoizedSensor<FingerValue>, public OH::ICalibrated, public ICurl {
+    class FingerSensor :
+      public StringEncodedMemoizedSensor<FingerValue>,
+      public SenseShift::Calibration::ICalibrated,
+      public ICurl {
       public:
         FingerSensor(CalibratedFingerSensor* sensor, IEncodedInput::Type type) :
           StringEncodedMemoizedSensor<FingerValue>(sensor, type){};
diff --git a/lib/opengloves/sensor/og_sensor.hpp b/lib/opengloves/sensor/og_sensor.hpp
index 60e099d7..71c4c439 100644
--- a/lib/opengloves/sensor/og_sensor.hpp
+++ b/lib/opengloves/sensor/og_sensor.hpp
@@ -1,6 +1,5 @@
 #pragma once
 
-#include <calibration.hpp>
 #include <og_protocol.hpp>
 #include <senseshift/input/sensor.hpp>
 
diff --git a/lib/opengloves_task/opengloves_task.hpp b/lib/opengloves_task/opengloves_task.hpp
index a5dd8b3b..9d1f115a 100644
--- a/lib/opengloves_task/opengloves_task.hpp
+++ b/lib/opengloves_task/opengloves_task.hpp
@@ -4,12 +4,12 @@
 
 #include <optional>
 
-#include <calibration.hpp>
 #include <og_alpha_encoding.hpp>
 #include <og_ffb.hpp>
 #include <og_serial_communication.hpp>
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/input/sensor/digital.hpp>
+#include <senseshift/calibration.hpp>
 #include <senseshift/input/sensor.hpp>
 #include <senseshift/input/sensor/joystick.hpp>
 #include <senseshift/utility.hpp>
@@ -141,7 +141,8 @@ namespace OpenGloves {
         std::vector<IStringEncodedMemoizedSensor*> allSensors;
 
         std::optional<StringEncodedMemoizedSensor<bool>>& calibrationButton;
-        std::vector<OH::ICalibrated*> calibrated = std::vector<OH::ICalibrated*>();
+        std::vector<SenseShift::Calibration::ICalibrated*> calibrated =
+          std::vector<SenseShift::Calibration::ICalibrated*>();
         unsigned long long calibrationStarted = 0;
 
         void startCalibration()
diff --git a/lib/sensor/senseshift/input/sensor.hpp b/lib/sensor/senseshift/input/sensor.hpp
index 1b08ec60..d721257e 100644
--- a/lib/sensor/senseshift/input/sensor.hpp
+++ b/lib/sensor/senseshift/input/sensor.hpp
@@ -2,7 +2,7 @@
 
 #include <type_traits>
 
-#include <calibration.hpp>
+#include <senseshift/calibration.hpp>
 #include <senseshift/logging.hpp>
 
 #if defined(__AVR__)
@@ -90,10 +90,10 @@ namespace SenseShift::Input {
      * @tparam _Tp Type of the sensor value
      */
     template<typename _Tp>
-    class CalibratedSensor : public ISensor<_Tp>, public OH::Calibrated {
+    class CalibratedSensor : public ISensor<_Tp>, public ::SenseShift::Calibration::Calibrated {
       protected:
         ISensor<_Tp>* sensor;
-        OH::ICalibrator<_Tp>* calibrator;
+        ::SenseShift::Calibration::ICalibrator<_Tp>* calibrator;
 
         _Tp getCalibratedValue()
         {
@@ -111,7 +111,7 @@ namespace SenseShift::Input {
          * @param sensor Sensor to be decorated
          * @param calibrator ICalibrator algorithm to be used
          */
-        CalibratedSensor(ISensor<_Tp>* sensor, OH::ICalibrator<_Tp>* calibrator) :
+        CalibratedSensor(ISensor<_Tp>* sensor, ::SenseShift::Calibration::ICalibrator<_Tp>* calibrator) :
           sensor(sensor), calibrator(calibrator){};
 
         void setup() override
diff --git a/test/test_calibration/main.cpp b/test/test_calibration/main.cpp
index 52452a67..3455463c 100644
--- a/test/test_calibration/main.cpp
+++ b/test/test_calibration/main.cpp
@@ -1,4 +1,4 @@
-#include <calibration.hpp>
+#include <senseshift/calibration.hpp>
 #include <unity.h>
 
 using namespace OH;

From 87e8a2bfbc5ca984aba56572cc92b99edff80301 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Tue, 22 Aug 2023 02:07:28 +0400
Subject: [PATCH 14/82] refactor: move serial plotter to `SenseShift`
 namespace/dir

---
 .github/scripts/get_firmware_name.sh          |  2 +-
 .github/workflows/ci.yml                      | 14 +++++-----
 .github/workflows/codeql-analysis.yml         |  2 +-
 firmware/firmware.cpp                         | 22 ++++++++-------
 lib/arduino/components/serial_plotter.cpp     | 26 -----------------
 .../arduino/components/serial_plotter.cpp     | 28 +++++++++++++++++++
 .../arduino}/components/serial_plotter.hpp    | 25 +++++++++++------
 lib/opengloves/og_protocol.hpp                |  4 +++
 platformio.ini                                |  2 +-
 test/test_calibration/main.cpp                |  2 +-
 test/test_opengloves_finger/main.cpp          |  3 +-
 test/test_sensor/main.cpp                     |  2 +-
 12 files changed, 74 insertions(+), 58 deletions(-)
 delete mode 100644 lib/arduino/components/serial_plotter.cpp
 create mode 100644 lib/arduino/senseshift/arduino/components/serial_plotter.cpp
 rename lib/arduino/{ => senseshift/arduino}/components/serial_plotter.hpp (56%)

diff --git a/.github/scripts/get_firmware_name.sh b/.github/scripts/get_firmware_name.sh
index 988e2d6c..75546a23 100755
--- a/.github/scripts/get_firmware_name.sh
+++ b/.github/scripts/get_firmware_name.sh
@@ -18,7 +18,7 @@ getBhapticsName() {
         target="$target+battery"
     fi
 
-    if [[ $flags =~ SERIAL_PLOTTER=true ]]; then
+    if [[ $flags =~ SENSESHIFT_SERIAL_PLOTTER=true ]]; then
         echo "::debug::Serial Plotter is enabled, appending +serialplotter to the target"
         target="$target+serialplotter"
     fi
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index fefeed9d..66ab3dbd 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -33,7 +33,7 @@ jobs:
           - bhaptics_tactvisor
           - bhaptics_tactglove_right
         battery_flag: [ SENSESHIFT_BATTERY_ENABLED=true ]
-        serial_plotter_flag: [ SERIAL_PLOTTER=false ]
+        serial_plotter_flag: [ SENSESHIFT_SERIAL_PLOTTER=false ]
         nimble_flag: [ BLUETOOTH_USE_NIMBLE=false ]
         coverage: [ false ]
 
@@ -43,19 +43,19 @@ jobs:
             os: ubuntu-latest
             coverage: true
             battery_flag: SENSESHIFT_BATTERY_ENABLED=true
-            serial_plotter_flag: SERIAL_PLOTTER=true
+            serial_plotter_flag: SENSESHIFT_SERIAL_PLOTTER=true
             nimble_flag: BLUETOOTH_USE_NIMBLE=true
           # - target: bhaptics_tactsuit_x40
           #   os: ubuntu-latest
           #   coverage: true
           #   battery_flag: SENSESHIFT_BATTERY_ENABLED=true
-          #   serial_plotter_flag: SERIAL_PLOTTER=true
+          #   serial_plotter_flag: SENSESHIFT_SERIAL_PLOTTER=true
           #   nimble_flag: BLUETOOTH_USE_NIMBLE=false
           - target: bhaptics_tactsuit_x40
             os: ubuntu-latest
             coverage: false
             battery_flag: SENSESHIFT_BATTERY_ENABLED=true
-            serial_plotter_flag: SERIAL_PLOTTER=false
+            serial_plotter_flag: SENSESHIFT_SERIAL_PLOTTER=false
             nimble_flag: BLUETOOTH_USE_NIMBLE=true
 
     steps:
@@ -177,15 +177,15 @@ jobs:
         include:
           - os: ubuntu-latest
             target: lucidgloves-prototype4-ffb
-            curl_calibration_flag: CALIBRATION_CURL="OH::MinMaxCalibrator<uint16_t, 0, ANALOG_MAX>"
+            curl_calibration_flag: CALIBRATION_CURL="::SenseShift::Calibration::MinMaxCalibrator<uint16_t, 0, ANALOG_MAX>"
             coverage: true
           - os: ubuntu-latest
             target: lucidgloves-prototype4-ffb
-            curl_calibration_flag: CALIBRATION_CURL="OH::CenterPointDeviationCalibrator<uint16_t, 20, 270, 0, ANALOG_MAX>"
+            curl_calibration_flag: CALIBRATION_CURL="::SenseShift::Calibration::CenterPointDeviationCalibrator<uint16_t, 20, 270, 0, ANALOG_MAX>"
             coverage: true
           - os: ubuntu-latest
             target: lucidgloves-prototype4-ffb
-            curl_calibration_flag: CALIBRATION_CURL="OH::FixedCenterPointDeviationCalibrator<uint16_t, 20, 270, 0, ANALOG_MAX>"
+            curl_calibration_flag: CALIBRATION_CURL="::SenseShift::Calibration::FixedCenterPointDeviationCalibrator<uint16_t, 20, 270, 0, ANALOG_MAX>"
             coverage: true
           - os: ubuntu-latest
             target: indexer-csf
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 6f82bad4..74b3dd8d 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -33,7 +33,7 @@ jobs:
         battery_flag:
           - SENSESHIFT_BATTERY_ENABLED=true
         serial_plotter_flag:
-          - SERIAL_PLOTTER=true
+          - SENSESHIFT_SERIAL_PLOTTER=true
 
     steps:
     - name: Checkout repository
diff --git a/firmware/firmware.cpp b/firmware/firmware.cpp
index ae0f0668..c30147c3 100644
--- a/firmware/firmware.cpp
+++ b/firmware/firmware.cpp
@@ -4,13 +4,13 @@
 #include <Arduino.h>
 #endif // ARDUINO
 
-#if defined(SERIAL_PLOTTER) && SERIAL_PLOTTER == true
-#include <components/serial_plotter.hpp>
-#endif // SERIAL_PLOTTER
+#if defined(SENSESHIFT_SERIAL_PLOTTER) && SENSESHIFT_SERIAL_PLOTTER == true
+#include <senseshift/arduino/components/serial_plotter.hpp>
+#endif // SENSESHIFT_SERIAL_PLOTTER
 
-#ifndef SERIAL_PLOTTER_PORT
-#define SERIAL_PLOTTER_PORT Serial
-#endif // SERIAL_PLOTTER_PORT
+#ifndef SENSESHIFT_SERIAL_PLOTTER_PORT
+#define SENSESHIFT_SERIAL_PLOTTER_PORT Serial
+#endif // SENSESHIFT_SERIAL_PLOTTER_PORT
 
 #ifndef PIO_UNIT_TESTING
 
@@ -25,11 +25,13 @@ void setup()
 {
     setupMode();
 
-#if defined(SERIAL_PLOTTER) && SERIAL_PLOTTER == true
-    auto* serialOutputState =
-      new OH::SerialPlotter_OutputStates<HardwareSerial>(SERIAL_PLOTTER_PORT, App.getHapticBody());
+#if defined(SENSESHIFT_SERIAL_PLOTTER) && SENSESHIFT_SERIAL_PLOTTER == true
+    auto* serialOutputState = new ::SenseShift::Arduino::SerialPlotter_OutputStates<HardwareSerial>(
+      SENSESHIFT_SERIAL_PLOTTER_PORT,
+      App.getHapticBody()
+    );
     serialOutputState->begin();
-#endif // SERIAL_PLOTTER
+#endif // SENSESHIFT_SERIAL_PLOTTER
 }
 
 void loop()
diff --git a/lib/arduino/components/serial_plotter.cpp b/lib/arduino/components/serial_plotter.cpp
deleted file mode 100644
index d5be860b..00000000
--- a/lib/arduino/components/serial_plotter.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-#include "components/serial_plotter.hpp"
-
-struct PlaneVisitor {
-    const SenseShift::Body::Haptics::Target_t target;
-    HardwareSerial* serial;
-
-    void operator()(const SenseShift::Body::Haptics::VibroPlane* plane) const
-    {
-        for (const auto& [position, state] : *(plane->getActuatorStates())) {
-            this->serial->printf("Output[%u][%ux%u]:%u, ", this->target, position.x, position.y, state.intensity);
-        }
-    }
-};
-
-template<typename _Tp>
-void OH::SerialPlotter_OutputStates<_Tp>::run()
-{
-    while (true) {
-        for (const auto& [target, plane] : *output->getTargets()) {
-            std::visit(PlaneVisitor{ target, this->serial }, plane);
-        }
-        this->serial->println();
-
-        delay(this->sampleRate);
-    }
-}
diff --git a/lib/arduino/senseshift/arduino/components/serial_plotter.cpp b/lib/arduino/senseshift/arduino/components/serial_plotter.cpp
new file mode 100644
index 00000000..bdd1fb38
--- /dev/null
+++ b/lib/arduino/senseshift/arduino/components/serial_plotter.cpp
@@ -0,0 +1,28 @@
+#include "senseshift/arduino/components/serial_plotter.hpp"
+
+namespace SenseShift::Arduino {
+    struct PlaneVisitor {
+        const SenseShift::Body::Haptics::Target_t target;
+        HardwareSerial* serial;
+
+        void operator()(const SenseShift::Body::Haptics::VibroPlane* plane) const
+        {
+            for (const auto& [position, state] : *(plane->getActuatorStates())) {
+                this->serial->printf("Output[%u][%ux%u]:%u, ", this->target, position.x, position.y, state.intensity);
+            }
+        }
+    };
+
+    template<typename _Tp>
+    void SerialPlotter_OutputStates<_Tp>::run()
+    {
+        while (true) {
+            for (const auto& [target, plane] : *output->getTargets()) {
+                std::visit(PlaneVisitor{ target, this->serial }, plane);
+            }
+            this->serial->println();
+
+            delay(this->sampleRate);
+        }
+    }
+} // namespace SenseShift::Arduino
diff --git a/lib/arduino/components/serial_plotter.hpp b/lib/arduino/senseshift/arduino/components/serial_plotter.hpp
similarity index 56%
rename from lib/arduino/components/serial_plotter.hpp
rename to lib/arduino/senseshift/arduino/components/serial_plotter.hpp
index b2b9b085..02e21b36 100644
--- a/lib/arduino/components/serial_plotter.hpp
+++ b/lib/arduino/senseshift/arduino/components/serial_plotter.hpp
@@ -9,19 +9,23 @@
 #define SERIAL_PLOTTER_BAUD 115200
 #endif // SERIAL_PLOTTER_BAUD
 
-namespace OH {
+namespace SenseShift::Arduino {
     /**
      * Component, that prints the current state of the output to the serial port in Arduino's Serial Plotter format
      *
      * @tparam _Tp the type of the serial port
      */
     template<class _Tp>
-    class SerialPlotter_OutputStates : public Task<SerialPlotter_OutputStates<_Tp>> {
-        friend class Task<SerialPlotter_OutputStates<_Tp>>;
+    class SerialPlotter_OutputStates : public OH::Task<SerialPlotter_OutputStates<_Tp>> {
+        static_assert(
+          std::is_base_of<Print, _Tp>::value,
+          "SerialPlotter_OutputStates only can be used with types, that inherit from Print"
+        );
+        friend class OH::Task<SerialPlotter_OutputStates<_Tp>>;
 
       private:
         _Tp* serial;
-        SenseShift::Body::Haptics::HapticBody* output;
+        ::SenseShift::Body::Haptics::HapticBody* output;
         uint32_t sampleRate;
 
         void setup(void){};
@@ -30,12 +34,15 @@ namespace OH {
       public:
         SerialPlotter_OutputStates(
           _Tp& serial,
-          SenseShift::Body::Haptics::HapticBody* output,
+          ::SenseShift::Body::Haptics::HapticBody* output,
           uint32_t sampleRate,
-          TaskConfig taskConfig = { "Serial Plotter", 2048, 1, tskNO_AFFINITY }
+          OH::TaskConfig taskConfig = { "Serial Plotter", 2048, 1, tskNO_AFFINITY }
         ) :
-          Task<SerialPlotter_OutputStates<_Tp>>(taskConfig), serial(&serial), output(output), sampleRate(sampleRate){};
-        SerialPlotter_OutputStates(_Tp& serial, SenseShift::Body::Haptics::HapticBody* output) :
+          OH::Task<SerialPlotter_OutputStates<_Tp>>(taskConfig),
+          serial(&serial),
+          output(output),
+          sampleRate(sampleRate){};
+        SerialPlotter_OutputStates(_Tp& serial, ::SenseShift::Body::Haptics::HapticBody* output) :
           SerialPlotter_OutputStates(serial, output, 100){};
 
         void begin() override
@@ -55,4 +62,4 @@ namespace OH {
     }
 
     template class SerialPlotter_OutputStates<HardwareSerial>;
-} // namespace OH
+} // namespace SenseShift::Arduino
diff --git a/lib/opengloves/og_protocol.hpp b/lib/opengloves/og_protocol.hpp
index 3d601656..05e28174 100644
--- a/lib/opengloves/og_protocol.hpp
+++ b/lib/opengloves/og_protocol.hpp
@@ -1,5 +1,9 @@
 #pragma once
 
+#include <cstdint>
+#include <functional>
+#include <vector>
+
 namespace OpenGloves {
     struct IEncodedInput {
       public:
diff --git a/platformio.ini b/platformio.ini
index 6d9b15a4..e626fcaa 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -25,7 +25,7 @@ build_flags       =
 	-D CORE_DEBUG_LEVEL=3
 ;	-D DEBUG_MODE=0
 ;	-D DEBUG_ESP_PORT=Serial
-;	-D SERIAL_PLOTTER=true
+;	-D SENSESHIFT_SERIAL_PLOTTER=true
 ;	-D SENSESHIFT_BATTERY_ENABLED=true
 	-D BLUETOOTH_USE_NIMBLE=true
 
diff --git a/test/test_calibration/main.cpp b/test/test_calibration/main.cpp
index 3455463c..a79a8bc6 100644
--- a/test/test_calibration/main.cpp
+++ b/test/test_calibration/main.cpp
@@ -1,7 +1,7 @@
 #include <senseshift/calibration.hpp>
 #include <unity.h>
 
-using namespace OH;
+using namespace SenseShift::Calibration;
 
 void test_minmax_calibrator(void)
 {
diff --git a/test/test_opengloves_finger/main.cpp b/test/test_opengloves_finger/main.cpp
index 0a3a9978..019d8b5c 100644
--- a/test/test_opengloves_finger/main.cpp
+++ b/test/test_opengloves_finger/main.cpp
@@ -2,6 +2,7 @@
 #include <unity.h>
 
 using namespace OpenGloves;
+using namespace SenseShift::Calibration;
 
 class TestAnalogSensor : public SenseShift::Input::ISensor<uint16_t> {
   private:
@@ -21,7 +22,7 @@ class TestAnalogSensor : public SenseShift::Input::ISensor<uint16_t> {
     };
 };
 
-class DummyCalibrator : public OH::ICalibrator<uint16_t> {
+class DummyCalibrator : public ICalibrator<uint16_t> {
   public:
     uint8_t resetCounter = 0;
     std::optional<uint16_t> calibrated = std::nullopt;
diff --git a/test/test_sensor/main.cpp b/test/test_sensor/main.cpp
index 7309e65c..02fe6e86 100644
--- a/test/test_sensor/main.cpp
+++ b/test/test_sensor/main.cpp
@@ -2,7 +2,7 @@
 #include <unity.h>
 
 using namespace SenseShift::Input;
-using namespace OH;
+using namespace SenseShift::Calibration;
 
 class TestAnalogSensor : public ISensor<int> {
   private:

From 4f818909f6da6a6e0cb21227440018d58bf46525 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Tue, 22 Aug 2023 10:17:49 +0400
Subject: [PATCH 15/82] refactor(BLE): rename global NimBLE flag

---
 .github/scripts/get_firmware_name.sh              | 2 +-
 .github/workflows/ci.yml                          | 8 ++++----
 .github/workflows/release.yml                     | 2 +-
 include/config/bluetooth.h                        | 4 ++--
 lib/bhaptics_ble/senseshift/bh/ble/connection.cpp | 8 ++++----
 lib/bhaptics_ble/senseshift/bh/ble/connection.hpp | 2 +-
 platformio.ini                                    | 2 +-
 7 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/.github/scripts/get_firmware_name.sh b/.github/scripts/get_firmware_name.sh
index 75546a23..d7c15d7a 100755
--- a/.github/scripts/get_firmware_name.sh
+++ b/.github/scripts/get_firmware_name.sh
@@ -8,7 +8,7 @@ getBhapticsName() {
     echo "::debug::Getting bHaptics name for $target"
     echo "::debug::Flags are $flags"
 
-    if [[ $flags =~ BLUETOOTH_USE_NIMBLE=true ]]; then
+    if [[ $flags =~ SENSESHIFT_BLE_USE_NIMBLE=true ]]; then
         echo "::debug::Nimble is enabled, appending +nimble to the target"
         target="$target+nimble"
     fi
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 66ab3dbd..5a0bf078 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -34,7 +34,7 @@ jobs:
           - bhaptics_tactglove_right
         battery_flag: [ SENSESHIFT_BATTERY_ENABLED=true ]
         serial_plotter_flag: [ SENSESHIFT_SERIAL_PLOTTER=false ]
-        nimble_flag: [ BLUETOOTH_USE_NIMBLE=false ]
+        nimble_flag: [ SENSESHIFT_BLE_USE_NIMBLE=false ]
         coverage: [ false ]
 
         include:
@@ -44,19 +44,19 @@ jobs:
             coverage: true
             battery_flag: SENSESHIFT_BATTERY_ENABLED=true
             serial_plotter_flag: SENSESHIFT_SERIAL_PLOTTER=true
-            nimble_flag: BLUETOOTH_USE_NIMBLE=true
+            nimble_flag: SENSESHIFT_BLE_USE_NIMBLE=true
           # - target: bhaptics_tactsuit_x40
           #   os: ubuntu-latest
           #   coverage: true
           #   battery_flag: SENSESHIFT_BATTERY_ENABLED=true
           #   serial_plotter_flag: SENSESHIFT_SERIAL_PLOTTER=true
-          #   nimble_flag: BLUETOOTH_USE_NIMBLE=false
+          #   nimble_flag: SENSESHIFT_BLE_USE_NIMBLE=false
           - target: bhaptics_tactsuit_x40
             os: ubuntu-latest
             coverage: false
             battery_flag: SENSESHIFT_BATTERY_ENABLED=true
             serial_plotter_flag: SENSESHIFT_SERIAL_PLOTTER=false
-            nimble_flag: BLUETOOTH_USE_NIMBLE=true
+            nimble_flag: SENSESHIFT_BLE_USE_NIMBLE=true
 
     steps:
       - uses: actions/checkout@v3
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 9b11b4a5..e4e216ef 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -35,7 +35,7 @@ jobs:
         battery_flag:
           - SENSESHIFT_BATTERY_ENABLED=true
         nimble_flag:
-          - BLUETOOTH_USE_NIMBLE=true
+          - SENSESHIFT_BLE_USE_NIMBLE=true
 
     steps:
       - uses: actions/checkout@v3
diff --git a/include/config/bluetooth.h b/include/config/bluetooth.h
index b12cff34..3de034d4 100644
--- a/include/config/bluetooth.h
+++ b/include/config/bluetooth.h
@@ -11,6 +11,6 @@
     }
 #endif
 
-#ifndef BLUETOOTH_USE_NIMBLE
-#define BLUETOOTH_USE_NIMBLE true
+#ifndef SENSESHIFT_BLE_USE_NIMBLE
+#define SENSESHIFT_BLE_USE_NIMBLE false
 #endif
diff --git a/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp b/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
index fd16ec75..7d200d5d 100644
--- a/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
+++ b/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
@@ -6,7 +6,7 @@
 
 #include <Arduino.h>
 
-#if defined(BLUETOOTH_USE_NIMBLE) && BLUETOOTH_USE_NIMBLE == true
+#if defined(SENSESHIFT_BLE_USE_NIMBLE) && SENSESHIFT_BLE_USE_NIMBLE == true
 // BLE2902 not needed: https://github.com/h2zero/NimBLE-Arduino/blob/release/1.4/docs/Migration_guide.md#descriptors
 
 #define PROPERTY_READ NIMBLE_PROPERTY::READ
@@ -77,7 +77,7 @@ namespace SenseShift::BH::BLE {
             );
         };
 
-#if defined(BLUETOOTH_USE_NIMBLE) && BLUETOOTH_USE_NIMBLE == true
+#if defined(SENSESHIFT_BLE_USE_NIMBLE) && SENSESHIFT_BLE_USE_NIMBLE == true
         void onStatus(BLECharacteristic* pCharacteristic, Status s, int code) override
 #else
         void onStatus(BLECharacteristic* pCharacteristic, Status s, uint32_t code) override
@@ -180,7 +180,7 @@ namespace SenseShift::BH::BLE {
                 | PROPERTY_NOTIFY // for whatever reason, it have to be writable, otherwise Desktop app crashes
             );
 
-#if !defined(BLUETOOTH_USE_NIMBLE) || BLUETOOTH_USE_NIMBLE != true
+#if !defined(SENSESHIFT_BLE_USE_NIMBLE) || SENSESHIFT_BLE_USE_NIMBLE != true
             batteryChar->addDescriptor(new BLE2902());
 #endif
 
@@ -207,7 +207,7 @@ namespace SenseShift::BH::BLE {
             );
             monitorChar->setCallbacks(new LogOutputCharCallbacks());
 
-#if !defined(BLUETOOTH_USE_NIMBLE) || BLUETOOTH_USE_NIMBLE != true
+#if !defined(SENSESHIFT_BLE_USE_NIMBLE) || SENSESHIFT_BLE_USE_NIMBLE != true
             monitorChar->addDescriptor(new BLE2902());
 #endif
 
diff --git a/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp b/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
index 49068f52..10dd4643 100644
--- a/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
+++ b/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
@@ -9,7 +9,7 @@
 #include <Arduino.h>
 #include <esp_wifi.h>
 
-#if defined(BLUETOOTH_USE_NIMBLE) && BLUETOOTH_USE_NIMBLE == true
+#if defined(SENSESHIFT_BLE_USE_NIMBLE) && SENSESHIFT_BLE_USE_NIMBLE == true
 #include <NimBLEDevice.h>
 #else
 #include <BLEDevice.h>
diff --git a/platformio.ini b/platformio.ini
index e626fcaa..1a6bb8c3 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -27,7 +27,7 @@ build_flags       =
 ;	-D DEBUG_ESP_PORT=Serial
 ;	-D SENSESHIFT_SERIAL_PLOTTER=true
 ;	-D SENSESHIFT_BATTERY_ENABLED=true
-	-D BLUETOOTH_USE_NIMBLE=true
+;	-D SENSESHIFT_BLE_USE_NIMBLE=true
 
 build_src_filter  =
 	+<*>

From 1e635bc033850aaea778f76d0282be76ad31cdab Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Tue, 22 Aug 2023 10:30:55 +0400
Subject: [PATCH 16/82] format(clang): inline short functions

---
 .clang-format                                 |   4 +-
 examples/bhaptics-ble-bt-serial.cpp           |   5 +-
 firmware/mode_configs/test.cpp                |   5 +-
 include/senseshift.h                          |   5 +-
 .../arduino/input/sensor/analog.hpp           |   5 +-
 .../arduino/input/sensor/digital.hpp          |   5 +-
 lib/battery/senseshift/battery/sensor.hpp     |   5 +-
 lib/bhaptics/senseshift/bh/devices.hpp        | 189 +++++++++---------
 .../senseshift/bh/ble/connection.hpp          |   5 +-
 lib/calibration/senseshift/calibration.hpp    |  10 +-
 lib/freertos/task.hpp                         |   5 +-
 lib/haptics/haptic_body.hpp                   |   5 +-
 lib/haptics/haptic_plane.hpp                  |  15 +-
 lib/haptics/haptics_interface.hpp             |   5 +-
 .../senseshift/arduino/battery/ina219.hpp     |   5 +-
 lib/math/senseshift/math/point2.hpp           |  15 +-
 lib/opengloves/og_protocol.hpp                |   5 +-
 lib/opengloves/sensor/og_finger.hpp           |  25 +--
 lib/opengloves/sensor/og_gesture.hpp          |   5 +-
 lib/opengloves/sensor/og_sensor.hpp           |  10 +-
 .../og_serial_communication.hpp               |   5 +-
 lib/sensor/senseshift/input/sensor.hpp        |  30 +--
 .../senseshift/input/sensor/joystick.hpp      |   5 +-
 test/test_bhaptics_encoding/main.cpp          |  10 +-
 test/test_haptics_body/main.cpp               |  10 +-
 test/test_haptics_plane/main.cpp              |  10 +-
 test/test_opengloves/main.cpp                 |  32 +--
 test/test_opengloves_finger/main.cpp          |  20 +-
 test/test_opengloves_gesture/main.cpp         |   5 +-
 test/test_sensor/main.cpp                     |  20 +-
 30 files changed, 155 insertions(+), 325 deletions(-)

diff --git a/.clang-format b/.clang-format
index 8972cb76..f1213235 100644
--- a/.clang-format
+++ b/.clang-format
@@ -7,9 +7,7 @@ AllowAllConstructorInitializersOnNextLine: 'true'
 AllowAllParametersOfDeclarationOnNextLine: 'true'
 AllowShortBlocksOnASingleLine: 'false'
 AllowShortCaseLabelsOnASingleLine: 'false'
-# TODO
-# AllowShortFunctionsOnASingleLine: Inline
-AllowShortFunctionsOnASingleLine: Empty
+AllowShortFunctionsOnASingleLine: Inline
 AllowShortIfStatementsOnASingleLine: Never
 AllowShortLambdasOnASingleLine: Empty
 AllowShortLoopsOnASingleLine: 'false'
diff --git a/examples/bhaptics-ble-bt-serial.cpp b/examples/bhaptics-ble-bt-serial.cpp
index d0c42d95..8adb23d3 100644
--- a/examples/bhaptics-ble-bt-serial.cpp
+++ b/examples/bhaptics-ble-bt-serial.cpp
@@ -27,10 +27,7 @@ static const ::SenseShift::Body::Haptics::Position_t* bhLayout[bhLayoutSize] = B
 
 class BLECallbacks : public BHBLEConnectionCallbacks {
   public:
-    void postInit()
-    {
-        btSerial->begin("SenseShift Serial");
-    }
+    void postInit() { btSerial->begin("SenseShift Serial"); }
 };
 
 void setupMode()
diff --git a/firmware/mode_configs/test.cpp b/firmware/mode_configs/test.cpp
index b6592e1b..943a38a2 100644
--- a/firmware/mode_configs/test.cpp
+++ b/firmware/mode_configs/test.cpp
@@ -22,10 +22,7 @@ class TestOutput : public OH::AbstractActuator {
 
   public:
     TestOutput(uint8_t channel) : channel(channel){};
-    uint8_t getChannel()
-    {
-        return channel;
-    };
+    uint8_t getChannel() { return channel; };
     void writeOutput(oh_output_intensity_t intensity){};
 };
 
diff --git a/include/senseshift.h b/include/senseshift.h
index d48c99bb..27a3c3e9 100644
--- a/include/senseshift.h
+++ b/include/senseshift.h
@@ -18,10 +18,7 @@ namespace SenseShift {
       public:
         SenseShift();
 
-        Body::Haptics::HapticBody* getHapticBody()
-        {
-            return this->pHapticBody;
-        };
+        Body::Haptics::HapticBody* getHapticBody() { return this->pHapticBody; };
 
         void postEvent(const IEvent* event) override;
         void addEventListener(const IEventListener* listener) override;
diff --git a/lib/arduino/senseshift/arduino/input/sensor/analog.hpp b/lib/arduino/senseshift/arduino/input/sensor/analog.hpp
index d55fc37e..4d1d5705 100644
--- a/lib/arduino/senseshift/arduino/input/sensor/analog.hpp
+++ b/lib/arduino/senseshift/arduino/input/sensor/analog.hpp
@@ -13,10 +13,7 @@ namespace SenseShift::Arduino::Input {
       public:
         AnalogSensor(uint8_t pin) : pin(pin) {}
 
-        void setup(void)
-        {
-            pinMode(this->pin, INPUT);
-        };
+        void setup(void) { pinMode(this->pin, INPUT); };
 
         uint16_t getValue(void) override;
     };
diff --git a/lib/arduino/senseshift/arduino/input/sensor/digital.hpp b/lib/arduino/senseshift/arduino/input/sensor/digital.hpp
index 5a9c2f63..16de1b6c 100644
--- a/lib/arduino/senseshift/arduino/input/sensor/digital.hpp
+++ b/lib/arduino/senseshift/arduino/input/sensor/digital.hpp
@@ -15,10 +15,7 @@ namespace SenseShift::Arduino::Input {
       public:
         DigitalSensor(uint8_t pin) : pin(pin) {}
 
-        void setup(void)
-        {
-            pinMode(this->pin, INPUT_PULLUP);
-        };
+        void setup(void) { pinMode(this->pin, INPUT_PULLUP); };
 
         bool getValue(void) override;
     };
diff --git a/lib/battery/senseshift/battery/sensor.hpp b/lib/battery/senseshift/battery/sensor.hpp
index bd9106d7..06a5f5b6 100644
--- a/lib/battery/senseshift/battery/sensor.hpp
+++ b/lib/battery/senseshift/battery/sensor.hpp
@@ -85,10 +85,7 @@ namespace SenseShift::Battery {
             return { .level =
                        static_cast<uint8_t>(::SenseShift::simpleMap<uint16_t>(this->sensor->getValue(), 4095, 255)) };
         };
-        void setup()
-        {
-            this->sensor->setup();
-        }
+        void setup() { this->sensor->setup(); }
 
       private:
         ::SenseShift::Input::ISensor<uint16_t>* sensor;
diff --git a/lib/bhaptics/senseshift/bh/devices.hpp b/lib/bhaptics/senseshift/bh/devices.hpp
index d3c24d1b..60cac07c 100644
--- a/lib/bhaptics/senseshift/bh/devices.hpp
+++ b/lib/bhaptics/senseshift/bh/devices.hpp
@@ -18,20 +18,20 @@
 // X * Y for front and back
 #define BH_LAYOUT_TACTSUITX40_SIZE 40
 // clang-format off
-#define BH_LAYOUT_TACTSUITX40 {                       \
-    /* Front, left part */                            \
-    /*  0 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 0) },  \
-    /*  1 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 0) },  \
-    /*  2 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 1) },  \
-    /*  3 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 1) },  \
-    /*  4 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 2) },  \
-    /*  5 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 2) },  \
-    /*  6 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 3) },  \
-    /*  7 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 3) },  \
-    /*  8 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 4) },  \
-    /*  9 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 4) },  \
-                                                      \
-    /* Back */                                        \
+#define BH_LAYOUT_TACTSUITX40 {                                                \
+    /* Front, left part */                                                     \
+    /*  0 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 0) }, \
+    /*  1 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 0) }, \
+    /*  2 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 1) }, \
+    /*  3 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 1) }, \
+    /*  4 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 2) }, \
+    /*  5 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 2) }, \
+    /*  6 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 3) }, \
+    /*  7 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 3) }, \
+    /*  8 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 4) }, \
+    /*  9 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 4) }, \
+                                                                               \
+    /* Back */                                                                 \
     /* 11 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 0) },  \
     /* 11 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 0) },  \
     /* 12 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 1) },  \
@@ -42,7 +42,7 @@
     /* 17 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 3) },  \
     /* 18 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 4) },  \
     /* 19 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 4) },  \
-                                                      \
+                                                                               \
     /* 20 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 0) },  \
     /* 21 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 0) },  \
     /* 22 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 1) },  \
@@ -53,18 +53,18 @@
     /* 27 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 3) },  \
     /* 28 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 4) },  \
     /* 29 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 4) },  \
-                                                      \
-    /* Front, again... Now right part */              \
-    /* 30 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 0) },  \
-    /* 31 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 0) },  \
-    /* 32 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 1) },  \
-    /* 33 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 1) },  \
-    /* 34 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 2) },  \
-    /* 35 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 2) },  \
-    /* 36 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 3) },  \
-    /* 37 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 3) },  \
-    /* 38 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 4) },  \
-    /* 39 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 4) }   \
+                                                                               \
+    /* Front, again... Now right part */                                       \
+    /* 30 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 0) }, \
+    /* 31 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 0) }, \
+    /* 32 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 1) }, \
+    /* 33 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 1) }, \
+    /* 34 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 2) }, \
+    /* 35 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 2) }, \
+    /* 36 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 3) }, \
+    /* 37 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 3) }, \
+    /* 38 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 4) }, \
+    /* 39 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 4) }  \
 }
 // clang-format on
 
@@ -85,57 +85,57 @@
 // X16 suit uses the same packets structure as x40 suit and performs motor grouping in firmware
 #define BH_LAYOUT_TACTSUITX16_SIZE 40
 // clang-format off
-#define BH_LAYOUT_TACTSUITX16 {                                 \
-    /* Front, left part */                                      \
-    /*  0 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0) },  /*  0 */  \
-    /*  1 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0) },  /*  1 */  \
-    /*  2 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0) },  /*  4 */  \
-    /*  3 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0) },  /*  5 */  \
-                                                                \
-    /*  4 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) },  /*  8 */  \
-    /*  5 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) },  /*  9 */  \
-    /*  6 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) },  /* 12 */  \
-    /*  7 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) },  /* 13 */  \
-    /*  8 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) },  /* 16 */  \
-    /*  9 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) },  /* 17 */  \
-                                                                \
-    /* Back */                                                  \
-    /* 10 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0) },  /*  0 */  \
-    /* 11 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0) },  /*  1 */  \
-    /* 12 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0) },  /*  4 */  \
-    /* 13 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0) },  /*  5 */  \
-                                                                \
-    /* 14 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) },  /*  8 */  \
-    /* 15 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) },  /*  9 */  \
-    /* 16 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) },  /* 12 */  \
-    /* 17 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) },  /* 13 */  \
-    /* 18 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) },  /* 16 */  \
-    /* 19 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) },  /* 17 */  \
-                                                                \
-    /* 20 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0) },  /*  2 */  \
-    /* 21 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0) },  /*  3 */  \
-    /* 22 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0) },  /*  4 */  \
-    /* 23 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0) },  /*  7 */  \
-                                                                \
-    /* 24 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) },  /* 10 */  \
-    /* 25 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) },  /* 11 */  \
-    /* 26 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) },  /* 14 */  \
-    /* 27 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) },  /* 15 */  \
-    /* 28 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) },  /* 18 */  \
-    /* 29 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) },  /* 19 */  \
-                                                                \
-    /* Front, again... Now right part */                        \
-    /* 30 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0) },  /*  2 */  \
-    /* 31 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0) },  /*  3 */  \
-    /* 32 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0) },  /*  4 */  \
-    /* 33 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0) },  /*  7 */  \
-                                                                \
-    /* 34 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) },  /* 10 */  \
-    /* 35 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) },  /* 11 */  \
-    /* 36 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) },  /* 14 */  \
-    /* 37 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) },  /* 15 */  \
-    /* 38 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) },  /* 18 */  \
-    /* 39 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) },  /* 19 */  \
+#define BH_LAYOUT_TACTSUITX16 {                                                         \
+    /* Front, left part */                                                              \
+    /*  0 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0) }, /*  0 */ \
+    /*  1 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0) }, /*  1 */ \
+    /*  2 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0) }, /*  4 */ \
+    /*  3 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0) }, /*  5 */ \
+                                                                                        \
+    /*  4 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) }, /*  8 */ \
+    /*  5 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) }, /*  9 */ \
+    /*  6 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) }, /* 12 */ \
+    /*  7 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) }, /* 13 */ \
+    /*  8 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) }, /* 16 */ \
+    /*  9 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) }, /* 17 */ \
+                                                                                        \
+    /* Back */                                                                          \
+    /* 10 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0) }, /*  0 */  \
+    /* 11 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0) }, /*  1 */  \
+    /* 12 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0) }, /*  4 */  \
+    /* 13 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0) }, /*  5 */  \
+                                                                                        \
+    /* 14 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) }, /*  8 */  \
+    /* 15 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) }, /*  9 */  \
+    /* 16 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) }, /* 12 */  \
+    /* 17 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) }, /* 13 */  \
+    /* 18 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) }, /* 16 */  \
+    /* 19 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) }, /* 17 */  \
+                                                                                        \
+    /* 20 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0) }, /*  2 */  \
+    /* 21 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0) }, /*  3 */  \
+    /* 22 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0) }, /*  4 */  \
+    /* 23 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0) }, /*  7 */  \
+                                                                                        \
+    /* 24 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) }, /* 10 */  \
+    /* 25 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) }, /* 11 */  \
+    /* 26 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) }, /* 14 */  \
+    /* 27 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) }, /* 15 */  \
+    /* 28 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) }, /* 18 */  \
+    /* 29 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) }, /* 19 */  \
+                                                                                        \
+    /* Front, again... Now right part */                                                \
+    /* 30 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0) }, /*  2 */ \
+    /* 31 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0) }, /*  3 */ \
+    /* 32 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0) }, /*  4 */ \
+    /* 33 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0) }, /*  7 */ \
+                                                                                        \
+    /* 34 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) }, /* 10 */ \
+    /* 35 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) }, /* 11 */ \
+    /* 36 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) }, /* 14 */ \
+    /* 37 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) }, /* 15 */ \
+    /* 38 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) }, /* 18 */ \
+    /* 39 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) }, /* 19 */ \
 }
 // clang-format on
 
@@ -260,24 +260,27 @@
 
 #pragma region BH_DEVIDE_TACTGLOVE
 
+// clang-format off
 #define BH_LAYOUT_TACTGLOVE_SIZE 6
 // TactGlove (Left) motor positions
-#define BH_LAYOUT_TACTGLOVE_LEFT                                                                                \
-    {                                                                                                           \
-        { Target_t::HandLeftThumb, FINGERTIP_POSITION }, { Target_t::HandLeftIndex, FINGERTIP_POSITION },       \
-          { Target_t::HandLeftMiddle, FINGERTIP_POSITION }, { Target_t::HandLeftRing, FINGERTIP_POSITION },     \
-          { Target_t::HandLeftLittle, FINGERTIP_POSITION }, { Target_t::HandLeftDorsal, WRIST_MOTOR_POSITION }, \
-    }
+#define BH_LAYOUT_TACTGLOVE_LEFT {                      \
+    { Target_t::HandLeftThumb,  FINGERTIP_POSITION },   \
+    { Target_t::HandLeftIndex,  FINGERTIP_POSITION },   \
+    { Target_t::HandLeftMiddle, FINGERTIP_POSITION },   \
+    { Target_t::HandLeftRing,   FINGERTIP_POSITION },   \
+    { Target_t::HandLeftLittle, FINGERTIP_POSITION },   \
+    { Target_t::HandLeftDorsal, WRIST_MOTOR_POSITION }, \
+}
 // TactGlove (Right) motor positions
-#define BH_LAYOUT_TACTGLOVE_RIGHT                                                                             \
-    {                                                                                                         \
-        { Target_t::HandRightThumb, FINGERTIP_POSITION }, { Target_t::HandRightIndex, FINGERTIP_POSITION },   \
-          { Target_t::HandRightMiddle, FINGERTIP_POSITION }, { Target_t::HandRightRing, FINGERTIP_POSITION }, \
-          { Target_t::HandRightLittle, FINGERTIP_POSITION },                                                  \
-        {                                                                                                     \
-            Target_t::HandRightDorsal, WRIST_MOTOR_POSITION                                                   \
-        }                                                                                                     \
-    }
+#define BH_LAYOUT_TACTGLOVE_RIGHT {                     \
+    { Target_t::HandRightThumb,  FINGERTIP_POSITION },  \
+    { Target_t::HandRightIndex,  FINGERTIP_POSITION },  \
+    { Target_t::HandRightMiddle, FINGERTIP_POSITION },  \
+    { Target_t::HandRightRing,   FINGERTIP_POSITION },  \
+    { Target_t::HandRightLittle, FINGERTIP_POSITION },  \
+    { Target_t::HandRightDorsal, WRIST_MOTOR_POSITION } \
+}
+// clang-format on
 
 #pragma endregion BH_DEVIDE_TACTGLOVE
 
diff --git a/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp b/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
index 10dd4643..ea5b1ae6 100644
--- a/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
+++ b/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
@@ -26,10 +26,7 @@ namespace SenseShift::BH::BLE {
 
     class ConnectionCallbacks {
       public:
-        virtual void postInit()
-        {
-            log_v("Default postInit");
-        };
+        virtual void postInit() { log_v("Default postInit"); };
     };
     static ConnectionCallbacks defaultCallback;
 
diff --git a/lib/calibration/senseshift/calibration.hpp b/lib/calibration/senseshift/calibration.hpp
index fb48f060..58c190c0 100644
--- a/lib/calibration/senseshift/calibration.hpp
+++ b/lib/calibration/senseshift/calibration.hpp
@@ -21,15 +21,9 @@ namespace SenseShift::Calibration {
       public:
         virtual void resetCalibration() = 0;
 
-        void enableCalibration() override
-        {
-            calibrate = true;
-        }
+        void enableCalibration() override { calibrate = true; }
 
-        void disableCalibration() override
-        {
-            calibrate = false;
-        }
+        void disableCalibration() override { calibrate = false; }
     };
 
     template<typename _Tp>
diff --git a/lib/freertos/task.hpp b/lib/freertos/task.hpp
index 6a40f33a..e77c7ec4 100644
--- a/lib/freertos/task.hpp
+++ b/lib/freertos/task.hpp
@@ -58,10 +58,7 @@ namespace OH {
         Task(TaskConfig config) : taskConfig(config){};
         virtual ~Task(){};
 
-        TaskHandle_t getHandle() const
-        {
-            return taskHandle;
-        };
+        TaskHandle_t getHandle() const { return taskHandle; };
 
         virtual void begin()
         {
diff --git a/lib/haptics/haptic_body.hpp b/lib/haptics/haptic_body.hpp
index ff05a1e4..d0e536cd 100644
--- a/lib/haptics/haptic_body.hpp
+++ b/lib/haptics/haptic_body.hpp
@@ -22,10 +22,7 @@ namespace SenseShift::Body::Haptics {
 
         void addTarget(const Target_t, VibroPlane* plane);
 
-        [[nodiscard]] const PlaneTargetMap_t* getTargets() const
-        {
-            return &allTargets;
-        }
+        [[nodiscard]] const PlaneTargetMap_t* getTargets() const { return &allTargets; }
 
       private:
         PlaneTargetMap_t allTargets{};
diff --git a/lib/haptics/haptic_plane.hpp b/lib/haptics/haptic_plane.hpp
index 4441b03a..e382e01a 100644
--- a/lib/haptics/haptic_plane.hpp
+++ b/lib/haptics/haptic_plane.hpp
@@ -31,22 +31,13 @@ namespace SenseShift::Body::Haptics {
 
         ActuativePlane() = default;
 
-        ActuativePlane(const ActuatorMap_t& actuators)
-        {
-            this->setActuators(actuators);
-        }
+        ActuativePlane(const ActuatorMap_t& actuators) { this->setActuators(actuators); }
 
         void setup();
         virtual void effect(const Position_t&, const Value_t&);
 
-        [[nodiscard]] const PositionSet_t* getAvailablePoints() const
-        {
-            return &points;
-        }
-        [[nodiscard]] const PositionStateMap_t* getActuatorStates() const
-        {
-            return &states;
-        }
+        [[nodiscard]] const PositionSet_t* getAvailablePoints() const { return &points; }
+        [[nodiscard]] const PositionStateMap_t* getActuatorStates() const { return &states; }
 
       private:
         PositionSet_t points;
diff --git a/lib/haptics/haptics_interface.hpp b/lib/haptics/haptics_interface.hpp
index 5b5a13bf..506f0928 100644
--- a/lib/haptics/haptics_interface.hpp
+++ b/lib/haptics/haptics_interface.hpp
@@ -61,10 +61,7 @@ namespace SenseShift::Body::Haptics {
         inline constexpr VibroEffectData(const Intensity_t intensity) : intensity(intensity) {}
         inline constexpr VibroEffectData(const VibroEffectData& other) = default;
 
-        inline constexpr operator uint16_t() const
-        {
-            return intensity;
-        }
+        inline constexpr operator uint16_t() const { return intensity; }
     } VibroEffectData_t;
 
     // TODO: thermal, etc.
diff --git a/lib/ina219/senseshift/arduino/battery/ina219.hpp b/lib/ina219/senseshift/arduino/battery/ina219.hpp
index 0d3487bf..ec2dea00 100644
--- a/lib/ina219/senseshift/arduino/battery/ina219.hpp
+++ b/lib/ina219/senseshift/arduino/battery/ina219.hpp
@@ -13,10 +13,7 @@ namespace SenseShift::Arduino::Battery {
       public:
         INA219_Battery(Adafruit_INA219* sensor) : sensor(sensor){};
 
-        void setup() override
-        {
-            this->active = this->sensor->begin();
-        }
+        void setup() override { this->active = this->sensor->begin(); }
         ::SenseShift::Battery::BatteryState getValue() override
         {
             if (!this->active) {
diff --git a/lib/math/senseshift/math/point2.hpp b/lib/math/senseshift/math/point2.hpp
index 6f8131d5..001d16bf 100644
--- a/lib/math/senseshift/math/point2.hpp
+++ b/lib/math/senseshift/math/point2.hpp
@@ -21,20 +21,11 @@ namespace SenseShift::Math {
         constexpr Point2(_Tp x, _Tp y) : x(x), y(y){};
         constexpr Point2(const Point2<_Tp>& v) : x((_Tp) v.x), y((_Tp) v.y){};
 
-        constexpr inline bool operator==(const Point2<_Tp>& rhs) const
-        {
-            return x == rhs.x && y == rhs.y;
-        }
+        constexpr inline bool operator==(const Point2<_Tp>& rhs) const { return x == rhs.x && y == rhs.y; }
 
-        constexpr inline bool operator!=(const Point2<_Tp>& rhs) const
-        {
-            return !(*this == rhs);
-        }
+        constexpr inline bool operator!=(const Point2<_Tp>& rhs) const { return !(*this == rhs); }
 
-        constexpr bool operator<(const Point2<_Tp>& rhs) const
-        {
-            return std::tie(x, y) < std::tie(rhs.x, rhs.y);
-        }
+        constexpr bool operator<(const Point2<_Tp>& rhs) const { return std::tie(x, y) < std::tie(rhs.x, rhs.y); }
 
         constexpr float operator-(const Point2<_Tp>& rhs) const
         {
diff --git a/lib/opengloves/og_protocol.hpp b/lib/opengloves/og_protocol.hpp
index 05e28174..9c92e98f 100644
--- a/lib/opengloves/og_protocol.hpp
+++ b/lib/opengloves/og_protocol.hpp
@@ -27,10 +27,7 @@ namespace OpenGloves {
 
         IEncodedInput(Type type) : type(type){};
 
-        constexpr Type getType() const
-        {
-            return this->type;
-        }
+        constexpr Type getType() const { return this->type; }
 
       protected:
         Type type;
diff --git a/lib/opengloves/sensor/og_finger.hpp b/lib/opengloves/sensor/og_finger.hpp
index 62f4b244..9b5193d3 100644
--- a/lib/opengloves/sensor/og_finger.hpp
+++ b/lib/opengloves/sensor/og_finger.hpp
@@ -68,10 +68,7 @@ namespace OpenGloves {
             return value;
         }
 
-        uint16_t getCurl() override
-        {
-            return this->getValue().getTotalCurl();
-        }
+        uint16_t getCurl() override { return this->getValue().getTotalCurl(); }
 
       protected:
         FingerSensors sensors;
@@ -139,25 +136,13 @@ namespace OpenGloves {
         FingerSensor(SenseShift::Input::CalibratedSensor<uint16_t>* curl1, IEncodedInput::Type type) :
           StringEncodedMemoizedSensor<FingerValue>(new CalibratedFingerSensor(curl1, std::nullopt), type){};
 
-        void resetCalibration() override
-        {
-            static_cast<CalibratedFingerSensor*>(this->sensor)->resetCalibration();
-        }
+        void resetCalibration() override { static_cast<CalibratedFingerSensor*>(this->sensor)->resetCalibration(); }
 
-        void enableCalibration() override
-        {
-            static_cast<CalibratedFingerSensor*>(this->sensor)->enableCalibration();
-        }
+        void enableCalibration() override { static_cast<CalibratedFingerSensor*>(this->sensor)->enableCalibration(); }
 
-        void disableCalibration() override
-        {
-            static_cast<CalibratedFingerSensor*>(this->sensor)->disableCalibration();
-        }
+        void disableCalibration() override { static_cast<CalibratedFingerSensor*>(this->sensor)->disableCalibration(); }
 
-        uint16_t getCurl() override
-        {
-            return this->getValue().getTotalCurl();
-        }
+        uint16_t getCurl() override { return this->getValue().getTotalCurl(); }
     };
 
     struct HandSensors {
diff --git a/lib/opengloves/sensor/og_gesture.hpp b/lib/opengloves/sensor/og_gesture.hpp
index 3c7e0944..324cdb30 100644
--- a/lib/opengloves/sensor/og_gesture.hpp
+++ b/lib/opengloves/sensor/og_gesture.hpp
@@ -36,10 +36,7 @@ namespace OpenGloves {
 
         void setup() override{};
 
-        bool getValue() override
-        {
-            return this->index.getCurl() > this->threshold;
-        }
+        bool getValue() override { return this->index.getCurl() > this->threshold; }
     };
 
     class PinchGesture : public Gesture {
diff --git a/lib/opengloves/sensor/og_sensor.hpp b/lib/opengloves/sensor/og_sensor.hpp
index 71c4c439..9fdd4a71 100644
--- a/lib/opengloves/sensor/og_sensor.hpp
+++ b/lib/opengloves/sensor/og_sensor.hpp
@@ -33,15 +33,9 @@ namespace OpenGloves {
         StringEncodedMemoizedSensor(SenseShift::Input::ISensor<_Tp>* sensor, IEncodedInput::Type type) :
           IStringEncodedMemoizedSensor(type), SenseShift::Input::MemoizedSensor<_Tp>(sensor){};
 
-        void setup() override
-        {
-            this->sensor->setup();
-        }
+        void setup() override { this->sensor->setup(); }
 
-        void updateValue() override
-        {
-            this->value = this->sensor->getValue();
-        }
+        void updateValue() override { this->value = this->sensor->getValue(); }
 
         size_t getEncodedLength() const override;
 
diff --git a/lib/opengloves_serial/og_serial_communication.hpp b/lib/opengloves_serial/og_serial_communication.hpp
index 45d89a52..800b70e0 100644
--- a/lib/opengloves_serial/og_serial_communication.hpp
+++ b/lib/opengloves_serial/og_serial_communication.hpp
@@ -70,10 +70,7 @@ namespace OpenGloves {
             this->ready = true;
         }
 
-        bool isReady() override
-        {
-            return this->ready;
-        }
+        bool isReady() override { return this->ready; }
 
         bool hasData() override
         {
diff --git a/lib/sensor/senseshift/input/sensor.hpp b/lib/sensor/senseshift/input/sensor.hpp
index d721257e..8de2a7bb 100644
--- a/lib/sensor/senseshift/input/sensor.hpp
+++ b/lib/sensor/senseshift/input/sensor.hpp
@@ -62,26 +62,17 @@ namespace SenseShift::Input {
         /**
          * Setup the sensor hardware
          */
-        void setup() override
-        {
-            this->sensor->setup();
-        };
+        void setup() override { this->sensor->setup(); };
 
         /**
          * Get the current memoized value
          */
-        _Tp getValue() override
-        {
-            return this->value;
-        };
+        _Tp getValue() override { return this->value; };
 
         /**
          * Read actual value from the hardware and memoize it
          */
-        void updateValue()
-        {
-            this->value = this->sensor->getValue();
-        };
+        void updateValue() { this->value = this->sensor->getValue(); };
     };
 
     /**
@@ -114,19 +105,10 @@ namespace SenseShift::Input {
         CalibratedSensor(ISensor<_Tp>* sensor, ::SenseShift::Calibration::ICalibrator<_Tp>* calibrator) :
           sensor(sensor), calibrator(calibrator){};
 
-        void setup() override
-        {
-            this->sensor->setup();
-        };
+        void setup() override { this->sensor->setup(); };
 
-        _Tp getValue() override
-        {
-            return this->getCalibratedValue();
-        };
+        _Tp getValue() override { return this->getCalibratedValue(); };
 
-        void resetCalibration() override
-        {
-            this->calibrator->reset();
-        };
+        void resetCalibration() override { this->calibrator->reset(); };
     };
 } // namespace SenseShift::Input
diff --git a/lib/sensor/senseshift/input/sensor/joystick.hpp b/lib/sensor/senseshift/input/sensor/joystick.hpp
index 307d49ea..b9d28519 100644
--- a/lib/sensor/senseshift/input/sensor/joystick.hpp
+++ b/lib/sensor/senseshift/input/sensor/joystick.hpp
@@ -24,10 +24,7 @@ namespace SenseShift::Input {
       public:
         JoystickAxisSensor(ISensor<_Tp>* sensor, float dead_zone) : sensor(sensor), dead_zone(dead_zone){};
 
-        void setup(void)
-        {
-            this->sensor->setup();
-        };
+        void setup(void) { this->sensor->setup(); };
 
         uint16_t getValue(void) override
         {
diff --git a/test/test_bhaptics_encoding/main.cpp b/test/test_bhaptics_encoding/main.cpp
index 2de6e52e..03a560be 100644
--- a/test/test_bhaptics_encoding/main.cpp
+++ b/test/test_bhaptics_encoding/main.cpp
@@ -12,14 +12,8 @@ class TestActuator : public IActuator<uint16_t> {
     uint16_t intensity = 0;
 
     TestActuator() : IActuator<uint16_t>() {}
-    void setup() override
-    {
-        this->isSetup = true;
-    }
-    void writeOutput(uint16_t intensity) override
-    {
-        this->intensity = intensity;
-    }
+    void setup() override { this->isSetup = true; }
+    void writeOutput(uint16_t intensity) override { this->intensity = intensity; }
 };
 
 void test_layout_tactsuitx16(void)
diff --git a/test/test_haptics_body/main.cpp b/test/test_haptics_body/main.cpp
index 27b8b56e..19c8c866 100644
--- a/test/test_haptics_body/main.cpp
+++ b/test/test_haptics_body/main.cpp
@@ -10,14 +10,8 @@ class TestActuator : public IActuator<uint16_t> {
     uint16_t intensity = 0;
 
     TestActuator() : IActuator<uint16_t>() {}
-    void setup() override
-    {
-        this->isSetup = true;
-    }
-    void writeOutput(uint16_t intensity) override
-    {
-        this->intensity = intensity;
-    }
+    void setup() override { this->isSetup = true; }
+    void writeOutput(uint16_t intensity) override { this->intensity = intensity; }
 };
 
 void test_it_sets_up_planes(void)
diff --git a/test/test_haptics_plane/main.cpp b/test/test_haptics_plane/main.cpp
index 38e4594b..091ba1c3 100644
--- a/test/test_haptics_plane/main.cpp
+++ b/test/test_haptics_plane/main.cpp
@@ -10,14 +10,8 @@ class TestActuator : public IActuator<uint16_t> {
     uint16_t intensity = 0;
 
     TestActuator() : IActuator<uint16_t>() {}
-    void setup() override
-    {
-        this->isSetup = true;
-    }
-    void writeOutput(uint16_t intensity) override
-    {
-        this->intensity = intensity;
-    }
+    void setup() override { this->isSetup = true; }
+    void writeOutput(uint16_t intensity) override { this->intensity = intensity; }
 };
 
 void test_it_sets_up_actuators(void)
diff --git a/test/test_opengloves/main.cpp b/test/test_opengloves/main.cpp
index 646a9785..c711cdc1 100644
--- a/test/test_opengloves/main.cpp
+++ b/test/test_opengloves/main.cpp
@@ -10,15 +10,9 @@ class TestAnalogSensor : public SenseShift::Input::ISensor<uint16_t> {
   public:
     int setupCounter = 0;
 
-    void setup() override
-    {
-        this->setupCounter++;
-    };
-
-    uint16_t getValue() override
-    {
-        return ++this->count;
-    };
+    void setup() override { this->setupCounter++; };
+
+    uint16_t getValue() override { return ++this->count; };
 };
 
 class TestBinarySensor : public SenseShift::Input::ISensor<bool> {
@@ -26,15 +20,9 @@ class TestBinarySensor : public SenseShift::Input::ISensor<bool> {
     bool value = false;
     int setupCounter = 0;
 
-    void setup() override
-    {
-        this->setupCounter++;
-    };
+    void setup() override { this->setupCounter++; };
 
-    bool getValue() override
-    {
-        return this->value;
-    };
+    bool getValue() override { return this->value; };
 };
 
 class TestFingerSensor : public SenseShift::Input::ISensor<FingerValue> {
@@ -42,15 +30,9 @@ class TestFingerSensor : public SenseShift::Input::ISensor<FingerValue> {
     FingerValue value;
     int setupCounter = 0;
 
-    void setup() override
-    {
-        this->setupCounter++;
-    };
+    void setup() override { this->setupCounter++; };
 
-    FingerValue getValue() override
-    {
-        return this->value;
-    };
+    FingerValue getValue() override { return this->value; };
 };
 
 void test_string_encoded_sensor_uint16(void)
diff --git a/test/test_opengloves_finger/main.cpp b/test/test_opengloves_finger/main.cpp
index 019d8b5c..ee063582 100644
--- a/test/test_opengloves_finger/main.cpp
+++ b/test/test_opengloves_finger/main.cpp
@@ -11,15 +11,9 @@ class TestAnalogSensor : public SenseShift::Input::ISensor<uint16_t> {
   public:
     int setupCounter = 0;
 
-    void setup() override
-    {
-        this->setupCounter++;
-    };
+    void setup() override { this->setupCounter++; };
 
-    uint16_t getValue() override
-    {
-        return this->count++;
-    };
+    uint16_t getValue() override { return this->count++; };
 };
 
 class DummyCalibrator : public ICalibrator<uint16_t> {
@@ -32,14 +26,8 @@ class DummyCalibrator : public ICalibrator<uint16_t> {
         this->resetCounter++;
         this->calibrated = std::nullopt;
     };
-    void update(uint16_t input) override
-    {
-        this->calibrated = input;
-    };
-    uint16_t calibrate(uint16_t input) const override
-    {
-        return this->calibrated.value_or(input);
-    };
+    void update(uint16_t input) override { this->calibrated = input; };
+    uint16_t calibrate(uint16_t input) const override { return this->calibrated.value_or(input); };
 };
 
 void test_simple_finger_sensor_curl(void)
diff --git a/test/test_opengloves_gesture/main.cpp b/test/test_opengloves_gesture/main.cpp
index 6ee8b066..0d89a11a 100644
--- a/test/test_opengloves_gesture/main.cpp
+++ b/test/test_opengloves_gesture/main.cpp
@@ -9,10 +9,7 @@ class TestCurlFinger : public ICurl {
 
     TestCurlFinger(uint16_t initialValue = 0) : value(initialValue){};
 
-    uint16_t getCurl() override
-    {
-        return this->value;
-    }
+    uint16_t getCurl() override { return this->value; }
 };
 
 void test_gesture_grab(void)
diff --git a/test/test_sensor/main.cpp b/test/test_sensor/main.cpp
index 02fe6e86..f9cbcea0 100644
--- a/test/test_sensor/main.cpp
+++ b/test/test_sensor/main.cpp
@@ -11,15 +11,9 @@ class TestAnalogSensor : public ISensor<int> {
   public:
     int setupCounter = 0;
 
-    void setup() override
-    {
-        this->setupCounter++;
-    };
+    void setup() override { this->setupCounter++; };
 
-    int getValue() override
-    {
-        return ++this->count;
-    };
+    int getValue() override { return ++this->count; };
 };
 
 void test_memoized_sensor(void)
@@ -50,14 +44,8 @@ class DummyCalibrator : public ICalibrator<int> {
         this->resetCounter++;
         this->calibrated = 0;
     };
-    void update(int input) override
-    {
-        this->calibrated = input;
-    };
-    int calibrate(int input) const override
-    {
-        return calibrated;
-    };
+    void update(int input) override { this->calibrated = input; };
+    int calibrate(int input) const override { return calibrated; };
 };
 
 void test_calibrated_sensor(void)

From e3c16396516395ebf5e9d50ee8e1669166b15962 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Tue, 22 Aug 2023 10:48:43 +0400
Subject: [PATCH 17/82] fix(BLE): non-NimBLE S/N char

---
 lib/bhaptics_ble/senseshift/bh/ble/connection.cpp | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp b/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
index 7d200d5d..66c91204 100644
--- a/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
+++ b/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
@@ -169,7 +169,9 @@ namespace SenseShift::BH::BLE {
               BH_BLE_SERVICE_MOTOR_CHAR_SERIAL_KEY_UUID,
               PROPERTY_READ | PROPERTY_WRITE
             );
-            serialNumberChar->setValue(this->config.serialNumber, ConnectionConfig_t::SN_LENGTH);
+            uint8_t serialNumber[ConnectionConfig_t::SN_LENGTH];
+            memcpy(serialNumber, this->config.serialNumber, ConnectionConfig_t::SN_LENGTH);
+            serialNumberChar->setValue(serialNumber, ConnectionConfig_t::SN_LENGTH);
             serialNumberChar->setCallbacks(new LogOutputCharCallbacks());
         }
 

From 8a1e2b5c086b7ba2c69ce41dc3d28e3476be89f7 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Tue, 22 Aug 2023 18:45:07 +0400
Subject: [PATCH 18/82] refactor(FreeRTOS): move to `SenseShift` namespace/dir

---
 examples/bhaptics-ble-bt-serial.cpp           |  1 -
 firmware/mode_configs/bhaptics/tactal.cpp     |  1 -
 firmware/mode_configs/bhaptics/tactglove.cpp  |  1 -
 firmware/mode_configs/bhaptics/tactosy2.cpp   |  1 -
 firmware/mode_configs/bhaptics/tactosyf.cpp   |  1 -
 firmware/mode_configs/bhaptics/tactosyh.cpp   |  1 -
 .../mode_configs/bhaptics/tactsuit_x16.cpp    |  1 -
 .../bhaptics/tactsuit_x16_pca9685.cpp         |  1 -
 .../mode_configs/bhaptics/tactsuit_x40.cpp    |  1 -
 firmware/mode_configs/bhaptics/tactvisor.cpp  |  1 -
 firmware/mode_configs/test.cpp                | 42 -------------------
 .../arduino/components/serial_plotter.hpp     | 12 +++---
 lib/battery/senseshift/battery/sensor.hpp     | 22 ++++++----
 .../{ => senseshift/freertos}/task.hpp        |  4 +-
 lib/opengloves_task/opengloves_task.hpp       | 27 +++++++-----
 15 files changed, 38 insertions(+), 79 deletions(-)
 delete mode 100644 firmware/mode_configs/test.cpp
 rename lib/freertos/{ => senseshift/freertos}/task.hpp (97%)

diff --git a/examples/bhaptics-ble-bt-serial.cpp b/examples/bhaptics-ble-bt-serial.cpp
index 8adb23d3..68dd1482 100644
--- a/examples/bhaptics-ble-bt-serial.cpp
+++ b/examples/bhaptics-ble-bt-serial.cpp
@@ -13,7 +13,6 @@
 #include <BluetoothSerial.h>
 #include <HardwareSerial.h>
 
-using namespace OH;
 using namespace BH;
 
 extern SenseShift App;
diff --git a/firmware/mode_configs/bhaptics/tactal.cpp b/firmware/mode_configs/bhaptics/tactal.cpp
index 991fdce5..d1886357 100644
--- a/firmware/mode_configs/bhaptics/tactal.cpp
+++ b/firmware/mode_configs/bhaptics/tactal.cpp
@@ -13,7 +13,6 @@
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
 
-using namespace OH;
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
diff --git a/firmware/mode_configs/bhaptics/tactglove.cpp b/firmware/mode_configs/bhaptics/tactglove.cpp
index c7fd1924..f3bfc5f4 100644
--- a/firmware/mode_configs/bhaptics/tactglove.cpp
+++ b/firmware/mode_configs/bhaptics/tactglove.cpp
@@ -14,7 +14,6 @@
 #include <senseshift/bh/encoding.hpp>
 #include <senseshift/utility.hpp>
 
-using namespace OH;
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
diff --git a/firmware/mode_configs/bhaptics/tactosy2.cpp b/firmware/mode_configs/bhaptics/tactosy2.cpp
index 26937edb..3063cc98 100644
--- a/firmware/mode_configs/bhaptics/tactosy2.cpp
+++ b/firmware/mode_configs/bhaptics/tactosy2.cpp
@@ -13,7 +13,6 @@
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
 
-using namespace OH;
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
diff --git a/firmware/mode_configs/bhaptics/tactosyf.cpp b/firmware/mode_configs/bhaptics/tactosyf.cpp
index 33aea620..ceae8983 100644
--- a/firmware/mode_configs/bhaptics/tactosyf.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyf.cpp
@@ -13,7 +13,6 @@
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
 
-using namespace OH;
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
diff --git a/firmware/mode_configs/bhaptics/tactosyh.cpp b/firmware/mode_configs/bhaptics/tactosyh.cpp
index 068add9e..0f8beef4 100644
--- a/firmware/mode_configs/bhaptics/tactosyh.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyh.cpp
@@ -13,7 +13,6 @@
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
 
-using namespace OH;
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
index 300d9535..8897b400 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
@@ -13,7 +13,6 @@
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
 
-using namespace OH;
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
index 39bb6835..e147a845 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
@@ -13,7 +13,6 @@
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
 
-using namespace OH;
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
index 6fe41fc7..5d990130 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
@@ -14,7 +14,6 @@
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
 
-using namespace OH;
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
diff --git a/firmware/mode_configs/bhaptics/tactvisor.cpp b/firmware/mode_configs/bhaptics/tactvisor.cpp
index f6e063e0..4c9fa3d0 100644
--- a/firmware/mode_configs/bhaptics/tactvisor.cpp
+++ b/firmware/mode_configs/bhaptics/tactvisor.cpp
@@ -13,7 +13,6 @@
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
 
-using namespace OH;
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
diff --git a/firmware/mode_configs/test.cpp b/firmware/mode_configs/test.cpp
deleted file mode 100644
index 943a38a2..00000000
--- a/firmware/mode_configs/test.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-// Override you configs in this file (Ctrl+Click)
-#include "config/all.h"
-
-#ifdef UNIT_TEST
-#include "ArduinoFake.h"
-#else
-#include "Arduino.h"
-#endif
-
-#include "senseshift.h"
-
-#include <haptic_body.hpp>
-
-using namespace OH;
-
-extern SenseShift App;
-SenseShift* app = &App;
-
-class TestOutput : public OH::AbstractActuator {
-  private:
-    uint8_t channel;
-
-  public:
-    TestOutput(uint8_t channel) : channel(channel){};
-    uint8_t getChannel() { return channel; };
-    void writeOutput(oh_output_intensity_t intensity){};
-};
-
-void setupMode()
-{
-    auto testOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
-      { new TestOutput(0), new TestOutput(1) },
-      { new TestOutput(2), new TestOutput(3) },
-      { new TestOutput(4), new TestOutput(5) },
-      { new TestOutput(6), new TestOutput(7) },
-    });
-
-    auto test = new HapticPlane_Closest(testOutputs);
-    App.getHapticBody()->addComponent(OUTPUT_PATH_ACCESSORY, test);
-}
-
-void loopMode() {}
diff --git a/lib/arduino/senseshift/arduino/components/serial_plotter.hpp b/lib/arduino/senseshift/arduino/components/serial_plotter.hpp
index 02e21b36..708111b6 100644
--- a/lib/arduino/senseshift/arduino/components/serial_plotter.hpp
+++ b/lib/arduino/senseshift/arduino/components/serial_plotter.hpp
@@ -1,7 +1,7 @@
 #pragma once
 
 #include <haptic_body.hpp>
-#include <task.hpp>
+#include <senseshift/freertos/task.hpp>
 
 #include <HardwareSerial.h>
 
@@ -16,12 +16,12 @@ namespace SenseShift::Arduino {
      * @tparam _Tp the type of the serial port
      */
     template<class _Tp>
-    class SerialPlotter_OutputStates : public OH::Task<SerialPlotter_OutputStates<_Tp>> {
+    class SerialPlotter_OutputStates : public ::SenseShift::FreeRTOS::Task<SerialPlotter_OutputStates<_Tp>> {
         static_assert(
           std::is_base_of<Print, _Tp>::value,
           "SerialPlotter_OutputStates only can be used with types, that inherit from Print"
         );
-        friend class OH::Task<SerialPlotter_OutputStates<_Tp>>;
+        friend class ::SenseShift::FreeRTOS::Task<SerialPlotter_OutputStates<_Tp>>;
 
       private:
         _Tp* serial;
@@ -36,9 +36,9 @@ namespace SenseShift::Arduino {
           _Tp& serial,
           ::SenseShift::Body::Haptics::HapticBody* output,
           uint32_t sampleRate,
-          OH::TaskConfig taskConfig = { "Serial Plotter", 2048, 1, tskNO_AFFINITY }
+          ::SenseShift::FreeRTOS::TaskConfig taskConfig = { "Serial Plotter", 2048, 1, tskNO_AFFINITY }
         ) :
-          OH::Task<SerialPlotter_OutputStates<_Tp>>(taskConfig),
+          ::SenseShift::FreeRTOS::Task<SerialPlotter_OutputStates<_Tp>>(taskConfig),
           serial(&serial),
           output(output),
           sampleRate(sampleRate){};
@@ -48,7 +48,7 @@ namespace SenseShift::Arduino {
         void begin() override
         {
             this->setup();
-            OH::Task<SerialPlotter_OutputStates<_Tp>>::begin();
+            ::SenseShift::FreeRTOS::Task<SerialPlotter_OutputStates<_Tp>>::begin();
         };
     };
 
diff --git a/lib/battery/senseshift/battery/sensor.hpp b/lib/battery/senseshift/battery/sensor.hpp
index 06a5f5b6..b5dea89d 100644
--- a/lib/battery/senseshift/battery/sensor.hpp
+++ b/lib/battery/senseshift/battery/sensor.hpp
@@ -2,9 +2,9 @@
 
 #include "senseshift/battery.hpp"
 
+#include <senseshift/freertos/task.hpp>
 #include <senseshift/input/sensor.hpp>
 #include <senseshift/utility.hpp>
-#include <task.hpp>
 
 #ifndef SENSESHIFT_BATTERY_TASK_PRIORITY
 #define SENSESHIFT_BATTERY_TASK_PRIORITY 1
@@ -19,17 +19,23 @@ namespace SenseShift::Battery {
      * Tasked sensor decorator
      */
     template<typename _Tp>
-    class TaskedSensor : public OH::Task<TaskedSensor<_Tp>>, public ::SenseShift::Input::MemoizedSensor<_Tp> {
-        friend class OH::Task<TaskedSensor<_Tp>>;
+    class TaskedSensor :
+      public ::SenseShift::FreeRTOS::Task<TaskedSensor<_Tp>>,
+      public ::SenseShift::Input::MemoizedSensor<_Tp> {
+        friend class ::SenseShift::FreeRTOS::Task<TaskedSensor<_Tp>>;
 
       public:
-        TaskedSensor(::SenseShift::Input::ISensor<_Tp>* sensor, OH::TaskConfig taskConfig, const uint32_t rate) :
-          ::SenseShift::Input::MemoizedSensor<_Tp>(sensor), OH::Task<TaskedSensor<_Tp>>(taskConfig), rate(rate){};
+        TaskedSensor(
+          ::SenseShift::Input::ISensor<_Tp>* sensor, ::SenseShift::FreeRTOS::TaskConfig taskConfig, const uint32_t rate
+        ) :
+          ::SenseShift::Input::MemoizedSensor<_Tp>(sensor),
+          ::SenseShift::FreeRTOS::Task<TaskedSensor<_Tp>>(taskConfig),
+          rate(rate){};
 
         void begin() override
         {
             this->setup();
-            this->OH::Task<TaskedSensor<_Tp>>::begin();
+            this->::SenseShift::FreeRTOS::Task<TaskedSensor<_Tp>>::begin();
         };
 
       protected:
@@ -51,7 +57,7 @@ namespace SenseShift::Battery {
     typedef ::SenseShift::Input::ISensor<BatteryState> IBatterySensor;
 
     class BatterySensor : public TaskedSensor<BatteryState> {
-        friend class OH::Task<TaskedSensor<BatteryState>>;
+        friend class ::SenseShift::FreeRTOS::Task<TaskedSensor<BatteryState>>;
         friend class TaskedSensor<BatteryState>;
 
       public:
@@ -59,7 +65,7 @@ namespace SenseShift::Battery {
           IBatterySensor* sensor,
           ::SenseShift::IEventDispatcher* eventDispatcher,
           const BatteryConfig_t& config,
-          OH::TaskConfig taskConfig
+          ::SenseShift::FreeRTOS::TaskConfig taskConfig
         ) :
           TaskedSensor<BatteryState>(sensor, taskConfig, config.sampleRate), eventDispatcher(eventDispatcher){};
 
diff --git a/lib/freertos/task.hpp b/lib/freertos/senseshift/freertos/task.hpp
similarity index 97%
rename from lib/freertos/task.hpp
rename to lib/freertos/senseshift/freertos/task.hpp
index e77c7ec4..d06aada4 100644
--- a/lib/freertos/task.hpp
+++ b/lib/freertos/senseshift/freertos/task.hpp
@@ -21,7 +21,7 @@ BaseType_t xTaskCreateUniversal(
 }
 #endif
 
-namespace OH {
+namespace SenseShift::FreeRTOS {
     struct TaskConfig {
         const char* name;
         uint32_t stackDepth;
@@ -78,4 +78,4 @@ namespace OH {
             }
         };
     };
-} // namespace OH
+} // namespace SenseShift::FreeRTOS
diff --git a/lib/opengloves_task/opengloves_task.hpp b/lib/opengloves_task/opengloves_task.hpp
index 9d1f115a..8fab0ec0 100644
--- a/lib/opengloves_task/opengloves_task.hpp
+++ b/lib/opengloves_task/opengloves_task.hpp
@@ -10,12 +10,12 @@
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/input/sensor/digital.hpp>
 #include <senseshift/calibration.hpp>
+#include <senseshift/freertos/task.hpp>
 #include <senseshift/input/sensor.hpp>
 #include <senseshift/input/sensor/joystick.hpp>
 #include <senseshift/utility.hpp>
 #include <sensor/og_finger.hpp>
 #include <sensor/og_gesture.hpp>
-#include <task.hpp>
 
 namespace OpenGloves {
     struct OpenGlovesTrackingTaskConfig {
@@ -36,8 +36,8 @@ namespace OpenGloves {
         }
     };
 
-    class OpenGlovesTrackingTask : public OH::Task<OpenGlovesTrackingTask> {
-        friend class OH::Task<OpenGlovesTrackingTask>;
+    class OpenGlovesTrackingTask : public SenseShift::FreeRTOS::Task<OpenGlovesTrackingTask> {
+        friend class SenseShift::FreeRTOS::Task<OpenGlovesTrackingTask>;
 
       public:
         /**
@@ -58,7 +58,7 @@ namespace OpenGloves {
           std::vector<StringEncodedMemoizedSensor<uint16_t>*>& joysticks,
           std::vector<IStringEncodedMemoizedSensor*>& otherSensors,
           std::optional<StringEncodedMemoizedSensor<bool>>& calibrationButton,
-          OH::TaskConfig taskConfig
+          SenseShift::FreeRTOS::TaskConfig taskConfig
         ) :
           config(config),
           communication(communication),
@@ -68,7 +68,7 @@ namespace OpenGloves {
           otherSensors(otherSensors),
           calibrationButton(calibrationButton),
           allSensors(std::vector<IStringEncodedMemoizedSensor*>()),
-          OH::Task<OpenGlovesTrackingTask>(taskConfig)
+          SenseShift::FreeRTOS::Task<OpenGlovesTrackingTask>(taskConfig)
         {
             if (fingers.thumb.has_value()) {
                 auto* thumb = &fingers.thumb.value();
@@ -126,7 +126,7 @@ namespace OpenGloves {
         {
             log_d("Starting OpenGloves tracking task: %p", this);
             this->setup();
-            this->OH::Task<OpenGlovesTrackingTask>::begin();
+            this->SenseShift::FreeRTOS::Task<OpenGlovesTrackingTask>::begin();
         };
 
       private:
@@ -215,14 +215,19 @@ namespace OpenGloves {
         };
     };
 
-    class OpenGlovesForceFeedbackTask : public OH::Task<OpenGlovesForceFeedbackTask> {
-        friend class OH::Task<OpenGlovesForceFeedbackTask>;
+    class OpenGlovesForceFeedbackTask : public SenseShift::FreeRTOS::Task<OpenGlovesForceFeedbackTask> {
+        friend class SenseShift::FreeRTOS::Task<OpenGlovesForceFeedbackTask>;
 
       public:
         OpenGlovesForceFeedbackTask(
-          ICommunication& communication, HandActuators& actuators, size_t updateRate, OH::TaskConfig taskConfig
+          ICommunication& communication,
+          HandActuators& actuators,
+          size_t updateRate,
+          SenseShift::FreeRTOS::TaskConfig taskConfig
         ) :
-          communication(communication), actuators(actuators), OH::Task<OpenGlovesForceFeedbackTask>(taskConfig)
+          communication(communication),
+          actuators(actuators),
+          SenseShift::FreeRTOS::Task<OpenGlovesForceFeedbackTask>(taskConfig)
         {
             this->updateIntervalMs = 1000 / updateRate;
         };
@@ -231,7 +236,7 @@ namespace OpenGloves {
         {
             log_d("Starting OpenGloves force feedback task: %p", this);
             this->setup();
-            this->OH::Task<OpenGlovesForceFeedbackTask>::begin();
+            this->SenseShift::FreeRTOS::Task<OpenGlovesForceFeedbackTask>::begin();
         };
 
       private:

From 46743e918485c6b3ddf0153a4a23e1cab36986e6 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Thu, 24 Aug 2023 13:32:33 +0400
Subject: [PATCH 19/82] refactor(Battery): decouple battery API and impl

---
 firmware/mode_configs/bhaptics/tactal.cpp     | 10 +--
 firmware/mode_configs/bhaptics/tactglove.cpp  | 10 +--
 firmware/mode_configs/bhaptics/tactosy2.cpp   | 10 +--
 firmware/mode_configs/bhaptics/tactosyf.cpp   | 10 +--
 firmware/mode_configs/bhaptics/tactosyh.cpp   | 10 +--
 .../mode_configs/bhaptics/tactsuit_x16.cpp    | 10 +--
 .../bhaptics/tactsuit_x16_pca9685.cpp         | 10 +--
 .../mode_configs/bhaptics/tactsuit_x40.cpp    | 10 +--
 firmware/mode_configs/bhaptics/tactvisor.cpp  | 10 +--
 include/senseshift.h                          |  2 +-
 lib/battery/senseshift/battery/sensor.hpp     | 66 -------------------
 .../senseshift/freertos/battery.hpp           | 22 +++++++
 .../senseshift/freertos/input/sensor.hpp      | 59 +++++++++++++++++
 lib/freertos/senseshift/freertos/task.hpp     | 33 +++++-----
 14 files changed, 154 insertions(+), 118 deletions(-)
 create mode 100644 lib/battery_task/senseshift/freertos/battery.hpp
 create mode 100644 lib/freertos/senseshift/freertos/input/sensor.hpp

diff --git a/firmware/mode_configs/bhaptics/tactal.cpp b/firmware/mode_configs/bhaptics/tactal.cpp
index d1886357..f19d9e7d 100644
--- a/firmware/mode_configs/bhaptics/tactal.cpp
+++ b/firmware/mode_configs/bhaptics/tactal.cpp
@@ -12,10 +12,13 @@
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
+#include <senseshift/freertos/battery.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
+using namespace SenseShift::FreeRTOS::Battery;
+using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
@@ -53,10 +56,9 @@ void setupMode()
     bhBleConnection->begin();
 
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
-    auto* battery = new BatterySensor(
-      new NaiveBatterySensor(new AnalogSensor(36)),
-      &App,
-      { .sampleRate = SENSESHIFT_BATTERY_SAMPLE_RATE },
+    auto* battery = new TaskedSensor<BatteryState>(
+      new BatterySensor(new NaiveBatterySensor(new AnalogSensor(36)), app),
+      SENSESHIFT_BATTERY_SAMPLE_RATE,
       { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     battery->begin();
diff --git a/firmware/mode_configs/bhaptics/tactglove.cpp b/firmware/mode_configs/bhaptics/tactglove.cpp
index f3bfc5f4..8b123ef4 100644
--- a/firmware/mode_configs/bhaptics/tactglove.cpp
+++ b/firmware/mode_configs/bhaptics/tactglove.cpp
@@ -12,11 +12,14 @@
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
+#include <senseshift/freertos/battery.hpp>
 #include <senseshift/utility.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
+using namespace SenseShift::FreeRTOS::Battery;
+using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
@@ -61,10 +64,9 @@ void setupMode()
     bhBleConnection->begin();
 
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
-    auto* battery = new BatterySensor(
-      new NaiveBatterySensor(new AnalogSensor(36)),
-      &App,
-      { .sampleRate = SENSESHIFT_BATTERY_SAMPLE_RATE },
+    auto* battery = new TaskedSensor<BatteryState>(
+      new BatterySensor(new NaiveBatterySensor(new AnalogSensor(36)), app),
+      SENSESHIFT_BATTERY_SAMPLE_RATE,
       { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     battery->begin();
diff --git a/firmware/mode_configs/bhaptics/tactosy2.cpp b/firmware/mode_configs/bhaptics/tactosy2.cpp
index 3063cc98..682a349b 100644
--- a/firmware/mode_configs/bhaptics/tactosy2.cpp
+++ b/firmware/mode_configs/bhaptics/tactosy2.cpp
@@ -12,10 +12,13 @@
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
+#include <senseshift/freertos/battery.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
+using namespace SenseShift::FreeRTOS::Battery;
+using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
@@ -54,10 +57,9 @@ void setupMode()
     bhBleConnection->begin();
 
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
-    auto* battery = new BatterySensor(
-      new NaiveBatterySensor(new AnalogSensor(36)),
-      &App,
-      { .sampleRate = SENSESHIFT_BATTERY_SAMPLE_RATE },
+    auto* battery = new TaskedSensor<BatteryState>(
+      new BatterySensor(new NaiveBatterySensor(new AnalogSensor(36)), app),
+      SENSESHIFT_BATTERY_SAMPLE_RATE,
       { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     battery->begin();
diff --git a/firmware/mode_configs/bhaptics/tactosyf.cpp b/firmware/mode_configs/bhaptics/tactosyf.cpp
index ceae8983..99b792fd 100644
--- a/firmware/mode_configs/bhaptics/tactosyf.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyf.cpp
@@ -12,10 +12,13 @@
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
+#include <senseshift/freertos/battery.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
+using namespace SenseShift::FreeRTOS::Battery;
+using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
@@ -55,10 +58,9 @@ void setupMode()
     bhBleConnection->begin();
 
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
-    auto* battery = new BatterySensor(
-      new NaiveBatterySensor(new AnalogSensor(36)),
-      &App,
-      { .sampleRate = SENSESHIFT_BATTERY_SAMPLE_RATE },
+    auto* battery = new TaskedSensor<BatteryState>(
+      new BatterySensor(new NaiveBatterySensor(new AnalogSensor(36)), app),
+      SENSESHIFT_BATTERY_SAMPLE_RATE,
       { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     battery->begin();
diff --git a/firmware/mode_configs/bhaptics/tactosyh.cpp b/firmware/mode_configs/bhaptics/tactosyh.cpp
index 0f8beef4..0e3cc417 100644
--- a/firmware/mode_configs/bhaptics/tactosyh.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyh.cpp
@@ -12,10 +12,13 @@
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
+#include <senseshift/freertos/battery.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
+using namespace SenseShift::FreeRTOS::Battery;
+using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
@@ -55,10 +58,9 @@ void setupMode()
     bhBleConnection->begin();
 
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
-    auto* battery = new BatterySensor(
-      new NaiveBatterySensor(new AnalogSensor(36)),
-      &App,
-      { .sampleRate = SENSESHIFT_BATTERY_SAMPLE_RATE },
+    auto* battery = new TaskedSensor<BatteryState>(
+      new BatterySensor(new NaiveBatterySensor(new AnalogSensor(36)), app),
+      SENSESHIFT_BATTERY_SAMPLE_RATE,
       { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     battery->begin();
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
index 8897b400..8bc88899 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
@@ -12,10 +12,13 @@
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
+#include <senseshift/freertos/battery.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
+using namespace SenseShift::FreeRTOS::Battery;
+using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
@@ -65,10 +68,9 @@ void setupMode()
     bhBleConnection->begin();
 
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
-    auto* battery = new BatterySensor(
-      new NaiveBatterySensor(new AnalogSensor(36)),
-      &App,
-      { .sampleRate = SENSESHIFT_BATTERY_SAMPLE_RATE },
+    auto* battery = new TaskedSensor<BatteryState>(
+      new BatterySensor(new NaiveBatterySensor(new AnalogSensor(36)), app),
+      SENSESHIFT_BATTERY_SAMPLE_RATE,
       { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     battery->begin();
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
index e147a845..f7a9b099 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
@@ -12,10 +12,13 @@
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
+#include <senseshift/freertos/battery.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
+using namespace SenseShift::FreeRTOS::Battery;
+using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
@@ -70,10 +73,9 @@ void setupMode()
     bhBleConnection->begin();
 
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
-    auto* battery = new BatterySensor(
-      new NaiveBatterySensor(new AnalogSensor(36)),
-      &App,
-      { .sampleRate = SENSESHIFT_BATTERY_SAMPLE_RATE },
+    auto* battery = new TaskedSensor<BatteryState>(
+      new BatterySensor(new NaiveBatterySensor(new AnalogSensor(36)), app),
+      SENSESHIFT_BATTERY_SAMPLE_RATE,
       { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     battery->begin();
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
index 5d990130..e8668bcf 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
@@ -13,10 +13,13 @@
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
+#include <senseshift/freertos/battery.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
+using namespace SenseShift::FreeRTOS::Battery;
+using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
@@ -78,10 +81,9 @@ void setupMode()
     bhBleConnection->begin();
 
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
-    auto* battery = new BatterySensor(
-      new NaiveBatterySensor(new AnalogSensor(36)),
-      &App,
-      { .sampleRate = SENSESHIFT_BATTERY_SAMPLE_RATE },
+    auto* battery = new TaskedSensor<BatteryState>(
+      new BatterySensor(new NaiveBatterySensor(new AnalogSensor(36)), app),
+      SENSESHIFT_BATTERY_SAMPLE_RATE,
       { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     battery->begin();
diff --git a/firmware/mode_configs/bhaptics/tactvisor.cpp b/firmware/mode_configs/bhaptics/tactvisor.cpp
index 4c9fa3d0..68cd1188 100644
--- a/firmware/mode_configs/bhaptics/tactvisor.cpp
+++ b/firmware/mode_configs/bhaptics/tactvisor.cpp
@@ -12,10 +12,13 @@
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
+#include <senseshift/freertos/battery.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
+using namespace SenseShift::FreeRTOS::Battery;
+using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
@@ -53,10 +56,9 @@ void setupMode()
     bhBleConnection->begin();
 
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
-    auto* battery = new BatterySensor(
-      new NaiveBatterySensor(new AnalogSensor(36)),
-      &App,
-      { .sampleRate = SENSESHIFT_BATTERY_SAMPLE_RATE },
+    auto* battery = new TaskedSensor<BatteryState>(
+      new BatterySensor(new NaiveBatterySensor(new AnalogSensor(36)), app),
+      SENSESHIFT_BATTERY_SAMPLE_RATE,
       { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     battery->begin();
diff --git a/include/senseshift.h b/include/senseshift.h
index 27a3c3e9..c63f5659 100644
--- a/include/senseshift.h
+++ b/include/senseshift.h
@@ -13,7 +13,7 @@ namespace SenseShift {
       private:
         std::vector<const IEventListener*> eventListeners{};
         Body::Haptics::HapticBody* pHapticBody;
-        Battery::BatterySensor* battery;
+        ::SenseShift::Battery::IBatterySensor* battery;
 
       public:
         SenseShift();
diff --git a/lib/battery/senseshift/battery/sensor.hpp b/lib/battery/senseshift/battery/sensor.hpp
index b5dea89d..0ae63ba3 100644
--- a/lib/battery/senseshift/battery/sensor.hpp
+++ b/lib/battery/senseshift/battery/sensor.hpp
@@ -11,77 +11,11 @@
 #endif
 
 namespace SenseShift::Battery {
-    typedef struct BatteryConfig {
-        uint sampleRate;
-    } BatteryConfig_t;
-
-    /**
-     * Tasked sensor decorator
-     */
-    template<typename _Tp>
-    class TaskedSensor :
-      public ::SenseShift::FreeRTOS::Task<TaskedSensor<_Tp>>,
-      public ::SenseShift::Input::MemoizedSensor<_Tp> {
-        friend class ::SenseShift::FreeRTOS::Task<TaskedSensor<_Tp>>;
-
-      public:
-        TaskedSensor(
-          ::SenseShift::Input::ISensor<_Tp>* sensor, ::SenseShift::FreeRTOS::TaskConfig taskConfig, const uint32_t rate
-        ) :
-          ::SenseShift::Input::MemoizedSensor<_Tp>(sensor),
-          ::SenseShift::FreeRTOS::Task<TaskedSensor<_Tp>>(taskConfig),
-          rate(rate){};
-
-        void begin() override
-        {
-            this->setup();
-            this->::SenseShift::FreeRTOS::Task<TaskedSensor<_Tp>>::begin();
-        };
-
-      protected:
-        const uint32_t rate;
-
-      private:
-        virtual void run(void)
-        {
-            while (true) {
-                this->updateValue();
-                delay(this->rate);
-            }
-        };
-    };
-
     /**
      * Abstract battery sensor
      */
     typedef ::SenseShift::Input::ISensor<BatteryState> IBatterySensor;
 
-    class BatterySensor : public TaskedSensor<BatteryState> {
-        friend class ::SenseShift::FreeRTOS::Task<TaskedSensor<BatteryState>>;
-        friend class TaskedSensor<BatteryState>;
-
-      public:
-        BatterySensor(
-          IBatterySensor* sensor,
-          ::SenseShift::IEventDispatcher* eventDispatcher,
-          const BatteryConfig_t& config,
-          ::SenseShift::FreeRTOS::TaskConfig taskConfig
-        ) :
-          TaskedSensor<BatteryState>(sensor, taskConfig, config.sampleRate), eventDispatcher(eventDispatcher){};
-
-        void run() override
-        {
-            while (true) {
-                this->updateValue();
-                this->eventDispatcher->postEvent(new BatteryLevelEvent(this->value));
-                delay(this->rate);
-            }
-        }
-
-      private:
-        ::SenseShift::IEventDispatcher* eventDispatcher;
-    };
-
     class NaiveBatterySensor : public IBatterySensor {
       public:
         NaiveBatterySensor(::SenseShift::Input::ISensor<uint16_t>* sensor) : sensor(sensor){};
diff --git a/lib/battery_task/senseshift/freertos/battery.hpp b/lib/battery_task/senseshift/freertos/battery.hpp
new file mode 100644
index 00000000..688459a2
--- /dev/null
+++ b/lib/battery_task/senseshift/freertos/battery.hpp
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <senseshift/battery/sensor.hpp>
+#include <senseshift/freertos/input/sensor.hpp>
+
+namespace SenseShift::FreeRTOS::Battery {
+    class BatterySensor : public ::SenseShift::Input::MemoizedSensor<::SenseShift::Battery::BatteryState> {
+      public:
+        BatterySensor(::SenseShift::Battery::IBatterySensor* sensor, ::SenseShift::IEventDispatcher* eventDispatcher) :
+          ::SenseShift::Input::MemoizedSensor<::SenseShift::Battery::BatteryState>(sensor),
+          eventDispatcher(eventDispatcher){};
+
+        void updateValue() override
+        {
+            this->::SenseShift::Input::MemoizedSensor<::SenseShift::Battery::BatteryState>::updateValue();
+            this->eventDispatcher->postEvent(new ::SenseShift::Battery::BatteryLevelEvent(this->value));
+        }
+
+      private:
+        ::SenseShift::IEventDispatcher* eventDispatcher;
+    };
+} // namespace SenseShift::FreeRTOS::Battery
diff --git a/lib/freertos/senseshift/freertos/input/sensor.hpp b/lib/freertos/senseshift/freertos/input/sensor.hpp
new file mode 100644
index 00000000..b4b273b8
--- /dev/null
+++ b/lib/freertos/senseshift/freertos/input/sensor.hpp
@@ -0,0 +1,59 @@
+#pragma once
+
+#include <senseshift/freertos/task.hpp>
+#include <senseshift/input/sensor.hpp>
+
+#include <cstdint>
+
+namespace SenseShift::FreeRTOS::Input {
+    /**
+     * Sensor update task
+     */
+    class SensorUpdateTask : public Task<SensorUpdateTask> {
+        friend class Task<SensorUpdateTask>;
+
+      private:
+        using Sensor_t = ::SenseShift::Input::IMemoizedSensor;
+
+      public:
+        SensorUpdateTask(Sensor_t* sensor, std::uint32_t updateDelay, TaskConfig taskConfig) :
+          Task<SensorUpdateTask>(taskConfig), updateDelay(updateDelay){};
+
+      protected:
+        Sensor_t* sensor;
+        std::uint32_t updateDelay;
+
+        void run()
+        {
+            while (true) {
+                this->sensor->updateValue();
+                ::delay(this->updateDelay);
+            }
+        }
+    };
+
+    template<typename _Tp>
+    class TaskedSensor : public SensorUpdateTask, public ::SenseShift::Input::ISensor<_Tp> {
+        friend class SensorUpdateTask;
+
+      private:
+        using Sensor_t = ::SenseShift::Input::MemoizedSensor<_Tp>;
+
+      public:
+        TaskedSensor(Sensor_t* sensor, std::uint32_t updateDelay, TaskConfig taskConfig) :
+          SensorUpdateTask(sensor, updateDelay, taskConfig), sensor(sensor){};
+
+        void begin() override
+        {
+            this->setup();
+            SensorUpdateTask::begin();
+        };
+
+        void setup() override { this->sensor->setup(); };
+
+        _Tp getValue() override { return this->sensor->getValue(); };
+
+      private:
+        Sensor_t* sensor;
+    };
+} // namespace SenseShift::FreeRTOS::Input
diff --git a/lib/freertos/senseshift/freertos/task.hpp b/lib/freertos/senseshift/freertos/task.hpp
index d06aada4..d41355ec 100644
--- a/lib/freertos/senseshift/freertos/task.hpp
+++ b/lib/freertos/senseshift/freertos/task.hpp
@@ -38,25 +38,18 @@ namespace SenseShift::FreeRTOS {
         template<typename>
         friend class Task;
 
-      private:
-        TaskConfig taskConfig;
-        TaskHandle_t taskHandle = nullptr;
-
-        static void taskFunction(void* params)
-        {
-            _Tp* task = static_cast<_Tp*>(params);
-            task->run();
-        }
-
       public:
         Task(const char* name, uint32_t stackDepth, UBaseType_t priority, const BaseType_t coreId = tskNO_AFFINITY)
         {
-            TaskConfig config = { name, stackDepth, priority, coreId };
-
-            this->taskConfig = config;
+            this->taskConfig = { name, stackDepth, priority, coreId };
+        };
+        Task(TaskConfig& config) : taskConfig(config){};
+        virtual ~Task()
+        {
+            if (taskHandle) {
+                vTaskDelete(taskHandle);
+            }
         };
-        Task(TaskConfig config) : taskConfig(config){};
-        virtual ~Task(){};
 
         TaskHandle_t getHandle() const { return taskHandle; };
 
@@ -77,5 +70,15 @@ namespace SenseShift::FreeRTOS {
                 log_e("Failed to create task %s", this->taskConfig.name);
             }
         };
+
+      private:
+        const TaskConfig& taskConfig;
+        TaskHandle_t taskHandle = nullptr;
+
+        static void taskFunction(void* params)
+        {
+            _Tp* task = static_cast<_Tp*>(params);
+            task->run();
+        }
     };
 } // namespace SenseShift::FreeRTOS

From bb73823f8a6d562888e9a12f9d9637a26a793d21 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Thu, 24 Aug 2023 14:52:58 +0400
Subject: [PATCH 20/82] ci(GitHub): easier build flag handling

---
 .github/scripts/get_firmware_name.sh | 45 +++++++++++++++++-----------
 1 file changed, 27 insertions(+), 18 deletions(-)

diff --git a/.github/scripts/get_firmware_name.sh b/.github/scripts/get_firmware_name.sh
index d7c15d7a..63b0b610 100755
--- a/.github/scripts/get_firmware_name.sh
+++ b/.github/scripts/get_firmware_name.sh
@@ -32,6 +32,22 @@ getBhapticsName() {
     fi
 }
 
+handleCalibrationFlag() {
+    local target=$1
+    local flag=$2
+    local prefix=$3
+
+    if [[ $flag =~ MinMaxCalibrator ]]; then
+        target="$target+${prefix}_minmax"
+    elif [[ $flag =~ FixedCenterPointDeviationCalibrator ]]; then
+        target="$target+${prefix}_fcpd"
+    elif [[ $flag =~ CenterPointDeviationCalibrator ]]; then
+        target="$target+${prefix}_cpd"
+    fi
+
+    echo "$target"
+}
+
 getOpenGlovesName() {
     local target=$1
     # rest of params
@@ -43,27 +59,20 @@ getOpenGlovesName() {
     if [[ $flags =~ OPENGLOVES_COMM_SERIAL ]]; then
         echo "::debug::Serial is enabled, appending +serial to the target"
         target="$target+serial"
-    fi
-
-    if [[ $flags =~ OPENGLOVES_COMM_BTSERIAL ]]; then
+    elif [[ $flags =~ OPENGLOVES_COMM_BTSERIAL ]]; then
         echo "::debug::Bluetooth Serial is enabled, appending +bluetooth to the target"
         target="$target+bluetooth"
     fi
 
-    if [[ $flags =~ CALIBRATION_CURL=::SenseShift::Calibration::MinMaxCalibrator ]]; then
-        echo "::debug::MinMaxCalibrator is enabled, appending +curl_minmax to the target"
-        target="$target+curl_minmaxcalib"
-    fi
-
-    if [[ $flags =~ CALIBRATION_CURL=::SenseShift::Calibration::CenterPointDeviationCalibrator ]]; then
-        echo "::debug::CenterPointDeviationCalibrator is enabled, appending +curl_cpcalib to the target"
-        target="$target+curl_cpcalib"
-    fi
+    for flag in "${@:2}"; do
+        if [[ $flag =~ CALIBRATION_CURL ]]; then
+            target=$(handleCalibrationFlag "$target" "$flag" "curl_calib")
+        fi
 
-    if [[ $flags =~ CALIBRATION_CURL=::SenseShift::Calibration::FixedCenterPointDeviationCalibrator ]]; then
-        echo "::debug::FixedCenterPointDeviationCalibrator is enabled, appending +curl_fcpcalib to the target"
-        target="$target+curl_fcpcalib"
-    fi
+        if [[ $flag =~ CALIBRATION_SPLAY ]]; then
+            target=$(handleCalibrationFlag "$target" "$flag" "splay_calib")
+        fi
+    done
 
     echo "firmware=$target"
     if [[ -n "$GITHUB_ACTIONS" ]]; then
@@ -77,9 +86,9 @@ getOpenGlovesName() {
 target=$1
 echo "::debug::Target is $target"
 if [[ $target =~ ^(bhaptics) ]]; then
-    getBhapticsName $@
+    getBhapticsName "${@}"
 elif [[ $target =~ ^(opengloves|lucidgloves|indexer) ]]; then
-    getOpenGlovesName $@
+    getOpenGlovesName "${@}"
 else
     echo "::error::Unknown target $target"
     exit 1

From 854f5c532dba357f85170b2cdc2e03610a7d6338 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Tue, 29 Aug 2023 01:24:25 +0400
Subject: [PATCH 21/82] refactor(Input): use memoized sensor by default

---
 .../mode_configs/opengloves/opengloves.cpp    |  6 +-
 .../arduino/input/sensor/analog.hpp           | 10 +--
 .../arduino/input/sensor/digital.hpp          | 10 +--
 lib/battery/senseshift/battery/sensor.hpp     |  8 +-
 .../senseshift/freertos/battery.hpp           |  4 +-
 lib/calibration/senseshift/calibration.hpp    |  2 -
 lib/core/senseshift/interface.hpp             | 11 +++
 .../senseshift/freertos/input/sensor.hpp      | 10 +--
 lib/opengloves/og_protocol.hpp                |  2 +-
 lib/opengloves/sensor/og_finger.hpp           | 36 ++++-----
 lib/opengloves/sensor/og_gesture.hpp          |  8 +-
 lib/opengloves/sensor/og_sensor.hpp           |  4 +-
 lib/opengloves_task/opengloves_task.hpp       |  2 +-
 lib/sensor/senseshift/input/sensor.hpp        | 77 +++++++++----------
 .../senseshift/input/sensor/joystick.hpp      |  8 +-
 test/test_opengloves/main.cpp                 | 18 ++---
 test/test_opengloves_finger/main.cpp          | 18 ++---
 test/test_sensor/main.cpp                     | 12 +--
 18 files changed, 126 insertions(+), 120 deletions(-)
 create mode 100644 lib/core/senseshift/interface.hpp

diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp
index 47d8e289..5af9b961 100644
--- a/firmware/mode_configs/opengloves/opengloves.cpp
+++ b/firmware/mode_configs/opengloves/opengloves.cpp
@@ -55,7 +55,7 @@
 #define FINGER_PINKY_ENABLED (defined(PIN_FINGER_PINKY) && (PIN_FINGER_PINKY != -1))
 #define FINGER_CLASS(type, curl_pin, curl_invert, curl_calib)                  \
     FingerSensor(                                                              \
-      new ::SenseShift::Input::CalibratedSensor<uint16_t>(                     \
+      new ::SenseShift::Input::CalibratedSimpleSensor<uint16_t>(               \
         new ::SenseShift::Arduino::Input::AnalogSensor<curl_invert>(curl_pin), \
         new curl_calib()                                                       \
       ),                                                                       \
@@ -69,11 +69,11 @@
 #define FINGER_PINKY_SPLAY (FINGER_PINKY_ENABLED && defined(PIN_FINGER_PINKY_SPLAY) && (PIN_FINGER_PINKY_SPLAY != -1))
 #define FINGER_SPLAY_CLASS(type, curl_pin, curl_invert, curl_calib, splay_pin, splay_invert, splay_calib) \
     FingerSensor(                                                                                         \
-      new ::SenseShift::Input::CalibratedSensor<uint16_t>(                                                \
+      new ::SenseShift::Input::CalibratedSimpleSensor<uint16_t>(                                          \
         new ::SenseShift::Arduino::Input::AnalogSensor<curl_invert>(curl_pin),                            \
         new curl_calib()                                                                                  \
       ),                                                                                                  \
-      new ::SenseShift::Input::CalibratedSensor<uint16_t>(                                                \
+      new ::SenseShift::Input::CalibratedSimpleSensor<uint16_t>(                                          \
         new ::SenseShift::Arduino::Input::AnalogSensor<splay_invert>(splay_pin),                          \
         new splay_calib()                                                                                 \
       ),                                                                                                  \
diff --git a/lib/arduino/senseshift/arduino/input/sensor/analog.hpp b/lib/arduino/senseshift/arduino/input/sensor/analog.hpp
index 4d1d5705..4dabd41b 100644
--- a/lib/arduino/senseshift/arduino/input/sensor/analog.hpp
+++ b/lib/arduino/senseshift/arduino/input/sensor/analog.hpp
@@ -6,26 +6,26 @@
 
 namespace SenseShift::Arduino::Input {
     template<bool invert = false>
-    class AnalogSensor : public ::SenseShift::Input::ISensor<uint16_t> {
+    class AnalogSensor : public ::SenseShift::Input::ISimpleSensor<uint16_t> {
       private:
         uint8_t pin;
 
       public:
         AnalogSensor(uint8_t pin) : pin(pin) {}
 
-        void setup(void) { pinMode(this->pin, INPUT); };
+        void init() override { pinMode(this->pin, INPUT); };
 
-        uint16_t getValue(void) override;
+        uint16_t getValue() override;
     };
 
     template<>
-    uint16_t AnalogSensor<false>::getValue(void)
+    uint16_t AnalogSensor<false>::getValue()
     {
         return analogRead(this->pin);
     }
 
     template<>
-    uint16_t AnalogSensor<true>::getValue(void)
+    uint16_t AnalogSensor<true>::getValue()
     {
         return ANALOG_MAX - analogRead(this->pin);
     }
diff --git a/lib/arduino/senseshift/arduino/input/sensor/digital.hpp b/lib/arduino/senseshift/arduino/input/sensor/digital.hpp
index 16de1b6c..31b77bbf 100644
--- a/lib/arduino/senseshift/arduino/input/sensor/digital.hpp
+++ b/lib/arduino/senseshift/arduino/input/sensor/digital.hpp
@@ -5,7 +5,7 @@
 #include <Arduino.h>
 
 namespace SenseShift::Arduino::Input {
-    typedef ::SenseShift::Input::ISensor<bool> IDigitalSensor;
+    typedef ::SenseShift::Input::ISimpleSensor<bool> IDigitalSensor;
 
     template<bool invert = false>
     class DigitalSensor : public IDigitalSensor {
@@ -15,19 +15,19 @@ namespace SenseShift::Arduino::Input {
       public:
         DigitalSensor(uint8_t pin) : pin(pin) {}
 
-        void setup(void) { pinMode(this->pin, INPUT_PULLUP); };
+        void init() override { pinMode(this->pin, INPUT_PULLUP); };
 
-        bool getValue(void) override;
+        bool getValue() override;
     };
 
     template<>
-    bool DigitalSensor<false>::getValue(void)
+    bool DigitalSensor<false>::getValue()
     {
         return digitalRead(this->pin) == LOW;
     }
 
     template<>
-    bool DigitalSensor<true>::getValue(void)
+    bool DigitalSensor<true>::getValue()
     {
         return digitalRead(this->pin) == HIGH;
     }
diff --git a/lib/battery/senseshift/battery/sensor.hpp b/lib/battery/senseshift/battery/sensor.hpp
index 0ae63ba3..5897564e 100644
--- a/lib/battery/senseshift/battery/sensor.hpp
+++ b/lib/battery/senseshift/battery/sensor.hpp
@@ -14,20 +14,20 @@ namespace SenseShift::Battery {
     /**
      * Abstract battery sensor
      */
-    typedef ::SenseShift::Input::ISensor<BatteryState> IBatterySensor;
+    typedef ::SenseShift::Input::ISimpleSensor<BatteryState> IBatterySensor;
 
     class NaiveBatterySensor : public IBatterySensor {
       public:
-        NaiveBatterySensor(::SenseShift::Input::ISensor<uint16_t>* sensor) : sensor(sensor){};
+        NaiveBatterySensor(::SenseShift::Input::ISimpleSensor<uint16_t>* sensor) : sensor(sensor){};
 
         BatteryState getValue() override
         {
             return { .level =
                        static_cast<uint8_t>(::SenseShift::simpleMap<uint16_t>(this->sensor->getValue(), 4095, 255)) };
         };
-        void setup() { this->sensor->setup(); }
+        void init() override { this->sensor->init(); }
 
       private:
-        ::SenseShift::Input::ISensor<uint16_t>* sensor;
+        ::SenseShift::Input::ISimpleSensor<uint16_t>* sensor;
     };
 } // namespace SenseShift::Battery
diff --git a/lib/battery_task/senseshift/freertos/battery.hpp b/lib/battery_task/senseshift/freertos/battery.hpp
index 688459a2..8a1199fe 100644
--- a/lib/battery_task/senseshift/freertos/battery.hpp
+++ b/lib/battery_task/senseshift/freertos/battery.hpp
@@ -10,9 +10,9 @@ namespace SenseShift::FreeRTOS::Battery {
           ::SenseShift::Input::MemoizedSensor<::SenseShift::Battery::BatteryState>(sensor),
           eventDispatcher(eventDispatcher){};
 
-        void updateValue() override
+        void tick() override
         {
-            this->::SenseShift::Input::MemoizedSensor<::SenseShift::Battery::BatteryState>::updateValue();
+            this->::SenseShift::Input::MemoizedSensor<::SenseShift::Battery::BatteryState>::tick();
             this->eventDispatcher->postEvent(new ::SenseShift::Battery::BatteryLevelEvent(this->value));
         }
 
diff --git a/lib/calibration/senseshift/calibration.hpp b/lib/calibration/senseshift/calibration.hpp
index 58c190c0..13a9834b 100644
--- a/lib/calibration/senseshift/calibration.hpp
+++ b/lib/calibration/senseshift/calibration.hpp
@@ -20,9 +20,7 @@ namespace SenseShift::Calibration {
 
       public:
         virtual void resetCalibration() = 0;
-
         void enableCalibration() override { calibrate = true; }
-
         void disableCalibration() override { calibrate = false; }
     };
 
diff --git a/lib/core/senseshift/interface.hpp b/lib/core/senseshift/interface.hpp
new file mode 100644
index 00000000..af428cc6
--- /dev/null
+++ b/lib/core/senseshift/interface.hpp
@@ -0,0 +1,11 @@
+#pragma once
+
+namespace SenseShift {
+    struct IInitializable {
+        virtual void init() = 0;
+    };
+
+    struct ITickable {
+        virtual void tick() = 0;
+    };
+} // namespace SenseShift
diff --git a/lib/freertos/senseshift/freertos/input/sensor.hpp b/lib/freertos/senseshift/freertos/input/sensor.hpp
index b4b273b8..cf7a1f45 100644
--- a/lib/freertos/senseshift/freertos/input/sensor.hpp
+++ b/lib/freertos/senseshift/freertos/input/sensor.hpp
@@ -13,7 +13,7 @@ namespace SenseShift::FreeRTOS::Input {
         friend class Task<SensorUpdateTask>;
 
       private:
-        using Sensor_t = ::SenseShift::Input::IMemoizedSensor;
+        using Sensor_t = ::SenseShift::ITickable;
 
       public:
         SensorUpdateTask(Sensor_t* sensor, std::uint32_t updateDelay, TaskConfig taskConfig) :
@@ -26,14 +26,14 @@ namespace SenseShift::FreeRTOS::Input {
         void run()
         {
             while (true) {
-                this->sensor->updateValue();
+                this->sensor->tick();
                 ::delay(this->updateDelay);
             }
         }
     };
 
     template<typename _Tp>
-    class TaskedSensor : public SensorUpdateTask, public ::SenseShift::Input::ISensor<_Tp> {
+    class TaskedSensor : public SensorUpdateTask, public ::SenseShift::Input::ISimpleSensor<_Tp> {
         friend class SensorUpdateTask;
 
       private:
@@ -45,11 +45,11 @@ namespace SenseShift::FreeRTOS::Input {
 
         void begin() override
         {
-            this->setup();
+            this->init();
             SensorUpdateTask::begin();
         };
 
-        void setup() override { this->sensor->setup(); };
+        void init() override { this->sensor->init(); };
 
         _Tp getValue() override { return this->sensor->getValue(); };
 
diff --git a/lib/opengloves/og_protocol.hpp b/lib/opengloves/og_protocol.hpp
index 9c92e98f..0e863109 100644
--- a/lib/opengloves/og_protocol.hpp
+++ b/lib/opengloves/og_protocol.hpp
@@ -45,7 +45,7 @@ namespace OpenGloves {
       public:
         IStringEncodedMemoizedSensor(Type type) : IStringEncoded(type){};
 
-        virtual void setup() = 0;
+        virtual void init() = 0;
         virtual void updateValue() = 0;
     };
 
diff --git a/lib/opengloves/sensor/og_finger.hpp b/lib/opengloves/sensor/og_finger.hpp
index 9b5193d3..1be50ec0 100644
--- a/lib/opengloves/sensor/og_finger.hpp
+++ b/lib/opengloves/sensor/og_finger.hpp
@@ -6,19 +6,19 @@
 
 namespace OpenGloves {
     struct FingerSensors {
-        std::vector<SenseShift::Input::CalibratedSensor<uint16_t>*> curl =
-          std::vector<SenseShift::Input::CalibratedSensor<uint16_t>*>();
-        std::optional<SenseShift::Input::CalibratedSensor<uint16_t>*> splay = std::nullopt;
+        std::vector<SenseShift::Input::CalibratedSimpleSensor<uint16_t>*> curl =
+          std::vector<SenseShift::Input::CalibratedSimpleSensor<uint16_t>*>();
+        std::optional<SenseShift::Input::CalibratedSimpleSensor<uint16_t>*> splay = std::nullopt;
 
         FingerSensors(
-          std::vector<SenseShift::Input::CalibratedSensor<uint16_t>*> curl,
-          std::optional<SenseShift::Input::CalibratedSensor<uint16_t>*> splay = std::nullopt
+          std::vector<SenseShift::Input::CalibratedSimpleSensor<uint16_t>*> curl,
+          std::optional<SenseShift::Input::CalibratedSimpleSensor<uint16_t>*> splay = std::nullopt
         ) :
           curl(curl), splay(splay){};
 
         FingerSensors(
-          SenseShift::Input::CalibratedSensor<uint16_t>* curl1,
-          std::optional<SenseShift::Input::CalibratedSensor<uint16_t>*> splay = std::nullopt
+          SenseShift::Input::CalibratedSimpleSensor<uint16_t>* curl1,
+          std::optional<SenseShift::Input::CalibratedSimpleSensor<uint16_t>*> splay = std::nullopt
         ) :
           curl({ curl1 }), splay(splay){};
     };
@@ -31,25 +31,25 @@ namespace OpenGloves {
         virtual uint16_t getCurl() = 0;
     };
 
-    typedef SenseShift::Input::ISensor<FingerValue> IFingerSensor;
+    typedef SenseShift::Input::ISimpleSensor<FingerValue> IFingerSensor;
 
     class SimpleFingerSensor : public IFingerSensor, public ICurl {
       public:
         SimpleFingerSensor(FingerSensors sensors) : sensors(sensors){};
 
         SimpleFingerSensor(
-          SenseShift::Input::CalibratedSensor<uint16_t>* curl1,
-          std::optional<SenseShift::Input::CalibratedSensor<uint16_t>*> splay = std::nullopt
+          SenseShift::Input::CalibratedSimpleSensor<uint16_t>* curl1,
+          std::optional<SenseShift::Input::CalibratedSimpleSensor<uint16_t>*> splay = std::nullopt
         ) :
           sensors(curl1, splay){};
 
-        void setup() override
+        void init() override
         {
             for (auto sensor : sensors.curl) {
-                sensor->setup();
+                sensor->init();
             }
             if (sensors.splay.has_value()) {
-                sensors.splay.value()->setup();
+                sensors.splay.value()->init();
             }
         }
 
@@ -82,8 +82,8 @@ namespace OpenGloves {
         CalibratedFingerSensor(FingerSensors sensors) : SimpleFingerSensor(sensors){};
 
         CalibratedFingerSensor(
-          SenseShift::Input::CalibratedSensor<uint16_t>* curl1,
-          std::optional<SenseShift::Input::CalibratedSensor<uint16_t>*> splay = std::nullopt
+          SenseShift::Input::CalibratedSimpleSensor<uint16_t>* curl1,
+          std::optional<SenseShift::Input::CalibratedSimpleSensor<uint16_t>*> splay = std::nullopt
         ) :
           SimpleFingerSensor(curl1, splay){};
 
@@ -127,13 +127,13 @@ namespace OpenGloves {
           StringEncodedMemoizedSensor<FingerValue>(sensor, type){};
 
         FingerSensor(
-          SenseShift::Input::CalibratedSensor<uint16_t>* curl1,
-          std::optional<SenseShift::Input::CalibratedSensor<uint16_t>*> splay,
+          SenseShift::Input::CalibratedSimpleSensor<uint16_t>* curl1,
+          std::optional<SenseShift::Input::CalibratedSimpleSensor<uint16_t>*> splay,
           IEncodedInput::Type type
         ) :
           StringEncodedMemoizedSensor<FingerValue>(new CalibratedFingerSensor(curl1, splay), type){};
 
-        FingerSensor(SenseShift::Input::CalibratedSensor<uint16_t>* curl1, IEncodedInput::Type type) :
+        FingerSensor(SenseShift::Input::CalibratedSimpleSensor<uint16_t>* curl1, IEncodedInput::Type type) :
           StringEncodedMemoizedSensor<FingerValue>(new CalibratedFingerSensor(curl1, std::nullopt), type){};
 
         void resetCalibration() override { static_cast<CalibratedFingerSensor*>(this->sensor)->resetCalibration(); }
diff --git a/lib/opengloves/sensor/og_gesture.hpp b/lib/opengloves/sensor/og_gesture.hpp
index 324cdb30..9bb0e375 100644
--- a/lib/opengloves/sensor/og_gesture.hpp
+++ b/lib/opengloves/sensor/og_gesture.hpp
@@ -3,7 +3,7 @@
 #include <sensor/og_finger.hpp>
 
 namespace OpenGloves {
-    class Gesture : public SenseShift::Input::ISensor<bool> {};
+    class Gesture : public SenseShift::Input::ISimpleSensor<bool> {};
 
     class GrabGesture : public Gesture {
       private:
@@ -17,7 +17,7 @@ namespace OpenGloves {
         GrabGesture(ICurl& index, ICurl& middle, ICurl& ring, ICurl& pinky, uint16_t threshold) :
           index(index), middle(middle), ring(ring), pinky(pinky), threshold(threshold){};
 
-        void setup() override{};
+        void init() override{};
 
         bool getValue() override
         {
@@ -34,7 +34,7 @@ namespace OpenGloves {
       public:
         TriggerGesture(ICurl& index, uint16_t threshold) : index(index), threshold(threshold){};
 
-        void setup() override{};
+        void init() override{};
 
         bool getValue() override { return this->index.getCurl() > this->threshold; }
     };
@@ -49,7 +49,7 @@ namespace OpenGloves {
         PinchGesture(ICurl& index, ICurl& thumb, uint16_t threshold) :
           index(index), thumb(thumb), threshold(threshold){};
 
-        void setup() override{};
+        void init() override{};
 
         bool getValue() override
         {
diff --git a/lib/opengloves/sensor/og_sensor.hpp b/lib/opengloves/sensor/og_sensor.hpp
index 9fdd4a71..42c722de 100644
--- a/lib/opengloves/sensor/og_sensor.hpp
+++ b/lib/opengloves/sensor/og_sensor.hpp
@@ -30,10 +30,10 @@ namespace OpenGloves {
       public IStringEncodedMemoizedSensor,
       public SenseShift::Input::MemoizedSensor<_Tp> {
       public:
-        StringEncodedMemoizedSensor(SenseShift::Input::ISensor<_Tp>* sensor, IEncodedInput::Type type) :
+        StringEncodedMemoizedSensor(SenseShift::Input::ISimpleSensor<_Tp>* sensor, IEncodedInput::Type type) :
           IStringEncodedMemoizedSensor(type), SenseShift::Input::MemoizedSensor<_Tp>(sensor){};
 
-        void setup() override { this->sensor->setup(); }
+        void init() override { this->sensor->init(); }
 
         void updateValue() override { this->value = this->sensor->getValue(); }
 
diff --git a/lib/opengloves_task/opengloves_task.hpp b/lib/opengloves_task/opengloves_task.hpp
index 8fab0ec0..630d6d8a 100644
--- a/lib/opengloves_task/opengloves_task.hpp
+++ b/lib/opengloves_task/opengloves_task.hpp
@@ -160,7 +160,7 @@ namespace OpenGloves {
             log_d("There is a total of %d sensors", this->allSensors.size());
             for (auto* input : this->allSensors) {
                 log_d("Setting up sensor: %c", input->getType());
-                input->setup();
+                input->init();
             }
 
             // Start calibration if no calibration button is present or if configured to always calibrate.
diff --git a/lib/sensor/senseshift/input/sensor.hpp b/lib/sensor/senseshift/input/sensor.hpp
index 8de2a7bb..a4f5e160 100644
--- a/lib/sensor/senseshift/input/sensor.hpp
+++ b/lib/sensor/senseshift/input/sensor.hpp
@@ -3,6 +3,7 @@
 #include <type_traits>
 
 #include <senseshift/calibration.hpp>
+#include <senseshift/interface.hpp>
 #include <senseshift/logging.hpp>
 
 #if defined(__AVR__)
@@ -22,93 +23,89 @@ namespace SenseShift::Input {
      * @tparam _Tp Type of the sensor value
      */
     template<typename _Tp>
-    class ISensor {
-      public:
-        /**
-         * Setup the sensor hardware
-         */
-        virtual void setup() = 0;
+    struct ISimpleSensor : public virtual IInitializable {
+        using ValueType = _Tp;
 
         /**
          * Get the current sensor value
          */
-        virtual _Tp getValue() = 0;
+        virtual ValueType getValue() = 0;
     };
 
-    class IMemoizedSensor {
-      public:
-        /**
-         * Update the memoized value
-         */
-        virtual void updateValue() = 0;
-    };
+    template<typename _Tp>
+    struct ISensor : public virtual ISimpleSensor<_Tp>, ITickable {};
 
     /**
      * Memoized sensor decorator
      * @tparam _Tp Type of the sensor value
      */
     template<typename _Tp>
-    class MemoizedSensor : public ISensor<_Tp>, public virtual IMemoizedSensor {
+    class MemoizedSensor : public ISensor<_Tp> {
       protected:
-        ISensor<_Tp>* sensor;
+        ISimpleSensor<_Tp>* sensor;
         _Tp value;
 
       public:
         /**
          * @param sensor Sensor to be decorated
          */
-        MemoizedSensor(ISensor<_Tp>* sensor) : sensor(sensor){};
+        MemoizedSensor(ISimpleSensor<_Tp>* sensor) : sensor(sensor){};
 
         /**
          * Setup the sensor hardware
          */
-        void setup() override { this->sensor->setup(); };
+        void init() override { this->sensor->init(); };
 
         /**
-         * Get the current memoized value
+         * Read actual value from the hardware and memoize it
          */
-        _Tp getValue() override { return this->value; };
+        void tick() override { this->value = this->sensor->getValue(); };
 
         /**
-         * Read actual value from the hardware and memoize it
+         * Get the current memoized value
          */
-        void updateValue() { this->value = this->sensor->getValue(); };
+        _Tp getValue() override { return this->value; };
     };
 
+    template<typename _Tp>
+    class ICalibratedSimpleSensor : public ISimpleSensor<_Tp>, public Calibration::ICalibrated {};
+
     /**
      * Calibrated sensor decorator
      *
      * @tparam _Tp Type of the sensor value
      */
     template<typename _Tp>
-    class CalibratedSensor : public ISensor<_Tp>, public ::SenseShift::Calibration::Calibrated {
+    class CalibratedSimpleSensor : public ICalibratedSimpleSensor<_Tp> {
+      public:
+        /**
+         * @param sensor Sensor to be decorated
+         * @param calibrator ICalibrator algorithm to be used
+         */
+        CalibratedSimpleSensor(ISimpleSensor<_Tp>* sensor, Calibration::ICalibrator<_Tp>* calibrator) :
+          sensor(sensor), calibrator(calibrator){};
+
+        void init() override { this->sensor->init(); };
+        _Tp getValue() override { return this->getCalibratedValue(); };
+
+        void resetCalibration() override { this->calibrator->reset(); };
+        void enableCalibration() override { calibrating = true; }
+        void disableCalibration() override { calibrating = false; }
+
       protected:
-        ISensor<_Tp>* sensor;
-        ::SenseShift::Calibration::ICalibrator<_Tp>* calibrator;
+        ISimpleSensor<_Tp>* sensor;
+        Calibration::ICalibrator<_Tp>* calibrator;
+        bool calibrating = false;
 
         _Tp getCalibratedValue()
         {
             auto value = this->sensor->getValue();
 
-            if (this->calibrate) {
+            if (this->calibrating) {
                 this->calibrator->update(value);
             }
 
             return this->calibrator->calibrate(value);
         }
-
-      public:
-        /**
-         * @param sensor Sensor to be decorated
-         * @param calibrator ICalibrator algorithm to be used
-         */
-        CalibratedSensor(ISensor<_Tp>* sensor, ::SenseShift::Calibration::ICalibrator<_Tp>* calibrator) :
-          sensor(sensor), calibrator(calibrator){};
-
-        void setup() override { this->sensor->setup(); };
-
-        _Tp getValue() override { return this->getCalibratedValue(); };
-
-        void resetCalibration() override { this->calibrator->reset(); };
     };
 } // namespace SenseShift::Input
diff --git a/lib/sensor/senseshift/input/sensor/joystick.hpp b/lib/sensor/senseshift/input/sensor/joystick.hpp
index b9d28519..a03af6f7 100644
--- a/lib/sensor/senseshift/input/sensor/joystick.hpp
+++ b/lib/sensor/senseshift/input/sensor/joystick.hpp
@@ -7,9 +7,9 @@ namespace SenseShift::Input {
      * Joystick axis sensor decorator
      */
     template<typename _Tp>
-    class JoystickAxisSensor : public ISensor<_Tp> {
+    class JoystickAxisSensor : public ISimpleSensor<_Tp> {
       private:
-        ISensor<_Tp>* sensor;
+        ISimpleSensor<_Tp>* sensor;
         float dead_zone;
 
         int filterDeadZone(int in)
@@ -22,9 +22,9 @@ namespace SenseShift::Input {
         }
 
       public:
-        JoystickAxisSensor(ISensor<_Tp>* sensor, float dead_zone) : sensor(sensor), dead_zone(dead_zone){};
+        JoystickAxisSensor(ISimpleSensor<_Tp>* sensor, float dead_zone) : sensor(sensor), dead_zone(dead_zone){};
 
-        void setup(void) { this->sensor->setup(); };
+        void init() override { this->sensor->init(); };
 
         uint16_t getValue(void) override
         {
diff --git a/test/test_opengloves/main.cpp b/test/test_opengloves/main.cpp
index c711cdc1..7f607d6f 100644
--- a/test/test_opengloves/main.cpp
+++ b/test/test_opengloves/main.cpp
@@ -3,34 +3,34 @@
 
 using namespace OpenGloves;
 
-class TestAnalogSensor : public SenseShift::Input::ISensor<uint16_t> {
+class TestAnalogSensor : public SenseShift::Input::ISimpleSensor<uint16_t> {
   private:
     uint16_t count = 0;
 
   public:
     int setupCounter = 0;
 
-    void setup() override { this->setupCounter++; };
+    void init() override { this->setupCounter++; };
 
     uint16_t getValue() override { return ++this->count; };
 };
 
-class TestBinarySensor : public SenseShift::Input::ISensor<bool> {
+class TestBinarySensor : public SenseShift::Input::ISimpleSensor<bool> {
   public:
     bool value = false;
     int setupCounter = 0;
 
-    void setup() override { this->setupCounter++; };
+    void init() override { this->setupCounter++; };
 
     bool getValue() override { return this->value; };
 };
 
-class TestFingerSensor : public SenseShift::Input::ISensor<FingerValue> {
+class TestFingerSensor : public SenseShift::Input::ISimpleSensor<FingerValue> {
   public:
     FingerValue value;
     int setupCounter = 0;
 
-    void setup() override { this->setupCounter++; };
+    void init() override { this->setupCounter++; };
 
     FingerValue getValue() override { return this->value; };
 };
@@ -41,7 +41,7 @@ void test_string_encoded_sensor_uint16(void)
     auto sensor = new StringEncodedMemoizedSensor<uint16_t>(inner, IEncodedInput::Type::INDEX);
 
     TEST_ASSERT_EQUAL_INT(0, inner->setupCounter);
-    sensor->setup();
+    sensor->init();
     TEST_ASSERT_EQUAL_INT(1, inner->setupCounter);
 
     TEST_ASSERT_EQUAL_INT(0, sensor->getValue());
@@ -63,7 +63,7 @@ void test_string_encoded_sensor_bool(void)
     auto sensor = new StringEncodedMemoizedSensor<bool>(inner, IEncodedInput::Type::A_BTN);
 
     TEST_ASSERT_EQUAL_INT(0, inner->setupCounter);
-    sensor->setup();
+    sensor->init();
     TEST_ASSERT_EQUAL_INT(1, inner->setupCounter);
 
     TEST_ASSERT_FALSE(sensor->getValue());
@@ -94,7 +94,7 @@ void test_string_encoded_sensor_fingervalue(void)
     auto sensor = new StringEncodedMemoizedSensor<FingerValue>(inner, IEncodedInput::Type::THUMB);
 
     TEST_ASSERT_EQUAL_INT(0, inner->setupCounter);
-    sensor->setup();
+    sensor->init();
     TEST_ASSERT_EQUAL_INT(1, inner->setupCounter);
 
     // curl-only
diff --git a/test/test_opengloves_finger/main.cpp b/test/test_opengloves_finger/main.cpp
index ee063582..569bcda8 100644
--- a/test/test_opengloves_finger/main.cpp
+++ b/test/test_opengloves_finger/main.cpp
@@ -4,14 +4,14 @@
 using namespace OpenGloves;
 using namespace SenseShift::Calibration;
 
-class TestAnalogSensor : public SenseShift::Input::ISensor<uint16_t> {
+class TestAnalogSensor : public SenseShift::Input::ISimpleSensor<uint16_t> {
   private:
     uint16_t count = 0;
 
   public:
     int setupCounter = 0;
 
-    void setup() override { this->setupCounter++; };
+    void init() override { this->setupCounter++; };
 
     uint16_t getValue() override { return this->count++; };
 };
@@ -34,11 +34,11 @@ void test_simple_finger_sensor_curl(void)
 {
     auto* inner = new TestAnalogSensor();
     auto* calibrator = new DummyCalibrator();
-    auto* calibrated = new SenseShift::Input::CalibratedSensor<uint16_t>(inner, calibrator);
+    auto* calibrated = new SenseShift::Input::CalibratedSimpleSensor<uint16_t>(inner, calibrator);
     auto* sensor = new SimpleFingerSensor(calibrated);
 
     TEST_ASSERT_EQUAL_INT(0, inner->setupCounter);
-    sensor->setup();
+    sensor->init();
     TEST_ASSERT_EQUAL_INT(1, inner->setupCounter);
 
     // since the sensor is not memoized, the value is updated on every call
@@ -60,17 +60,17 @@ void test_simple_finger_sensor_curl_flex(void)
 {
     auto* inner_curl = new TestAnalogSensor();
     auto* calibrator_curl = new DummyCalibrator();
-    auto* calibrated_curl = new SenseShift::Input::CalibratedSensor<uint16_t>(inner_curl, calibrator_curl);
+    auto* calibrated_curl = new SenseShift::Input::CalibratedSimpleSensor<uint16_t>(inner_curl, calibrator_curl);
 
     auto* inner_flex = new TestAnalogSensor();
     auto* calibrator_flex = new DummyCalibrator();
-    auto* calibrated_flex = new SenseShift::Input::CalibratedSensor<uint16_t>(inner_flex, calibrator_flex);
+    auto* calibrated_flex = new SenseShift::Input::CalibratedSimpleSensor<uint16_t>(inner_flex, calibrator_flex);
 
     auto* sensor = new SimpleFingerSensor(calibrated_curl, calibrated_flex);
 
     TEST_ASSERT_EQUAL_INT(0, inner_curl->setupCounter);
     TEST_ASSERT_EQUAL_INT(0, inner_flex->setupCounter);
-    sensor->setup();
+    sensor->init();
     TEST_ASSERT_EQUAL_INT(1, inner_curl->setupCounter);
     TEST_ASSERT_EQUAL_INT(1, inner_flex->setupCounter);
 
@@ -100,11 +100,11 @@ void test_finger_sensor_curl(void)
 {
     auto* inner = new TestAnalogSensor();
     auto* calibrator = new DummyCalibrator();
-    auto* calibrated = new SenseShift::Input::CalibratedSensor<uint16_t>(inner, calibrator);
+    auto* calibrated = new SenseShift::Input::CalibratedSimpleSensor<uint16_t>(inner, calibrator);
     auto* sensor = new FingerSensor(calibrated, IEncodedInput::Type::INDEX);
 
     TEST_ASSERT_EQUAL_INT(0, inner->setupCounter);
-    sensor->setup();
+    sensor->init();
     TEST_ASSERT_EQUAL_INT(1, inner->setupCounter);
 
     TEST_ASSERT_EQUAL_INT(0, sensor->getValue().curl[0]);
diff --git a/test/test_sensor/main.cpp b/test/test_sensor/main.cpp
index f9cbcea0..c0b75acb 100644
--- a/test/test_sensor/main.cpp
+++ b/test/test_sensor/main.cpp
@@ -4,14 +4,14 @@
 using namespace SenseShift::Input;
 using namespace SenseShift::Calibration;
 
-class TestAnalogSensor : public ISensor<int> {
+class TestAnalogSensor : public ISimpleSensor<int> {
   private:
     int count = 0;
 
   public:
     int setupCounter = 0;
 
-    void setup() override { this->setupCounter++; };
+    void init() override { this->setupCounter++; };
 
     int getValue() override { return ++this->count; };
 };
@@ -22,13 +22,13 @@ void test_memoized_sensor(void)
     auto sensor = new MemoizedSensor<int>(inner);
 
     TEST_ASSERT_EQUAL_INT(0, inner->setupCounter);
-    sensor->setup();
+    sensor->init();
     TEST_ASSERT_EQUAL_INT(1, inner->setupCounter);
 
     TEST_ASSERT_EQUAL_INT(0, sensor->getValue());
     TEST_ASSERT_EQUAL_INT(0, sensor->getValue());
 
-    sensor->updateValue();
+    sensor->tick();
 
     TEST_ASSERT_EQUAL_INT(1, sensor->getValue());
     TEST_ASSERT_EQUAL_INT(1, sensor->getValue());
@@ -52,10 +52,10 @@ void test_calibrated_sensor(void)
 {
     auto inner = new TestAnalogSensor();
     auto calibrator = new DummyCalibrator();
-    auto sensor = new CalibratedSensor<int>(inner, calibrator);
+    auto sensor = new CalibratedSimpleSensor<int>(inner, calibrator);
 
     TEST_ASSERT_EQUAL_INT(0, inner->setupCounter);
-    sensor->setup();
+    sensor->init();
     TEST_ASSERT_EQUAL_INT(1, inner->setupCounter);
 
     calibrator->update(-1);

From 985e4c01f69a4ef8f65815de7f770205c0132465 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Wed, 30 Aug 2023 19:42:26 +0400
Subject: [PATCH 22/82] refactor: use advanced PlatformIO LDF

---
 examples/bhaptics-ble-bt-serial.cpp           |  2 +-
 firmware/mode_configs/bhaptics/tactal.cpp     |  7 ++--
 firmware/mode_configs/bhaptics/tactglove.cpp  | 17 +++++-----
 firmware/mode_configs/bhaptics/tactosy2.cpp   |  9 +++---
 firmware/mode_configs/bhaptics/tactosyf.cpp   | 11 +++----
 firmware/mode_configs/bhaptics/tactosyh.cpp   | 11 +++----
 .../mode_configs/bhaptics/tactsuit_x16.cpp    | 13 ++++----
 .../bhaptics/tactsuit_x16_pca9685.cpp         | 13 ++++----
 .../mode_configs/bhaptics/tactsuit_x40.cpp    | 27 ++++++++--------
 firmware/mode_configs/bhaptics/tactvisor.cpp  |  7 ++--
 .../mode_configs/opengloves/opengloves.cpp    | 12 +++----
 lib/arduino/library.json                      | 13 ++++++++
 .../senseshift/arduino/battery/ina219.hpp     |  3 +-
 .../senseshift/arduino/battery/max17048.hpp   |  4 +--
 .../arduino/output/actuator}/pca9685.hpp      |  4 +--
 .../arduino/output/{ => actuator}/pwm.hpp     |  4 +--
 .../arduino/output/{ => actuator}/servo.hpp   |  4 +--
 lib/battery/senseshift/battery/sensor.hpp     | 16 ++++++++++
 .../senseshift/freertos/battery.hpp           | 22 -------------
 lib/bhaptics_ble/library.json                 | 10 ++++++
 lib/core/senseshift/events.hpp                |  4 +++
 lib/core/senseshift/logging.hpp               |  2 ++
 .../senseshift/input/sensor.hpp               |  0
 .../senseshift/input/sensor/joystick.hpp      |  0
 .../senseshift/output/actuator.hpp            |  0
 .../senseshift/calibration.hpp                |  0
 .../senseshift/container.hpp}                 | 32 +++++++------------
 lib/util/senseshift/range.hpp                 | 30 +++++++++++++++++
 lib/util/senseshift/utility.hpp               |  8 +++++
 platformio.ini                                | 13 +-------
 test/{test_sensor => test_io_sensor}/main.cpp |  0
 .../{test_core_utility => test_util}/main.cpp |  1 +
 .../main.cpp                                  |  0
 33 files changed, 166 insertions(+), 133 deletions(-)
 create mode 100644 lib/arduino/library.json
 rename lib/{ina219 => arduino}/senseshift/arduino/battery/ina219.hpp (92%)
 rename lib/{max17048 => arduino}/senseshift/arduino/battery/max17048.hpp (92%)
 rename lib/{pca9685/senseshift/arduino/output => arduino/senseshift/arduino/output/actuator}/pca9685.hpp (75%)
 rename lib/arduino/senseshift/arduino/output/{ => actuator}/pwm.hpp (86%)
 rename lib/arduino/senseshift/arduino/output/{ => actuator}/servo.hpp (87%)
 delete mode 100644 lib/battery_task/senseshift/freertos/battery.hpp
 create mode 100644 lib/bhaptics_ble/library.json
 rename lib/{sensor => io}/senseshift/input/sensor.hpp (100%)
 rename lib/{sensor => io}/senseshift/input/sensor/joystick.hpp (100%)
 rename lib/{actuators => io}/senseshift/output/actuator.hpp (100%)
 rename lib/{calibration => util}/senseshift/calibration.hpp (100%)
 rename lib/{core/senseshift/utility.hpp => util/senseshift/container.hpp} (54%)
 create mode 100644 lib/util/senseshift/range.hpp
 create mode 100644 lib/util/senseshift/utility.hpp
 rename test/{test_sensor => test_io_sensor}/main.cpp (100%)
 rename test/{test_core_utility => test_util}/main.cpp (99%)
 rename test/{test_calibration => test_util_calibration}/main.cpp (100%)

diff --git a/examples/bhaptics-ble-bt-serial.cpp b/examples/bhaptics-ble-bt-serial.cpp
index 68dd1482..3a532691 100644
--- a/examples/bhaptics-ble-bt-serial.cpp
+++ b/examples/bhaptics-ble-bt-serial.cpp
@@ -34,7 +34,7 @@ void setupMode()
     // Configure PWM pins to their positions on the face
     auto faceOutputs = PlaneMapper_Margin::mapMatrixCoordinates<AbstractActuator>({
       // clang-format off
-      {new PWMOutputWriter(32), new PWMOutputWriter(33), new PWMOutputWriter(25), new PWMOutputWriter(26), new PWMOutputWriter(27), new PWMOutputWriter(14)},
+      {new ActuatorPWM(32), new ActuatorPWM(33), new ActuatorPWM(25), new ActuatorPWM(26), new ActuatorPWM(27), new ActuatorPWM(14)},
       // clang-format on
     });
 
diff --git a/firmware/mode_configs/bhaptics/tactal.cpp b/firmware/mode_configs/bhaptics/tactal.cpp
index f19d9e7d..2c99a8fc 100644
--- a/firmware/mode_configs/bhaptics/tactal.cpp
+++ b/firmware/mode_configs/bhaptics/tactal.cpp
@@ -7,17 +7,16 @@
 #include "senseshift.h"
 
 #include <senseshift/arduino/input/sensor/analog.hpp>
-#include <senseshift/arduino/output/pwm.hpp>
+#include <senseshift/arduino/output/actuator/pwm.hpp>
 #include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
-#include <senseshift/freertos/battery.hpp>
+#include <senseshift/freertos/input/sensor.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
-using namespace SenseShift::FreeRTOS::Battery;
 using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
@@ -34,7 +33,7 @@ void setupMode()
     // Configure PWM pins to their positions on the face
     const auto faceOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       // clang-format off
-      { new PWMOutputWriter(32), new PWMOutputWriter(33), new PWMOutputWriter(25), new PWMOutputWriter(26), new PWMOutputWriter(27), new PWMOutputWriter(14) },
+      { new ActuatorPWM(32), new ActuatorPWM(33), new ActuatorPWM(25), new ActuatorPWM(26), new ActuatorPWM(27), new ActuatorPWM(14) },
       // clang-format on
     });
 
diff --git a/firmware/mode_configs/bhaptics/tactglove.cpp b/firmware/mode_configs/bhaptics/tactglove.cpp
index 8b123ef4..8d2138f6 100644
--- a/firmware/mode_configs/bhaptics/tactglove.cpp
+++ b/firmware/mode_configs/bhaptics/tactglove.cpp
@@ -7,18 +7,17 @@
 #include "senseshift.h"
 
 #include <senseshift/arduino/input/sensor/analog.hpp>
-#include <senseshift/arduino/output/pwm.hpp>
+#include <senseshift/arduino/output/actuator/pwm.hpp>
 #include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
-#include <senseshift/freertos/battery.hpp>
+#include <senseshift/freertos/input/sensor.hpp>
 #include <senseshift/utility.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
-using namespace SenseShift::FreeRTOS::Battery;
 using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
@@ -40,12 +39,12 @@ void setupMode()
     addTactGloveActuators(
       app->getHapticBody(),
       handSide,
-      new PWMOutputWriter(32), // Thumb
-      new PWMOutputWriter(33), // Index
-      new PWMOutputWriter(25), // Middle
-      new PWMOutputWriter(26), // Ring
-      new PWMOutputWriter(27), // Little
-      new PWMOutputWriter(14)  // Wrist
+      new ActuatorPWM(32), // Thumb
+      new ActuatorPWM(33), // Index
+      new ActuatorPWM(25), // Middle
+      new ActuatorPWM(26), // Ring
+      new ActuatorPWM(27), // Little
+      new ActuatorPWM(14)  // Wrist
     );
 
     app->getHapticBody()->setup();
diff --git a/firmware/mode_configs/bhaptics/tactosy2.cpp b/firmware/mode_configs/bhaptics/tactosy2.cpp
index 682a349b..38743a78 100644
--- a/firmware/mode_configs/bhaptics/tactosy2.cpp
+++ b/firmware/mode_configs/bhaptics/tactosy2.cpp
@@ -7,17 +7,16 @@
 #include "senseshift.h"
 
 #include <senseshift/arduino/input/sensor/analog.hpp>
-#include <senseshift/arduino/output/pwm.hpp>
+#include <senseshift/arduino/output/actuator/pwm.hpp>
 #include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
-#include <senseshift/freertos/battery.hpp>
+#include <senseshift/freertos/input/sensor.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
-using namespace SenseShift::FreeRTOS::Battery;
 using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
@@ -34,8 +33,8 @@ void setupMode()
     // Configure PWM pins to their positions on the forearm
     auto forearmOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       // clang-format off
-      { new PWMOutputWriter(32), new PWMOutputWriter(33), new PWMOutputWriter(25) },
-      { new PWMOutputWriter(26), new PWMOutputWriter(27), new PWMOutputWriter(14) },
+      { new ActuatorPWM(32), new ActuatorPWM(33), new ActuatorPWM(25) },
+      { new ActuatorPWM(26), new ActuatorPWM(27), new ActuatorPWM(14) },
       // clang-format on
     });
 
diff --git a/firmware/mode_configs/bhaptics/tactosyf.cpp b/firmware/mode_configs/bhaptics/tactosyf.cpp
index 99b792fd..0dd2733f 100644
--- a/firmware/mode_configs/bhaptics/tactosyf.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyf.cpp
@@ -7,17 +7,16 @@
 #include "senseshift.h"
 
 #include <senseshift/arduino/input/sensor/analog.hpp>
-#include <senseshift/arduino/output/pwm.hpp>
+#include <senseshift/arduino/output/actuator/pwm.hpp>
 #include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
-#include <senseshift/freertos/battery.hpp>
+#include <senseshift/freertos/input/sensor.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
-using namespace SenseShift::FreeRTOS::Battery;
 using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
@@ -34,9 +33,9 @@ void setupMode()
     // Configure PWM pins to their positions on the feet
     auto footOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       // clang-format off
-      { new PWMOutputWriter(32) },
-      { new PWMOutputWriter(33) },
-      { new PWMOutputWriter(25) },
+      { new ActuatorPWM(32) },
+      { new ActuatorPWM(33) },
+      { new ActuatorPWM(25) },
       // clang-format on
     });
 
diff --git a/firmware/mode_configs/bhaptics/tactosyh.cpp b/firmware/mode_configs/bhaptics/tactosyh.cpp
index 0e3cc417..ae3de9da 100644
--- a/firmware/mode_configs/bhaptics/tactosyh.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyh.cpp
@@ -7,17 +7,16 @@
 #include "senseshift.h"
 
 #include <senseshift/arduino/input/sensor/analog.hpp>
-#include <senseshift/arduino/output/pwm.hpp>
+#include <senseshift/arduino/output/actuator/pwm.hpp>
 #include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
-#include <senseshift/freertos/battery.hpp>
+#include <senseshift/freertos/input/sensor.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
-using namespace SenseShift::FreeRTOS::Battery;
 using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
@@ -34,9 +33,9 @@ void setupMode()
     // Configure PWM pins to their positions on the hands
     auto handOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       // clang-format off
-      { new PWMOutputWriter(32) },
-      { new PWMOutputWriter(33) },
-      { new PWMOutputWriter(25) }
+      { new ActuatorPWM(32) },
+      { new ActuatorPWM(33) },
+      { new ActuatorPWM(25) }
       // clang-format on
     });
 
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
index 8bc88899..88b46536 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
@@ -7,17 +7,16 @@
 #include "senseshift.h"
 
 #include <senseshift/arduino/input/sensor/analog.hpp>
-#include <senseshift/arduino/output/pwm.hpp>
+#include <senseshift/arduino/output/actuator/pwm.hpp>
 #include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
-#include <senseshift/freertos/battery.hpp>
+#include <senseshift/freertos/input/sensor.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
-using namespace SenseShift::FreeRTOS::Battery;
 using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
@@ -38,14 +37,14 @@ void setupMode()
     // Configure PWM pins to their positions on the vest
     auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       // clang-format off
-      { new PWMOutputWriter(32), new PWMOutputWriter(33), new PWMOutputWriter(25), new PWMOutputWriter(26) },
-      { new PWMOutputWriter(27), new PWMOutputWriter(14), new PWMOutputWriter(12), new PWMOutputWriter(13) },
+      { new ActuatorPWM(32), new ActuatorPWM(33), new ActuatorPWM(25), new ActuatorPWM(26) },
+      { new ActuatorPWM(27), new ActuatorPWM(14), new ActuatorPWM(12), new ActuatorPWM(13) },
       // clang-format on
     });
     auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       // clang-format off
-      { new PWMOutputWriter(19), new PWMOutputWriter(18), new PWMOutputWriter(5), new PWMOutputWriter(17) },
-      { new PWMOutputWriter(16), new PWMOutputWriter(4), new PWMOutputWriter(2), new PWMOutputWriter(15)  },
+      { new ActuatorPWM(19), new ActuatorPWM(18), new ActuatorPWM(5), new ActuatorPWM(17) },
+      { new ActuatorPWM(16), new ActuatorPWM(4), new ActuatorPWM(2), new ActuatorPWM(15)  },
       // clang-format on
     });
 
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
index f7a9b099..6782ecee 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
@@ -7,17 +7,16 @@
 #include "senseshift.h"
 
 #include <senseshift/arduino/input/sensor/analog.hpp>
-#include <senseshift/arduino/output/pca9685.hpp>
+#include <senseshift/arduino/output/actuator/pca9685.hpp>
 #include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
-#include <senseshift/freertos/battery.hpp>
+#include <senseshift/freertos/input/sensor.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
-using namespace SenseShift::FreeRTOS::Battery;
 using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
@@ -43,14 +42,14 @@ void setupMode()
     // Assign the pins on the configured PCA9685 to positions on the vest
     auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       // clang-format off
-      { new PCA9685OutputWriter(pwm, 0), new PCA9685OutputWriter(pwm, 1), new PCA9685OutputWriter(pwm, 2), new PCA9685OutputWriter(pwm, 3) },
-      { new PCA9685OutputWriter(pwm, 4), new PCA9685OutputWriter(pwm, 5), new PCA9685OutputWriter(pwm, 6), new PCA9685OutputWriter(pwm, 7) },
+      { new ActuatorPCA9685(pwm, 0), new ActuatorPCA9685(pwm, 1), new ActuatorPCA9685(pwm, 2), new ActuatorPCA9685(pwm, 3) },
+      { new ActuatorPCA9685(pwm, 4), new ActuatorPCA9685(pwm, 5), new ActuatorPCA9685(pwm, 6), new ActuatorPCA9685(pwm, 7) },
       // clang-format on
     });
     auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       // clang-format off
-      { new PCA9685OutputWriter(pwm, 8),  new PCA9685OutputWriter(pwm, 9),  new PCA9685OutputWriter(pwm, 10), new PCA9685OutputWriter(pwm, 11) },
-      { new PCA9685OutputWriter(pwm, 12), new PCA9685OutputWriter(pwm, 13), new PCA9685OutputWriter(pwm, 14), new PCA9685OutputWriter(pwm, 15) },
+      { new ActuatorPCA9685(pwm, 8),  new ActuatorPCA9685(pwm, 9),  new ActuatorPCA9685(pwm, 10), new ActuatorPCA9685(pwm, 11) },
+      { new ActuatorPCA9685(pwm, 12), new ActuatorPCA9685(pwm, 13), new ActuatorPCA9685(pwm, 14), new ActuatorPCA9685(pwm, 15) },
       // clang-format on
     });
 
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
index e8668bcf..318e6ef6 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
@@ -7,18 +7,17 @@
 #include "senseshift.h"
 
 #include <senseshift/arduino/input/sensor/analog.hpp>
-#include <senseshift/arduino/output/pca9685.hpp>
-#include <senseshift/arduino/output/pwm.hpp>
+#include <senseshift/arduino/output/actuator/pca9685.hpp>
+#include <senseshift/arduino/output/actuator/pwm.hpp>
 #include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
-#include <senseshift/freertos/battery.hpp>
+#include <senseshift/freertos/input/sensor.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
-using namespace SenseShift::FreeRTOS::Battery;
 using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
@@ -45,20 +44,20 @@ void setupMode()
     // vest
     auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       // clang-format off
-      { new PCA9685OutputWriter(pwm0, 0),  new PCA9685OutputWriter(pwm0, 1),  new PCA9685OutputWriter(pwm0, 2),  new PCA9685OutputWriter(pwm0, 3)  },
-      { new PCA9685OutputWriter(pwm0, 4),  new PCA9685OutputWriter(pwm0, 5),  new PCA9685OutputWriter(pwm0, 6),  new PCA9685OutputWriter(pwm0, 7)  },
-      { new PCA9685OutputWriter(pwm0, 8),  new PCA9685OutputWriter(pwm0, 9),  new PCA9685OutputWriter(pwm0, 10), new PCA9685OutputWriter(pwm0, 11) },
-      { new PCA9685OutputWriter(pwm0, 12), new PCA9685OutputWriter(pwm0, 13), new PCA9685OutputWriter(pwm0, 14), new PCA9685OutputWriter(pwm0, 15) },
-      { new PWMOutputWriter(32),           new PWMOutputWriter(33),           new PWMOutputWriter(25),           new PWMOutputWriter(26)           },
+      { new ActuatorPCA9685(pwm0, 0),  new ActuatorPCA9685(pwm0, 1),  new ActuatorPCA9685(pwm0, 2),  new ActuatorPCA9685(pwm0, 3)  },
+      { new ActuatorPCA9685(pwm0, 4),  new ActuatorPCA9685(pwm0, 5),  new ActuatorPCA9685(pwm0, 6),  new ActuatorPCA9685(pwm0, 7)  },
+      { new ActuatorPCA9685(pwm0, 8),  new ActuatorPCA9685(pwm0, 9),  new ActuatorPCA9685(pwm0, 10), new ActuatorPCA9685(pwm0, 11) },
+      { new ActuatorPCA9685(pwm0, 12), new ActuatorPCA9685(pwm0, 13), new ActuatorPCA9685(pwm0, 14), new ActuatorPCA9685(pwm0, 15) },
+      { new ActuatorPWM(32),           new ActuatorPWM(33),           new ActuatorPWM(25),           new ActuatorPWM(26)           },
       // clang-format on
     });
     auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       // clang-format off
-      { new PCA9685OutputWriter(pwm1, 0),  new PCA9685OutputWriter(pwm1, 1),  new PCA9685OutputWriter(pwm1, 2),  new PCA9685OutputWriter(pwm1, 3)  },
-      { new PCA9685OutputWriter(pwm1, 4),  new PCA9685OutputWriter(pwm1, 5),  new PCA9685OutputWriter(pwm1, 6),  new PCA9685OutputWriter(pwm1, 7)  },
-      { new PCA9685OutputWriter(pwm1, 8),  new PCA9685OutputWriter(pwm1, 9),  new PCA9685OutputWriter(pwm1, 10), new PCA9685OutputWriter(pwm1, 11) },
-      { new PCA9685OutputWriter(pwm1, 12), new PCA9685OutputWriter(pwm1, 13), new PCA9685OutputWriter(pwm1, 14), new PCA9685OutputWriter(pwm1, 15) },
-      { new PWMOutputWriter(27),           new PWMOutputWriter(14),           new PWMOutputWriter(12),           new PWMOutputWriter(13)           },
+      { new ActuatorPCA9685(pwm1, 0),  new ActuatorPCA9685(pwm1, 1),  new ActuatorPCA9685(pwm1, 2),  new ActuatorPCA9685(pwm1, 3)  },
+      { new ActuatorPCA9685(pwm1, 4),  new ActuatorPCA9685(pwm1, 5),  new ActuatorPCA9685(pwm1, 6),  new ActuatorPCA9685(pwm1, 7)  },
+      { new ActuatorPCA9685(pwm1, 8),  new ActuatorPCA9685(pwm1, 9),  new ActuatorPCA9685(pwm1, 10), new ActuatorPCA9685(pwm1, 11) },
+      { new ActuatorPCA9685(pwm1, 12), new ActuatorPCA9685(pwm1, 13), new ActuatorPCA9685(pwm1, 14), new ActuatorPCA9685(pwm1, 15) },
+      { new ActuatorPWM(27),           new ActuatorPWM(14),           new ActuatorPWM(12),           new ActuatorPWM(13)           },
       // clang-format on
     });
 
diff --git a/firmware/mode_configs/bhaptics/tactvisor.cpp b/firmware/mode_configs/bhaptics/tactvisor.cpp
index 68cd1188..758ab9aa 100644
--- a/firmware/mode_configs/bhaptics/tactvisor.cpp
+++ b/firmware/mode_configs/bhaptics/tactvisor.cpp
@@ -7,17 +7,16 @@
 #include "senseshift.h"
 
 #include <senseshift/arduino/input/sensor/analog.hpp>
-#include <senseshift/arduino/output/pwm.hpp>
+#include <senseshift/arduino/output/actuator/pwm.hpp>
 #include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
-#include <senseshift/freertos/battery.hpp>
+#include <senseshift/freertos/input/sensor.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
-using namespace SenseShift::FreeRTOS::Battery;
 using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
@@ -34,7 +33,7 @@ void setupMode()
     // Configure PWM pins to their positions on the face
     auto faceOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
       // clang-format off
-      { new PWMOutputWriter(32), new PWMOutputWriter(33), new PWMOutputWriter(25), new PWMOutputWriter(26) },
+      { new ActuatorPWM(32), new ActuatorPWM(33), new ActuatorPWM(25), new ActuatorPWM(26) },
       // clang-format on
     });
 
diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp
index 5af9b961..c58e221c 100644
--- a/firmware/mode_configs/opengloves/opengloves.cpp
+++ b/firmware/mode_configs/opengloves/opengloves.cpp
@@ -3,7 +3,7 @@
 #include <opengloves_task.hpp>
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/input/sensor/digital.hpp>
-#include <senseshift/arduino/output/servo.hpp>
+#include <senseshift/arduino/output/actuator/servo.hpp>
 #include <senseshift/calibration.hpp>
 #include <senseshift/input/sensor.hpp>
 #include <senseshift/input/sensor/joystick.hpp>
@@ -298,23 +298,23 @@ OpenGlovesTrackingTask* trackingTask;
 #if FFB_ENABLED
 HandActuators handActuators = {
 #if FFB_THUMB_ENABLED
-    .thumb = new SenseShift::Arduino::Output::ServoActuator(PIN_FFB_THUMB),
+    .thumb = new SenseShift::Arduino::Output::ActuatorServo(PIN_FFB_THUMB),
 #endif
 
 #if FFB_INDEX_ENABLED
-    .index = new SenseShift::Arduino::Output::ServoActuator(PIN_FFB_INDEX),
+    .index = new SenseShift::Arduino::Output::ActuatorServo(PIN_FFB_INDEX),
 #endif
 
 #if FFB_MIDDLE_ENABLED
-    .middle = new SenseShift::Arduino::Output::ServoActuator(PIN_FFB_MIDDLE),
+    .middle = new SenseShift::Arduino::Output::ActuatorServo(PIN_FFB_MIDDLE),
 #endif
 
 #if FFB_RING_ENABLED
-    .ring = new SenseShift::Arduino::Output::ServoActuator(PIN_FFB_RING),
+    .ring = new SenseShift::Arduino::Output::ActuatorServo(PIN_FFB_RING),
 #endif
 
 #if FFB_PINKY_ENABLED
-    .pinky = new SenseShift::Arduino::Output::ServoActuator(PIN_FFB_PINKY),
+    .pinky = new SenseShift::Arduino::Output::ActuatorServo(PIN_FFB_PINKY),
 #endif
 };
 OpenGlovesForceFeedbackTask* ffbTask;
diff --git a/lib/arduino/library.json b/lib/arduino/library.json
new file mode 100644
index 00000000..8601c003
--- /dev/null
+++ b/lib/arduino/library.json
@@ -0,0 +1,13 @@
+{
+    "$schema": "https://raw.githubusercontent.com/platformio/platformio-core/develop/platformio/assets/schema/library.json",
+    "frameworks": "arduino",
+    "platforms": "*",
+    "dependencies": {
+        "adafruit/Adafruit BusIO": "^1.14.1",
+        "adafruit/Adafruit Unified Sensor": "^1.1.4",
+        "adafruit/Adafruit PWM Servo Driver Library": "^2.4.0",
+        "adafruit/Adafruit INA219": "^1.2.1",
+        "sparkfun/SparkFun MAX1704x Fuel Gauge Arduino Library": "^1.0.4",
+        "madhephaestus/ESP32Servo": "^0.13.0"
+    }
+}
diff --git a/lib/ina219/senseshift/arduino/battery/ina219.hpp b/lib/arduino/senseshift/arduino/battery/ina219.hpp
similarity index 92%
rename from lib/ina219/senseshift/arduino/battery/ina219.hpp
rename to lib/arduino/senseshift/arduino/battery/ina219.hpp
index ec2dea00..a343488d 100644
--- a/lib/ina219/senseshift/arduino/battery/ina219.hpp
+++ b/lib/arduino/senseshift/arduino/battery/ina219.hpp
@@ -13,7 +13,8 @@ namespace SenseShift::Arduino::Battery {
       public:
         INA219_Battery(Adafruit_INA219* sensor) : sensor(sensor){};
 
-        void setup() override { this->active = this->sensor->begin(); }
+        void init() override { this->active = this->sensor->begin(); }
+
         ::SenseShift::Battery::BatteryState getValue() override
         {
             if (!this->active) {
diff --git a/lib/max17048/senseshift/arduino/battery/max17048.hpp b/lib/arduino/senseshift/arduino/battery/max17048.hpp
similarity index 92%
rename from lib/max17048/senseshift/arduino/battery/max17048.hpp
rename to lib/arduino/senseshift/arduino/battery/max17048.hpp
index c95c8e86..dc886cc2 100644
--- a/lib/max17048/senseshift/arduino/battery/max17048.hpp
+++ b/lib/arduino/senseshift/arduino/battery/max17048.hpp
@@ -13,7 +13,7 @@ namespace SenseShift::Arduino::Battery {
          * @see
          * https://github.com/sparkfun/SparkFun_MAX1704x_Fuel_Gauge_Arduino_Library/blob/main/examples/Example1_Simple/Example1_Simple.ino
          */
-        void setup() override
+        void init() override
         {
             // Set up the MAX17043 LiPo fuel gauge:
             this->active = this->gauge->begin();
@@ -35,7 +35,7 @@ namespace SenseShift::Arduino::Battery {
                 return { 0 };
             }
 
-            return { .level = this->gauge->getSOC() };
+            return { .level = simpleMap(this->gauge->getSOC(), 1.0f, 255.0f) };
         }
 
       private:
diff --git a/lib/pca9685/senseshift/arduino/output/pca9685.hpp b/lib/arduino/senseshift/arduino/output/actuator/pca9685.hpp
similarity index 75%
rename from lib/pca9685/senseshift/arduino/output/pca9685.hpp
rename to lib/arduino/senseshift/arduino/output/actuator/pca9685.hpp
index aa8b4ce2..4d28f92d 100644
--- a/lib/pca9685/senseshift/arduino/output/pca9685.hpp
+++ b/lib/arduino/senseshift/arduino/output/actuator/pca9685.hpp
@@ -8,11 +8,11 @@
 #include <Wire.h>
 
 namespace SenseShift::Arduino::Output {
-    class PCA9685OutputWriter : public ::SenseShift::Output::IActuator<std::uint16_t> {
+    class ActuatorPCA9685 : public ::SenseShift::Output::IActuator<std::uint16_t> {
       public:
         static inline constexpr const std::uint16_t MAX_INTENSITY = 4095;
 
-        PCA9685OutputWriter(Adafruit_PWMServoDriver* driver, const std::uint8_t num) : driver(driver), num(num){};
+        ActuatorPCA9685(Adafruit_PWMServoDriver* driver, const std::uint8_t num) : driver(driver), num(num){};
 
         void writeOutput(std::uint16_t intensity) override
         {
diff --git a/lib/arduino/senseshift/arduino/output/pwm.hpp b/lib/arduino/senseshift/arduino/output/actuator/pwm.hpp
similarity index 86%
rename from lib/arduino/senseshift/arduino/output/pwm.hpp
rename to lib/arduino/senseshift/arduino/output/actuator/pwm.hpp
index 50605e5b..5f2a595e 100644
--- a/lib/arduino/senseshift/arduino/output/pwm.hpp
+++ b/lib/arduino/senseshift/arduino/output/actuator/pwm.hpp
@@ -6,11 +6,11 @@
 #include <Arduino.h>
 
 namespace SenseShift::Arduino::Output {
-    class PWMOutputWriter : public ::SenseShift::Output::IActuator<std::uint16_t> {
+    class ActuatorPWM : public ::SenseShift::Output::IActuator<std::uint16_t> {
       public:
         static inline constexpr const std::uint16_t MAX_INTENSITY = 4095;
 
-        PWMOutputWriter(const std::uint8_t pin, const double freq = 60, const std::uint8_t resolution = 12) :
+        ActuatorPWM(const std::uint8_t pin, const double freq = 60, const std::uint8_t resolution = 12) :
           pin(pin), freq(freq), resolution(resolution){};
 
         void setup() override
diff --git a/lib/arduino/senseshift/arduino/output/servo.hpp b/lib/arduino/senseshift/arduino/output/actuator/servo.hpp
similarity index 87%
rename from lib/arduino/senseshift/arduino/output/servo.hpp
rename to lib/arduino/senseshift/arduino/output/actuator/servo.hpp
index 027c0b05..29bfa5e1 100644
--- a/lib/arduino/senseshift/arduino/output/servo.hpp
+++ b/lib/arduino/senseshift/arduino/output/actuator/servo.hpp
@@ -6,11 +6,11 @@
 #include <ESP32Servo.h>
 
 namespace SenseShift::Arduino::Output {
-    class ServoActuator : public ::SenseShift::Output::IActuator<std::uint16_t> {
+    class ActuatorServo : public ::SenseShift::Output::IActuator<std::uint16_t> {
       public:
         static inline constexpr const std::uint16_t MAX_INTENSITY = 4095;
 
-        ServoActuator(const uint8_t pin, const std::uint16_t min = 500, const std::uint16_t max = 2400) :
+        ActuatorServo(const uint8_t pin, const std::uint16_t min = 500, const std::uint16_t max = 2400) :
           pin(pin), min(min), max(max){};
 
         void setup() override
diff --git a/lib/battery/senseshift/battery/sensor.hpp b/lib/battery/senseshift/battery/sensor.hpp
index 5897564e..f2c2968a 100644
--- a/lib/battery/senseshift/battery/sensor.hpp
+++ b/lib/battery/senseshift/battery/sensor.hpp
@@ -30,4 +30,20 @@ namespace SenseShift::Battery {
       private:
         ::SenseShift::Input::ISimpleSensor<uint16_t>* sensor;
     };
+
+    class BatterySensor : public ::SenseShift::Input::MemoizedSensor<::SenseShift::Battery::BatteryState> {
+      public:
+        BatterySensor(::SenseShift::Battery::IBatterySensor* sensor, ::SenseShift::IEventDispatcher* eventDispatcher) :
+          ::SenseShift::Input::MemoizedSensor<::SenseShift::Battery::BatteryState>(sensor),
+          eventDispatcher(eventDispatcher){};
+
+        void tick() override
+        {
+            this->::SenseShift::Input::MemoizedSensor<::SenseShift::Battery::BatteryState>::tick();
+            this->eventDispatcher->postEvent(new ::SenseShift::Battery::BatteryLevelEvent(this->value));
+        }
+
+      private:
+        ::SenseShift::IEventDispatcher* eventDispatcher;
+    };
 } // namespace SenseShift::Battery
diff --git a/lib/battery_task/senseshift/freertos/battery.hpp b/lib/battery_task/senseshift/freertos/battery.hpp
deleted file mode 100644
index 8a1199fe..00000000
--- a/lib/battery_task/senseshift/freertos/battery.hpp
+++ /dev/null
@@ -1,22 +0,0 @@
-#pragma once
-
-#include <senseshift/battery/sensor.hpp>
-#include <senseshift/freertos/input/sensor.hpp>
-
-namespace SenseShift::FreeRTOS::Battery {
-    class BatterySensor : public ::SenseShift::Input::MemoizedSensor<::SenseShift::Battery::BatteryState> {
-      public:
-        BatterySensor(::SenseShift::Battery::IBatterySensor* sensor, ::SenseShift::IEventDispatcher* eventDispatcher) :
-          ::SenseShift::Input::MemoizedSensor<::SenseShift::Battery::BatteryState>(sensor),
-          eventDispatcher(eventDispatcher){};
-
-        void tick() override
-        {
-            this->::SenseShift::Input::MemoizedSensor<::SenseShift::Battery::BatteryState>::tick();
-            this->eventDispatcher->postEvent(new ::SenseShift::Battery::BatteryLevelEvent(this->value));
-        }
-
-      private:
-        ::SenseShift::IEventDispatcher* eventDispatcher;
-    };
-} // namespace SenseShift::FreeRTOS::Battery
diff --git a/lib/bhaptics_ble/library.json b/lib/bhaptics_ble/library.json
new file mode 100644
index 00000000..7ab73cce
--- /dev/null
+++ b/lib/bhaptics_ble/library.json
@@ -0,0 +1,10 @@
+{
+    "$schema": "https://raw.githubusercontent.com/platformio/platformio-core/develop/platformio/assets/schema/library.json",
+    "frameworks": "arduino",
+    "platforms": [
+        "espressif32"
+    ],
+    "dependencies": {
+        "h2zero/NimBLE-Arduino": "^1.4.0"
+    }
+}
diff --git a/lib/core/senseshift/events.hpp b/lib/core/senseshift/events.hpp
index bf09ec22..f9f404f2 100644
--- a/lib/core/senseshift/events.hpp
+++ b/lib/core/senseshift/events.hpp
@@ -6,6 +6,10 @@
 #define OH_EVENT_CONNECTED "connected"
 #define OH_EVENT_DISCONNECTED "disconnected"
 
+// TODO: Use ETL
+// https://www.etlcpp.com/observer.html
+// https://www.etlcpp.com/messages.html
+
 namespace SenseShift {
     enum Event {
 
diff --git a/lib/core/senseshift/logging.hpp b/lib/core/senseshift/logging.hpp
index d3cbc94b..9cf54f37 100644
--- a/lib/core/senseshift/logging.hpp
+++ b/lib/core/senseshift/logging.hpp
@@ -3,7 +3,9 @@
 #if defined(ESP32)
 
 #include <esp32-hal-log.h>
+
 #elif defined(UNITY_INCLUDE_PRINT_FORMATTED)
+
 #define log_e(...) TEST_PRINTF(__VA_ARGS__)
 #define log_w(...) TEST_PRINTF(__VA_ARGS__)
 #define log_i(...) TEST_PRINTF(__VA_ARGS__)
diff --git a/lib/sensor/senseshift/input/sensor.hpp b/lib/io/senseshift/input/sensor.hpp
similarity index 100%
rename from lib/sensor/senseshift/input/sensor.hpp
rename to lib/io/senseshift/input/sensor.hpp
diff --git a/lib/sensor/senseshift/input/sensor/joystick.hpp b/lib/io/senseshift/input/sensor/joystick.hpp
similarity index 100%
rename from lib/sensor/senseshift/input/sensor/joystick.hpp
rename to lib/io/senseshift/input/sensor/joystick.hpp
diff --git a/lib/actuators/senseshift/output/actuator.hpp b/lib/io/senseshift/output/actuator.hpp
similarity index 100%
rename from lib/actuators/senseshift/output/actuator.hpp
rename to lib/io/senseshift/output/actuator.hpp
diff --git a/lib/calibration/senseshift/calibration.hpp b/lib/util/senseshift/calibration.hpp
similarity index 100%
rename from lib/calibration/senseshift/calibration.hpp
rename to lib/util/senseshift/calibration.hpp
diff --git a/lib/core/senseshift/utility.hpp b/lib/util/senseshift/container.hpp
similarity index 54%
rename from lib/core/senseshift/utility.hpp
rename to lib/util/senseshift/container.hpp
index 227748f4..7f0ab894 100644
--- a/lib/core/senseshift/utility.hpp
+++ b/lib/util/senseshift/container.hpp
@@ -2,7 +2,7 @@
 
 #include <algorithm>
 #include <iterator>
-#include <senseshift/logging.hpp>
+#include <type_traits>
 
 namespace SenseShift {
     /**
@@ -17,6 +17,12 @@ namespace SenseShift {
         return std::find(std::begin(c), std::end(c), val) != std::end(c);
     };
 
+    template<>
+    inline bool contains<std::string&, char>(std::string& s, char val)
+    {
+        return s.find(val) != std::string::npos;
+    };
+
     template<typename _Tp>
     inline bool contains(_Tp* begin, _Tp* end, const _Tp& val)
     {
@@ -26,26 +32,10 @@ namespace SenseShift {
     template<typename _Tp>
     constexpr inline bool contains(const _Tp* arr, const std::size_t size, const _Tp& val)
     {
+        static_assert(
+          std::is_same<_Tp, typename std::iterator_traits<_Tp*>::value_type>::value,
+          "Container and value must be of the same type"
+        );
         return std::find(arr, arr + size, val) != arr + size;
     };
-
-    template<typename _Tp>
-    constexpr _Tp accurateMap(_Tp x, _Tp in_min, _Tp in_max, _Tp out_min, _Tp out_max)
-    {
-        const _Tp run = in_max - in_min;
-        if (run == 0) {
-            log_e("map(): Invalid input range, min == max");
-            return (out_min + out_max) / 2;
-        }
-        const _Tp rise = out_max - out_min;
-        const _Tp delta = x - in_min;
-        return (delta * rise) / run + out_min;
-    }
-
-    // Same as the above, but both mins are 0.
-    template<typename _Tp>
-    constexpr inline _Tp simpleMap(_Tp x, _Tp in_max, _Tp out_max)
-    {
-        return x * out_max / in_max;
-    }
 } // namespace SenseShift
diff --git a/lib/util/senseshift/range.hpp b/lib/util/senseshift/range.hpp
new file mode 100644
index 00000000..ea563482
--- /dev/null
+++ b/lib/util/senseshift/range.hpp
@@ -0,0 +1,30 @@
+#include <cstdint>
+#include <type_traits>
+
+#include <senseshift/logging.hpp>
+
+namespace SenseShift {
+    template<typename _Tp>
+    constexpr _Tp accurateMap(_Tp x, _Tp in_min, _Tp in_max, _Tp out_min, _Tp out_max)
+    {
+        static_assert(std::is_arithmetic<_Tp>::value, "Type must be arithmetic");
+
+        const _Tp run = in_max - in_min;
+        if (run == 0) {
+            log_e("map(): Invalid input range, min == max");
+            return (out_min + out_max) / 2;
+        }
+        const _Tp rise = out_max - out_min;
+        const _Tp delta = x - in_min;
+        return (delta * rise) / run + out_min;
+    }
+
+    // Same as the above, but both mins are 0.
+    template<typename _Tp>
+    constexpr inline _Tp simpleMap(_Tp x, _Tp in_max, _Tp out_max)
+    {
+        static_assert(std::is_arithmetic<_Tp>::value, "Type must be arithmetic");
+
+        return x * out_max / in_max;
+    }
+} // namespace SenseShift
diff --git a/lib/util/senseshift/utility.hpp b/lib/util/senseshift/utility.hpp
new file mode 100644
index 00000000..5c95fd01
--- /dev/null
+++ b/lib/util/senseshift/utility.hpp
@@ -0,0 +1,8 @@
+#pragma once
+
+#include <algorithm>
+#include <iterator>
+#include <type_traits>
+
+#include "senseshift/container.hpp"
+#include "senseshift/range.hpp"
diff --git a/platformio.ini b/platformio.ini
index 1a6bb8c3..d2a48669 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -34,28 +34,17 @@ build_src_filter  =
 	-<mode_configs>
 
 lib_deps          =
-	Wire
-	SPI
-	adafruit/Adafruit BusIO@^1.14.1
-	adafruit/Adafruit PWM Servo Driver Library@^2.4.0
-	adafruit/Adafruit INA219@^1.2.1
-	sparkfun/SparkFun MAX1704x Fuel Gauge Arduino Library@^1.0.4
-	h2zero/NimBLE-Arduino@^1.4.0
-	madhephaestus/ESP32Servo @ ^0.13.0
 
 [env]
 build_flags      = ${common.build_flags}
 build_unflags    = ${common.build_unflags}
 build_src_filter = ${common.build_src_filter}
 lib_deps         = ${common.lib_deps}
+lib_ldf_mode     = deep+
 
 check_tool  = clangtidy
 check_flags =
 	clangtidy: --config-file=./.clang-tidy --fix
-check_src_filters =
-	+<firmware>
-	+<include>
-	+<lib>
 
 [env:native]
 platform 		 = native
diff --git a/test/test_sensor/main.cpp b/test/test_io_sensor/main.cpp
similarity index 100%
rename from test/test_sensor/main.cpp
rename to test/test_io_sensor/main.cpp
diff --git a/test/test_core_utility/main.cpp b/test/test_util/main.cpp
similarity index 99%
rename from test/test_core_utility/main.cpp
rename to test/test_util/main.cpp
index a468a49a..4be719ba 100644
--- a/test/test_core_utility/main.cpp
+++ b/test/test_util/main.cpp
@@ -76,6 +76,7 @@ int process(void)
     RUN_TEST(test_contains_container);
     RUN_TEST(test_contains_iterator);
     RUN_TEST(test_contains_string);
+
     RUN_TEST(test_accurate_map);
     RUN_TEST(test_simple_map);
 
diff --git a/test/test_calibration/main.cpp b/test/test_util_calibration/main.cpp
similarity index 100%
rename from test/test_calibration/main.cpp
rename to test/test_util_calibration/main.cpp

From f1dd8cca1e73b02cbd5d17db0ea5d573c707463e Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Thu, 24 Aug 2023 17:24:20 +0400
Subject: [PATCH 23/82] refactor(Haptics): move into `SenseShift` dir/namespace

---
 include/senseshift.h                                 |  2 +-
 .../senseshift/arduino/components/serial_plotter.hpp |  2 +-
 lib/bhaptics/senseshift/bh/devices.hpp               |  2 +-
 lib/bhaptics/senseshift/bh/encoding.hpp              |  2 +-
 lib/bhaptics_ble/senseshift/bh/ble/connection.cpp    |  2 +-
 lib/hands/hand_interface.hpp                         |  2 +-
 .../body/haptics/body.cpp}                           |  2 +-
 .../body/haptics/body.hpp}                           |  4 ++--
 .../body/haptics/interface.hpp}                      | 12 ++++++------
 .../body/haptics/plane.cpp}                          |  4 +---
 .../body/haptics/plane.hpp}                          |  2 +-
 test/test_haptics_body/main.cpp                      |  2 +-
 test/test_haptics_plane/main.cpp                     |  2 +-
 13 files changed, 19 insertions(+), 21 deletions(-)
 rename lib/haptics/{haptic_body.cpp => senseshift/body/haptics/body.cpp} (95%)
 rename lib/haptics/{haptic_body.hpp => senseshift/body/haptics/body.hpp} (88%)
 rename lib/haptics/{haptics_interface.hpp => senseshift/body/haptics/interface.hpp} (88%)
 rename lib/haptics/{haptic_plane.cpp => senseshift/body/haptics/plane.cpp} (97%)
 rename lib/haptics/{haptic_plane.hpp => senseshift/body/haptics/plane.hpp} (98%)

diff --git a/include/senseshift.h b/include/senseshift.h
index c63f5659..95c5603e 100644
--- a/include/senseshift.h
+++ b/include/senseshift.h
@@ -2,8 +2,8 @@
 
 #include "config/all.h"
 
-#include <haptic_body.hpp>
 #include <senseshift/battery/sensor.hpp>
+#include <senseshift/body/haptics/body.hpp>
 #include <senseshift/events.hpp>
 
 #include <vector>
diff --git a/lib/arduino/senseshift/arduino/components/serial_plotter.hpp b/lib/arduino/senseshift/arduino/components/serial_plotter.hpp
index 708111b6..2cc04d92 100644
--- a/lib/arduino/senseshift/arduino/components/serial_plotter.hpp
+++ b/lib/arduino/senseshift/arduino/components/serial_plotter.hpp
@@ -1,6 +1,6 @@
 #pragma once
 
-#include <haptic_body.hpp>
+#include <senseshift/body/haptics/body.hpp>
 #include <senseshift/freertos/task.hpp>
 
 #include <HardwareSerial.h>
diff --git a/lib/bhaptics/senseshift/bh/devices.hpp b/lib/bhaptics/senseshift/bh/devices.hpp
index 60cac07c..b78bbb9f 100644
--- a/lib/bhaptics/senseshift/bh/devices.hpp
+++ b/lib/bhaptics/senseshift/bh/devices.hpp
@@ -1,7 +1,7 @@
 #pragma once
 
 #include <hand_interface.hpp>
-#include <haptic_body.hpp>
+#include <senseshift/body/haptics/body.hpp>
 
 #pragma region BH_DEVICE_TACTSUITX40
 
diff --git a/lib/bhaptics/senseshift/bh/encoding.hpp b/lib/bhaptics/senseshift/bh/encoding.hpp
index e2500cd9..3b75f8f0 100644
--- a/lib/bhaptics/senseshift/bh/encoding.hpp
+++ b/lib/bhaptics/senseshift/bh/encoding.hpp
@@ -1,7 +1,7 @@
 #pragma once
 
 #include <cstring>
-#include <haptic_body.hpp>
+#include <senseshift/body/haptics/body.hpp>
 
 namespace SenseShift::BH {
     class Decoder {
diff --git a/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp b/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
index 66c91204..48e11876 100644
--- a/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
+++ b/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
@@ -1,7 +1,7 @@
 #include "senseshift/bh/ble/connection.hpp"
 
-#include <haptic_body.hpp>
 #include <senseshift/bh/constants.hpp>
+#include <senseshift/body/haptics/body.hpp>
 #include <senseshift/events.hpp>
 
 #include <Arduino.h>
diff --git a/lib/hands/hand_interface.hpp b/lib/hands/hand_interface.hpp
index ee079c5a..71249f0c 100644
--- a/lib/hands/hand_interface.hpp
+++ b/lib/hands/hand_interface.hpp
@@ -1,7 +1,7 @@
 #pragma once
 
 #include <cstdint>
-#include <haptics_interface.hpp>
+#include <senseshift/body/haptics/interface.hpp>
 
 namespace SenseShift::Body {
     namespace Hands {
diff --git a/lib/haptics/haptic_body.cpp b/lib/haptics/senseshift/body/haptics/body.cpp
similarity index 95%
rename from lib/haptics/haptic_body.cpp
rename to lib/haptics/senseshift/body/haptics/body.cpp
index ddde0702..ed97efd4 100644
--- a/lib/haptics/haptic_body.cpp
+++ b/lib/haptics/senseshift/body/haptics/body.cpp
@@ -1,4 +1,4 @@
-#include "haptic_body.hpp"
+#include "senseshift/body/haptics/body.hpp"
 
 #include <senseshift/logging.hpp>
 
diff --git a/lib/haptics/haptic_body.hpp b/lib/haptics/senseshift/body/haptics/body.hpp
similarity index 88%
rename from lib/haptics/haptic_body.hpp
rename to lib/haptics/senseshift/body/haptics/body.hpp
index d0e536cd..2b049c83 100644
--- a/lib/haptics/haptic_body.hpp
+++ b/lib/haptics/senseshift/body/haptics/body.hpp
@@ -1,7 +1,7 @@
 #pragma once
 
-#include "haptic_plane.hpp"
-#include "haptics_interface.hpp"
+#include "senseshift/body/haptics/interface.hpp"
+#include "senseshift/body/haptics/plane.hpp"
 
 #include <senseshift/utility.hpp>
 
diff --git a/lib/haptics/haptics_interface.hpp b/lib/haptics/senseshift/body/haptics/interface.hpp
similarity index 88%
rename from lib/haptics/haptics_interface.hpp
rename to lib/haptics/senseshift/body/haptics/interface.hpp
index 506f0928..4ab0df0e 100644
--- a/lib/haptics/haptics_interface.hpp
+++ b/lib/haptics/senseshift/body/haptics/interface.hpp
@@ -1,12 +1,12 @@
 #pragma once
 
-#include <stdint.h>
+#include <cstdint>
 #include <variant>
 
 #include <senseshift/math/point2.hpp>
 
 namespace SenseShift::Body::Haptics {
-    typedef uint8_t EffectIntex_t;
+    typedef std::uint8_t EffectIntex_t;
     static const EffectIntex_t EFFECT_INVALID = 0xFF;
     typedef enum class Effect : EffectIntex_t {
         Invalid = EFFECT_INVALID,
@@ -15,7 +15,7 @@ namespace SenseShift::Body::Haptics {
         // Thermal = 0x01,
     } Effect_t;
 
-    typedef uint8_t TargetIndex_t;
+    typedef std::uint8_t TargetIndex_t;
     static const TargetIndex_t TARGET_INVALID = 0xFF;
     typedef enum class Target : TargetIndex_t {
         Invalid = TARGET_INVALID,
@@ -46,12 +46,12 @@ namespace SenseShift::Body::Haptics {
         // TODO: arms, legs, etc.
     } Target_t;
 
-    typedef uint8_t Coordinate_t;
+    typedef std::uint8_t Coordinate_t;
     typedef ::SenseShift::Math::Point2<Coordinate_t> Position_t;
 
     // Vibration intensity.
     typedef struct VibroEffectData {
-        using Intensity_t = uint16_t;
+        using Intensity_t = std::uint16_t;
         inline static const Intensity_t INTENSITY_MIN = 0;
         inline static const Intensity_t INTENSITY_MAX = 4095;
 
@@ -61,7 +61,7 @@ namespace SenseShift::Body::Haptics {
         inline constexpr VibroEffectData(const Intensity_t intensity) : intensity(intensity) {}
         inline constexpr VibroEffectData(const VibroEffectData& other) = default;
 
-        inline constexpr operator uint16_t() const { return intensity; }
+        inline constexpr operator std::uint16_t() const { return intensity; }
     } VibroEffectData_t;
 
     // TODO: thermal, etc.
diff --git a/lib/haptics/haptic_plane.cpp b/lib/haptics/senseshift/body/haptics/plane.cpp
similarity index 97%
rename from lib/haptics/haptic_plane.cpp
rename to lib/haptics/senseshift/body/haptics/plane.cpp
index 47af5ae6..21614dcc 100644
--- a/lib/haptics/haptic_plane.cpp
+++ b/lib/haptics/senseshift/body/haptics/plane.cpp
@@ -1,11 +1,9 @@
-#include "haptic_plane.hpp"
+#include "senseshift/body/haptics/plane.hpp"
 
 #include <algorithm>
 #include <cmath>
 #include <senseshift/logging.hpp>
 
-#include "haptic_plane.hpp"
-
 namespace SenseShift::Body::Haptics {
     template<typename _Tp, typename _Ta>
     void ActuativePlane<_Tp, _Ta>::setActuators(const ActuatorMap_t& actuators)
diff --git a/lib/haptics/haptic_plane.hpp b/lib/haptics/senseshift/body/haptics/plane.hpp
similarity index 98%
rename from lib/haptics/haptic_plane.hpp
rename to lib/haptics/senseshift/body/haptics/plane.hpp
index e382e01a..5c2cf7be 100644
--- a/lib/haptics/haptic_plane.hpp
+++ b/lib/haptics/senseshift/body/haptics/plane.hpp
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "haptics_interface.hpp"
+#include "senseshift/body/haptics/interface.hpp"
 
 #include <senseshift/output/actuator.hpp>
 #include <senseshift/utility.hpp>
diff --git a/test/test_haptics_body/main.cpp b/test/test_haptics_body/main.cpp
index 19c8c866..5abefa7d 100644
--- a/test/test_haptics_body/main.cpp
+++ b/test/test_haptics_body/main.cpp
@@ -1,4 +1,4 @@
-#include <haptic_body.hpp>
+#include <senseshift/body/haptics/body.hpp>
 #include <unity.h>
 
 using namespace SenseShift::Body::Haptics;
diff --git a/test/test_haptics_plane/main.cpp b/test/test_haptics_plane/main.cpp
index 091ba1c3..0be53a48 100644
--- a/test/test_haptics_plane/main.cpp
+++ b/test/test_haptics_plane/main.cpp
@@ -1,4 +1,4 @@
-#include <haptic_plane.hpp>
+#include <senseshift/body/haptics/plane.hpp>
 #include <unity.h>
 
 using namespace SenseShift::Body::Haptics;

From ae5355e776251862ad98303ea154f8539ebd694e Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Sun, 27 Aug 2023 14:17:44 +0400
Subject: [PATCH 24/82] feat(OpenGloves): use Frozen for command mapping

---
 .github/workflows/ci.yml              |  8 +++++++-
 .github/workflows/codeql-analysis.yml |  1 +
 .github/workflows/release.yml         |  4 ++++
 .gitmodules                           |  3 +++
 docs/DEVELOPMENT.md                   |  7 +++++++
 lib/frozen                            |  1 +
 lib/opengloves/og_alpha_encoding.cpp  |  2 +-
 lib/opengloves/og_alpha_encoding.hpp  | 16 ++++++++++------
 lib/opengloves/og_protocol.hpp        |  3 ++-
 9 files changed, 36 insertions(+), 9 deletions(-)
 create mode 100644 .gitmodules
 create mode 100644 docs/DEVELOPMENT.md
 create mode 160000 lib/frozen

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5a0bf078..4ecc2720 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -60,6 +60,8 @@ jobs:
 
     steps:
       - uses: actions/checkout@v3
+        with:
+          submodules: 'recursive'
 
       - name: Get firmware name
         id: firmware_name
@@ -194,6 +196,8 @@ jobs:
 
     steps:
       - uses: actions/checkout@v3
+        with:
+          submodules: 'recursive'
 
       - name: Get firmware name
         id: firmware_name
@@ -298,6 +302,8 @@ jobs:
 
     steps:
       - uses: actions/checkout@v3
+        with:
+          submodules: 'recursive'
 
       - name: Speedup package installation
         uses: abbbi/github-actions-tune@v1
@@ -382,7 +388,7 @@ jobs:
           ls -lahR ./build/lcov/
 
           find ./build/lcov -name 'lcov.info.*' -exec echo -a {} \; | xargs lcov -o ./build/lcov/lcov.info
-          lcov --remove ./build/lcov/lcov.info '/usr/include/*' '*.platformio/*' '*/.pio/*' '*/tool-unity/*' '*/test/*' '*/MockArduino/*' -o ./build/lcov/lcov.info.cleaned
+          lcov --remove ./build/lcov/lcov.info '/usr/include/*' '*.platformio/*' '*/.pio/*' '*/tool-unity/*' '*/test/*' '*/MockArduino/*' '*/lib/frozen/*' -o ./build/lcov/lcov.info.cleaned
 
       - name: Generate HTML report
         run: genhtml -p $PWD -o ./build/coverage/ --demangle-cpp ./build/lcov/lcov.info.cleaned
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 74b3dd8d..ed8f6731 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -42,6 +42,7 @@ jobs:
         # We must fetch at least the immediate parents so that if this is
         # a pull request then we can checkout the head.
         fetch-depth: 2
+        submodules: 'recursive'
 
     # If this run was triggered by a pull request event, then checkout
     # the head of the pull request instead of the merge commit.
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index e4e216ef..6eb7910a 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -39,6 +39,8 @@ jobs:
 
     steps:
       - uses: actions/checkout@v3
+        with:
+          submodules: 'recursive'
 
       - name: Get firmware name
         id: firmware_name
@@ -134,6 +136,8 @@ jobs:
           - OPENGLOVES_COMMUNCATION=OPENGLOVES_COMM_BTSERIAL
     steps:
       - uses: actions/checkout@v3
+        with:
+          submodules: 'recursive'
 
       - name: Get firmware name
         id: firmware_name
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 00000000..cd1f2794
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "lib/frozen"]
+	path = lib/frozen
+	url = git@github.com:senseshift/frozen.git
diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md
new file mode 100644
index 00000000..e7d53aef
--- /dev/null
+++ b/docs/DEVELOPMENT.md
@@ -0,0 +1,7 @@
+## Useful Scripts
+
+### Fix `clang-format`
+
+```shell
+find lib include firmware test examples -type f -regex '.*\.\(cpp\|hpp\|cu\|c\|h\)' ! -regex '^lib/frozen\(/.*\)' -exec clang-format-16 -style=file -i {} \;
+```
diff --git a/lib/frozen b/lib/frozen
new file mode 160000
index 00000000..eb113465
--- /dev/null
+++ b/lib/frozen
@@ -0,0 +1 @@
+Subproject commit eb113465791905f29384a9deac6650fc323f4d53
diff --git a/lib/opengloves/og_alpha_encoding.cpp b/lib/opengloves/og_alpha_encoding.cpp
index ed30cbf4..da70fbe7 100644
--- a/lib/opengloves/og_alpha_encoding.cpp
+++ b/lib/opengloves/og_alpha_encoding.cpp
@@ -33,7 +33,7 @@ namespace OpenGloves {
         }
 
         // Split the command into prefix and number
-        size_t split_index = current_command.find_last_not_of(valueSymbols) + 1;
+        size_t split_index = current_command.find_last_not_of(valueSymbols.data()) + 1;
 
         if (split_index >= current_command.size()) {
             log_w("Invalid command: %s", current_command.c_str());
diff --git a/lib/opengloves/og_alpha_encoding.hpp b/lib/opengloves/og_alpha_encoding.hpp
index 8ca21f07..f2ad57d1 100644
--- a/lib/opengloves/og_alpha_encoding.hpp
+++ b/lib/opengloves/og_alpha_encoding.hpp
@@ -1,21 +1,25 @@
 #pragma once
 
+#include <algorithm>
 #include <cstdint>
 #include <functional>
 #include <map>
 #include <string>
 #include <vector>
 
+#include <frozen/map.h>
+#include <frozen/string.h>
+#include <frozen/unordered_map.h>
+
 #include <og_protocol.hpp>
 #include <senseshift/logging.hpp>
 
 namespace OpenGloves {
     class AlphaEncodingService {
       public:
-        inline static const std::string valueSymbols = "0123456789";
-        // TODO: use Frozen library to save memory
-        inline static const std::map<std::string, Command> commandMap = {
-            // clang-format off
+        inline static constexpr frozen::string valueSymbols = "0123456789";
+        inline static const auto commandMap = frozen::make_map<std::string, Command>({
+          // clang-format off
             { "A", Command::ThumbCurl },
             { "(AB)", Command::ThumbSplay },
             { "B", Command::IndexCurl },
@@ -26,8 +30,8 @@ namespace OpenGloves {
             { "(DB)", Command::RingSplay },
             { "E", Command::PinkyCurl },
             { "(EB)", Command::PinkySplay },
-            // clang-format on
-        };
+          // clang-format on
+        });
 
         AlphaEncodingService() = default;
 
diff --git a/lib/opengloves/og_protocol.hpp b/lib/opengloves/og_protocol.hpp
index 0e863109..3b8dcccc 100644
--- a/lib/opengloves/og_protocol.hpp
+++ b/lib/opengloves/og_protocol.hpp
@@ -57,7 +57,8 @@ namespace OpenGloves {
         virtual size_t readCommand(char* buffer, size_t length) = 0;
     };
 
-    typedef enum Command {
+    typedef uint16_t CommandIndex_t;
+    typedef enum Command : CommandIndex_t {
         ThumbCurl,
         ThumbSplay,
 

From 6629ccdfecb0a348d0e7519cf4ddecb0b5e1bb26 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Tue, 5 Sep 2023 07:28:21 +0000
Subject: [PATCH 25/82] chore: add devcontainer codespace

---
 .devcontainer/Dockerfile        | 48 +++++++++++++++++++++++++++++++++
 .devcontainer/devcontainer.json | 34 +++++++++++++++++++++++
 2 files changed, 82 insertions(+)
 create mode 100644 .devcontainer/Dockerfile
 create mode 100644 .devcontainer/devcontainer.json

diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 00000000..238133e3
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,48 @@
+FROM mcr.microsoft.com/vscode/devcontainers/base:ubuntu
+
+ARG CLANG_VERSION=16
+
+RUN apt-get update \
+    && apt-get -qqy install \
+    lsb-release \
+    wget \
+    software-properties-common \
+    gnupg \
+    bc \
+    sudo \
+    build-essential \
+    ca-certificates \
+    clang \
+    curl \
+    gcc \
+    git \
+    python3 \
+    python3-dev \
+    python3-pip \
+    python3-venv \
+    python3-distutils \
+    python3-setuptools \
+    srecord \
+    udev \
+    xz-utils \
+    && wget --quiet https://apt.llvm.org/llvm.sh \
+    && chmod +x llvm.sh \
+    && ./llvm.sh $CLANG_VERSION && rm -rf llvm.sh \
+    && apt-get --no-install-recommends -y install clang-format-$CLANG_VERSION clang-tidy-$CLANG_VERSION \
+    && ln -s /usr/bin/clang-format-$CLANG_VERSION /usr/bin/clang-format \
+    && ln -s /usr/bin/clang-tidy-$CLANG_VERSION /usr/bin/clang-tidy \
+    && ln -s /usr/bin/python3 /usr/bin/python \
+    && apt-get autoremove -y \
+    && apt-get clean -y \
+    && rm -rf /var/lib/apt/lists/*
+
+RUN curl -fLo /etc/udev/rules.d/99-platformio-udev.rules --create-dirs https://raw.githubusercontent.com/platformio/platformio-core/develop/platformio/assets/system/99-platformio-udev.rules
+
+USER $USERNAME
+
+# RUN wget https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py -O get-platformio.py \
+#     && python3 get-platformio.py \
+#     && rm -rf get-platformio.py
+
+RUN python -m pip install --upgrade pip \
+    && pip install --upgrade platformio
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 00000000..01be1c44
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,34 @@
+{
+	// "name": "SenseShift",
+	"dockerFile": "Dockerfile",
+	"runArgs": [
+		"--privileged"
+	],
+	"forwardPorts": [
+		8008
+	],
+	"mounts": [
+		"source=/dev/,target=/dev/,type=bind,consistency=consistent"
+	],
+	"postAttachCommand": "sudo service udev restart",
+	"customizations": {
+		"vscode": {
+			"settings": {
+				"terminal.integrated.defaultProfile.linux": "zsh",
+				"editor.formatOnSave": true,
+				"platformio-ide.useBuiltinPIOCore": false,
+				"platformio-ide.useBuiltinPython": false,
+				"platformio-ide.disablePIOHomeStartup": true,
+				"platformio-ide.pioHomeServerHttpPort": 8008
+			},
+			"extensions": [
+				"ms-vscode.cpptools",
+				"platformio.platformio-ide",
+				"xaver.clang-format",
+				"GitHub.copilot",
+				"GitHub.copilot-labs",
+				"GitHub.copilot-chat"
+			]
+		}
+	}
+}

From ba92af52b32cc527bbe65d1f1e61e1ea61da4fad Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Wed, 6 Sep 2023 13:31:29 +0000
Subject: [PATCH 26/82] ci(GitHub): add Wokwi testing

---
 .devcontainer/Dockerfile                      |   3 +
 .devcontainer/devcontainer.json               |   6 +-
 .github/workflows/ci.yml                      |  80 ++++-
 .gitmodules                                   |   2 +-
 .../diagram.json                              | 314 ++++++++++++++++++
 .../lucidgloves-prototype3+serial/test.yaml   |  76 +++++
 .../lucidgloves-prototype3+serial/wokwi.toml  |   4 +
 7 files changed, 465 insertions(+), 20 deletions(-)
 create mode 100644 .wokwi/lucidgloves-prototype3+serial/diagram.json
 create mode 100644 .wokwi/lucidgloves-prototype3+serial/test.yaml
 create mode 100644 .wokwi/lucidgloves-prototype3+serial/wokwi.toml

diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index 238133e3..eac4101c 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -46,3 +46,6 @@ USER $USERNAME
 
 RUN python -m pip install --upgrade pip \
     && pip install --upgrade platformio
+
+RUN sudo wget -O /usr/local/bin/wokwi-cli https://github.com/wokwi/wokwi-cli/releases/latest/download/wokwi-cli-linuxstatic-x64 \
+    && sudo chmod +x /usr/local/bin/wokwi-cli
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 01be1c44..4ec06d5d 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -10,7 +10,10 @@
 	"mounts": [
 		"source=/dev/,target=/dev/,type=bind,consistency=consistent"
 	],
-	"postAttachCommand": "sudo service udev restart",
+	"postAttachCommand": {
+		"submodules": ["git", "submodule", "update", "--recursive", "--init"],
+		"udev": ["sudo", "service", "udev", "restart"]
+	},
 	"customizations": {
 		"vscode": {
 			"settings": {
@@ -24,6 +27,7 @@
 			"extensions": [
 				"ms-vscode.cpptools",
 				"platformio.platformio-ide",
+				"Wokwi.wokwi-vscode",
 				"xaver.clang-format",
 				"GitHub.copilot",
 				"GitHub.copilot-labs",
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4ecc2720..1119c892 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -6,13 +6,13 @@ on:
       - master
       - develop
     paths-ignore:
-      - '**/*.md'
+      - "**/*.md"
   push:
     branches:
       - master
       - develop
     paths-ignore:
-      - '**/*.md'
+      - "**/*.md"
 
 jobs:
   build-bhaptics:
@@ -21,7 +21,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        os: [ ubuntu-latest ]
+        os: [ubuntu-latest]
         target:
           - bhaptics_tactsuit_x16
           - bhaptics_tactsuit_x16_pca9685
@@ -32,10 +32,10 @@ jobs:
           - bhaptics_tactal
           - bhaptics_tactvisor
           - bhaptics_tactglove_right
-        battery_flag: [ SENSESHIFT_BATTERY_ENABLED=true ]
-        serial_plotter_flag: [ SENSESHIFT_SERIAL_PLOTTER=false ]
-        nimble_flag: [ SENSESHIFT_BLE_USE_NIMBLE=false ]
-        coverage: [ false ]
+        battery_flag: [SENSESHIFT_BATTERY_ENABLED=true]
+        serial_plotter_flag: [SENSESHIFT_SERIAL_PLOTTER=false]
+        nimble_flag: [SENSESHIFT_BLE_USE_NIMBLE=false]
+        coverage: [false]
 
         include:
           # Extra tests for x40, as it uses the most features
@@ -61,7 +61,7 @@ jobs:
     steps:
       - uses: actions/checkout@v3
         with:
-          submodules: 'recursive'
+          submodules: "recursive"
 
       - name: Get firmware name
         id: firmware_name
@@ -120,7 +120,7 @@ jobs:
       - name: Set up Python
         uses: actions/setup-python@v4
         with:
-          python-version: '3.9'
+          python-version: "3.9"
 
       - name: Install PlatformIO
         run: |
@@ -156,14 +156,14 @@ jobs:
         with:
           name: lcov.info.${{ steps.firmware_name.outputs.firmware }}
           path: ./build/lcov/lcov.info.${{ steps.firmware_name.outputs.firmware }}
-          retention-days: 5
+          retention-days: 1
 
   build-opengloves:
     runs-on: ${{ matrix.os }}
     strategy:
       fail-fast: false
       matrix:
-        os: [ ubuntu-latest ]
+        os: [ubuntu-latest]
         target:
           - lucidgloves-prototype3
           - lucidgloves-prototype4
@@ -174,7 +174,7 @@ jobs:
         comm_flag:
           - COMMUNICATION_PROTOCOL=OPENGLOVES_COMM_SERIAL
           - COMMUNICATION_PROTOCOL=OPENGLOVES_COMM_BTSERIAL
-        coverage: [ false ]
+        coverage: [false]
 
         include:
           - os: ubuntu-latest
@@ -197,7 +197,7 @@ jobs:
     steps:
       - uses: actions/checkout@v3
         with:
-          submodules: 'recursive'
+          submodules: "recursive"
 
       - name: Get firmware name
         id: firmware_name
@@ -234,7 +234,7 @@ jobs:
       - name: Set up Python
         uses: actions/setup-python@v4
         with:
-          python-version: '3.9'
+          python-version: "3.9"
 
       - name: Enable coverage (non-macOS)
         if: runner.os != 'macOS' && matrix.coverage
@@ -278,6 +278,17 @@ jobs:
           echo "::endgroup::"
           pio run --environment ${{matrix.target}}
 
+      - name: Upload firmware Artifact
+        if: matrix.coverage == false
+        uses: actions/upload-artifact@v3
+        with:
+          name: ${{ steps.firmware_name.outputs.firmware }}
+          path: |
+            ./.pio/build/${{matrix.target}}/firmware.bin
+            ./.pio/build/${{matrix.target}}/firmware.elf
+          retention-days: 1
+          if-no-files-found: error
+
       - name: Collect initial coverage
         if: matrix.coverage
         run: |
@@ -290,7 +301,8 @@ jobs:
         with:
           name: lcov.info.${{ steps.firmware_name.outputs.firmware }}
           path: ./build/lcov/lcov.info.${{ steps.firmware_name.outputs.firmware }}
-          retention-days: 5
+          retention-days: 1
+          if-no-files-found: error
 
   test:
     runs-on: ubuntu-latest
@@ -303,7 +315,7 @@ jobs:
     steps:
       - uses: actions/checkout@v3
         with:
-          submodules: 'recursive'
+          submodules: "recursive"
 
       - name: Speedup package installation
         uses: abbbi/github-actions-tune@v1
@@ -333,7 +345,7 @@ jobs:
       - name: Set up Python
         uses: actions/setup-python@v4
         with:
-          python-version: '3.9'
+          python-version: "3.9"
 
       - name: Install PlatformIO
         run: |
@@ -398,7 +410,6 @@ jobs:
         with:
           name: coverage-report
           path: |
-            ./build/lcov/
             ./build/coverage/
           retention-days: 5
 
@@ -406,3 +417,36 @@ jobs:
         with:
           github-token: ${{ secrets.GITHUB_TOKEN }}
           lcov-file: ./build/lcov/lcov.info.cleaned
+
+  wokwi:
+    needs:
+      - build-opengloves
+      - test
+    runs-on: ubuntu-latest
+
+    strategy:
+      matrix:
+        variant:
+          - lucidgloves-prototype3+serial
+
+    steps:
+      - uses: actions/checkout@v3
+
+      - name: Extract target
+        id: target_name
+        run: |
+          target=$(echo ${{ matrix.variant }} | cut -d'+' -f1)
+          echo "target=$target" >> $GITHUB_OUTPUT
+
+      - name: Download firmware artifact
+        uses: actions/download-artifact@v3
+        with:
+          name: ${{ matrix.variant }}
+          path: ./.pio/build/${{ steps.target_name.outputs.target }}
+
+      - name: Run Wokwi scenario
+        uses: wokwi/wokwi-ci-action@v1
+        with:
+          token: ${{ secrets.WOKWI_CLI_TOKEN }}
+          path: ./.wokwi/${{ matrix.variant }}
+          scenario: "test.yaml"
diff --git a/.gitmodules b/.gitmodules
index cd1f2794..fb9d97bc 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,3 @@
 [submodule "lib/frozen"]
 	path = lib/frozen
-	url = git@github.com:senseshift/frozen.git
+	url = https://github.com/senseshift/frozen.git
diff --git a/.wokwi/lucidgloves-prototype3+serial/diagram.json b/.wokwi/lucidgloves-prototype3+serial/diagram.json
new file mode 100644
index 00000000..e20e4916
--- /dev/null
+++ b/.wokwi/lucidgloves-prototype3+serial/diagram.json
@@ -0,0 +1,314 @@
+{
+    "version": 1,
+    "author": "Leonid Meleshin",
+    "editor": "wokwi",
+    "parts": [
+        {
+            "type": "wokwi-esp32-devkit-v1",
+            "id": "esp",
+            "top": 110.3,
+            "left": 523,
+            "attrs": {}
+        },
+        {
+            "type": "wokwi-potentiometer",
+            "id": "pot-thumb",
+            "top": -87.7,
+            "left": -278.6,
+            "attrs": {}
+        },
+        {
+            "type": "wokwi-potentiometer",
+            "id": "pot-index",
+            "top": -87.7,
+            "left": -192.2,
+            "attrs": {}
+        },
+        {
+            "type": "wokwi-potentiometer",
+            "id": "pot-middle",
+            "top": -87.7,
+            "left": -105.8,
+            "attrs": {}
+        },
+        {
+            "type": "wokwi-potentiometer",
+            "id": "pot-ring",
+            "top": -87.7,
+            "left": -19.4,
+            "attrs": {}
+        },
+        {
+            "type": "wokwi-potentiometer",
+            "id": "pot-pinky",
+            "top": -87.7,
+            "left": 67,
+            "attrs": {}
+        },
+        {
+            "type": "wokwi-analog-joystick",
+            "id": "joystick1",
+            "top": -135,
+            "left": 149.4,
+            "attrs": {}
+        },
+        {
+            "type": "wokwi-pushbutton",
+            "id": "btn1",
+            "top": 6.2,
+            "left": 268.8,
+            "attrs": {
+                "color": "green",
+                "key": "a",
+                "bounce": "1",
+                "label": "A"
+            }
+        },
+        {
+            "type": "wokwi-pushbutton",
+            "id": "btn2",
+            "top": 6.2,
+            "left": 355.2,
+            "attrs": {
+                "color": "green",
+                "key": "b",
+                "bounce": "1",
+                "label": "B"
+            }
+        },
+        {
+            "type": "wokwi-gnd",
+            "id": "gnd1",
+            "top": 307.2,
+            "left": 729,
+            "attrs": {}
+        }
+    ],
+    "connections": [
+        [
+            "esp:TX0",
+            "$serialMonitor:RX",
+            "",
+            []
+        ],
+        [
+            "esp:RX0",
+            "$serialMonitor:TX",
+            "",
+            []
+        ],
+        [
+            "pot-thumb:VCC",
+            "pot-index:VCC",
+            "red",
+            [
+                "v28.8",
+                "h76.8"
+            ]
+        ],
+        [
+            "pot-index:VCC",
+            "pot-middle:VCC",
+            "red",
+            [
+                "v28.8",
+                "h76.8"
+            ]
+        ],
+        [
+            "pot-middle:VCC",
+            "pot-ring:VCC",
+            "red",
+            [
+                "v28.8",
+                "h76.8"
+            ]
+        ],
+        [
+            "pot-thumb:GND",
+            "pot-index:GND",
+            "black",
+            [
+                "v38.4",
+                "h76.8"
+            ]
+        ],
+        [
+            "pot-index:GND",
+            "pot-middle:GND",
+            "black",
+            [
+                "v38.4",
+                "h76.8"
+            ]
+        ],
+        [
+            "pot-middle:GND",
+            "pot-ring:GND",
+            "black",
+            [
+                "v38.4",
+                "h76.8"
+            ]
+        ],
+        [
+            "pot-ring:GND",
+            "pot-pinky:GND",
+            "black",
+            [
+                "v38.4",
+                "h76.8"
+            ]
+        ],
+        [
+            "esp:VP",
+            "pot-pinky:SIG",
+            "green",
+            [
+                "h0"
+            ]
+        ],
+        [
+            "esp:VN",
+            "pot-ring:SIG",
+            "green",
+            [
+                "h0"
+            ]
+        ],
+        [
+            "esp:D34",
+            "pot-middle:SIG",
+            "green",
+            [
+                "h0"
+            ]
+        ],
+        [
+            "esp:D35",
+            "pot-index:SIG",
+            "green",
+            [
+                "h0"
+            ]
+        ],
+        [
+            "esp:D32",
+            "pot-thumb:SIG",
+            "green",
+            [
+                "h0"
+            ]
+        ],
+        [
+            "joystick1:GND",
+            "pot-pinky:GND",
+            "black",
+            [
+                "v38.4",
+                "h-124.8"
+            ]
+        ],
+        [
+            "esp:D33",
+            "joystick1:HORZ",
+            "green",
+            [
+                "h0"
+            ]
+        ],
+        [
+            "esp:D25",
+            "joystick1:VERT",
+            "green",
+            [
+                "h0"
+            ]
+        ],
+        [
+            "esp:D26",
+            "joystick1:SEL",
+            "green",
+            [
+                "h0"
+            ]
+        ],
+        [
+            "esp:GND.2",
+            "joystick1:GND",
+            "black",
+            [
+                "h0"
+            ]
+        ],
+        [
+            "esp:VIN",
+            "joystick1:VCC",
+            "red",
+            [
+                "h0"
+            ]
+        ],
+        [
+            "pot-ring:VCC",
+            "pot-pinky:VCC",
+            "red",
+            [
+                "v28.8",
+                "h37.6"
+            ]
+        ],
+        [
+            "joystick1:VCC",
+            "pot-pinky:VCC",
+            "red",
+            [
+                "v28.8",
+                "h-38.4"
+            ]
+        ],
+        [
+            "btn1:2.r",
+            "esp:D27",
+            "green",
+            [
+                "h0.2",
+                "v249.8"
+            ]
+        ],
+        [
+            "btn2:2.r",
+            "esp:D14",
+            "green",
+            [
+                "h0.2",
+                "v259.4"
+            ]
+        ],
+        [
+            "btn1:1.r",
+            "btn2:1.l",
+            "black",
+            [
+                "v0"
+            ]
+        ],
+        [
+            "joystick1:GND",
+            "btn1:1.l",
+            "black",
+            [
+                "v0"
+            ]
+        ],
+        [
+            "esp:GND.1",
+            "gnd1:GND",
+            "black",
+            [
+                "h0"
+            ]
+        ]
+    ],
+    "dependencies": {}
+}
diff --git a/.wokwi/lucidgloves-prototype3+serial/test.yaml b/.wokwi/lucidgloves-prototype3+serial/test.yaml
new file mode 100644
index 00000000..0639c5cc
--- /dev/null
+++ b/.wokwi/lucidgloves-prototype3+serial/test.yaml
@@ -0,0 +1,76 @@
+name: Test LucidGloves Prototype 3
+version: 1
+author: Leonid Meleshin
+
+steps:
+  - wait-serial: "A0B0C0D0E0F2047G2047"
+
+  # Press the 'A' button
+  - set-control:
+      part-id: btn1
+      control: pressed
+      value: 1
+  - wait-serial: 'A0B0C0D0E0F2047G2047J'
+
+  # Press the 'B' button
+  - set-control:
+      part-id: btn2
+      control: pressed
+      value: 1
+  - wait-serial: 'A0B0C0D0E0F2047G2047JK'
+
+  # Release the 'A' button
+  - set-control:
+      part-id: btn1
+      control: pressed
+      value: 0
+  - wait-serial: 'A0B0C0D0E0F2047G2047K'
+
+  # Release the 'B' button
+  - set-control:
+      part-id: btn2
+      control: pressed
+      value: 0
+  - wait-serial: 'A0B0C0D0E0F2047G2047'
+
+  # Curl Index finger
+  - set-control:
+      part-id: pot-index
+      control: position
+      value: 1
+  - wait-serial: 'A0B4095C0D0E0F2047G2047I' # I is for Trigger gesture
+
+  # Curl Thumb finger
+  - set-control:
+      part-id: pot-thumb
+      control: position
+      value: 1
+  - wait-serial: 'A4095B4095C0D0E0F2047G2047IM' # M is for Pinch gesture
+
+  # Curl Middle finger
+  - set-control:
+      part-id: pot-middle
+      control: position
+      value: 1
+  - wait-serial: 'A4095B4095C4095D0E0F2047G2047IM'
+
+  # Curl Ring finger
+  - set-control:
+      part-id: pot-ring
+      control: position
+      value: 1
+  - wait-serial: 'A4095B4095C4095D4095E0F2047G2047IM'
+
+  # Curl Pinky finger
+  - set-control:
+      part-id: pot-pinky
+      control: position
+      value: 1
+  - wait-serial: 'A4095B4095C4095D4095E4095F2047G2047ILM' # L is for the Grab gesture
+
+  # Release Thumb finger
+  - set-control:
+      part-id: pot-thumb
+      control: position
+      value: 0
+  - wait-serial: 'A0B4095C4095D4095E4095F2047G2047IL'
diff --git a/.wokwi/lucidgloves-prototype3+serial/wokwi.toml b/.wokwi/lucidgloves-prototype3+serial/wokwi.toml
new file mode 100644
index 00000000..54e28de8
--- /dev/null
+++ b/.wokwi/lucidgloves-prototype3+serial/wokwi.toml
@@ -0,0 +1,4 @@
+[wokwi]
+version = 1
+firmware = "../../.pio/build/lucidgloves-prototype3/firmware.bin"
+elf = "../../.pio/build/lucidgloves-prototype3/firmware.elf"

From e7db5fbe43f70e9a0bd7190660ae37304f0c8b78 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Sun, 3 Sep 2023 13:19:03 +0400
Subject: [PATCH 27/82] refactor(OpenGloves): decouple transport and
 serialization

---
 .../mode_configs/opengloves/opengloves.cpp    |   8 +-
 .../opengloves/encoding/alpha.cpp}            |  31 +++-
 .../opengloves/encoding/alpha.hpp}            |  18 ++-
 .../senseshift/opengloves/interface.hpp       |  21 +++
 lib/opengloves/sensor/og_sensor.hpp           |   1 -
 .../opengloves/transport/stream.hpp           | 121 ++++++++++++++++
 .../og_serial_communication.hpp               | 137 ------------------
 lib/opengloves_task/opengloves_task.hpp       |  24 +--
 test/test_opengloves_alpha_encoding/main.cpp  |   7 +-
 9 files changed, 206 insertions(+), 162 deletions(-)
 rename lib/opengloves/{og_alpha_encoding.cpp => senseshift/opengloves/encoding/alpha.cpp} (65%)
 rename lib/opengloves/{og_alpha_encoding.hpp => senseshift/opengloves/encoding/alpha.hpp} (67%)
 create mode 100644 lib/opengloves/senseshift/opengloves/interface.hpp
 create mode 100644 lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp
 delete mode 100644 lib/opengloves_serial/og_serial_communication.hpp

diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp
index c58e221c..95c5f9c0 100644
--- a/firmware/mode_configs/opengloves/opengloves.cpp
+++ b/firmware/mode_configs/opengloves/opengloves.cpp
@@ -1,5 +1,4 @@
 #include <og_constants.hpp>
-#include <og_serial_communication.hpp>
 #include <opengloves_task.hpp>
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/input/sensor/digital.hpp>
@@ -7,6 +6,7 @@
 #include <senseshift/calibration.hpp>
 #include <senseshift/input/sensor.hpp>
 #include <senseshift/input/sensor/joystick.hpp>
+#include <senseshift/opengloves/transport/stream.hpp>
 #include <senseshift/utility.hpp>
 #include <sensor/og_finger.hpp>
 #include <sensor/og_gesture.hpp>
@@ -146,6 +146,7 @@
     (FFB_THUMB_ENABLED || FFB_INDEX_ENABLED || FFB_MIDDLE_ENABLED || FFB_RING_ENABLED || FFB_PINKY_ENABLED)
 
 using namespace OpenGloves;
+using namespace SenseShift::OpenGloves;
 
 HandSensors handSensors = {
 #if FINGER_THUMB_SPLAY
@@ -323,15 +324,16 @@ OpenGlovesForceFeedbackTask* ffbTask;
 void setupMode()
 {
 #if OPENGLOVES_COMMUNCATION == OPENGLOVES_COMM_SERIAL
-    auto* communication = new SerialCommunication(SERIAL_PORT, SERIAL_BAUDRATE);
+    SERIAL_PORT.begin(SERIAL_BAUDRATE);
+    auto* communication = new StreamTransport(SERIAL_PORT);
 #elif OPENGLOVES_COMMUNCATION == OPENGLOVES_COMM_BTSERIAL
 #ifdef BTSERIAL_NAME
     std::string name = BTSERIAL_NAME;
 #else
     char suffix[4];
     sprintf(suffix, "%04X", (uint16_t) (ESP.getEfuseMac() >> 32));
-    log_i("Generated Bluetooth suffix: %s", suffix);
     std::string name = BTSERIAL_PREFIX + std::string(suffix);
+    log_i("Generated Bluetooth name: %s", name.c_str());
 #endif
     BluetoothSerial* bt_serial = new BluetoothSerial();
     auto* communication = new BTSerialCommunication(*bt_serial, name);
diff --git a/lib/opengloves/og_alpha_encoding.cpp b/lib/opengloves/senseshift/opengloves/encoding/alpha.cpp
similarity index 65%
rename from lib/opengloves/og_alpha_encoding.cpp
rename to lib/opengloves/senseshift/opengloves/encoding/alpha.cpp
index da70fbe7..702a157c 100644
--- a/lib/opengloves/og_alpha_encoding.cpp
+++ b/lib/opengloves/senseshift/opengloves/encoding/alpha.cpp
@@ -1,7 +1,10 @@
-#include "og_alpha_encoding.hpp"
+#include "senseshift/opengloves/encoding/alpha.hpp"
 
-namespace OpenGloves {
-    std::map<Command, uint16_t> AlphaEncodingService::splitCommands(std::string input_string)
+#include <string.h>
+
+namespace SenseShift::OpenGloves {
+    const std::map<AlphaEncodingService::Command, uint16_t> AlphaEncodingService::deserialize(const std::string& input_string
+    ) const
     {
         std::map<Command, uint16_t> commands;
 
@@ -54,4 +57,24 @@ namespace OpenGloves {
         Command command = it->second;
         commands[command] = number;
     }
-} // namespace OpenGloves
+
+    const std::string AlphaEncodingService::serialize(const std::vector<::OpenGloves::IStringEncodedMemoizedSensor*>& sensors
+    )
+    {
+        memset(this->writeBuffer, 0, 256);
+        this->writeBuffer[0] = '\0';
+
+        size_t offset = 0;
+
+        for (size_t i = 0; i < sensors.size(); i++) {
+            // The offset is the total charecters already added to the string.
+            offset += sensors[i]->encodeString(this->writeBuffer + offset);
+        }
+
+        // Add a newline and terminator to the end of the encoded string.
+        this->writeBuffer[offset++] = '\n';
+        this->writeBuffer[offset] = '\0';
+
+        return std::string(this->writeBuffer, offset);
+    }
+} // namespace SenseShift::OpenGloves
diff --git a/lib/opengloves/og_alpha_encoding.hpp b/lib/opengloves/senseshift/opengloves/encoding/alpha.hpp
similarity index 67%
rename from lib/opengloves/og_alpha_encoding.hpp
rename to lib/opengloves/senseshift/opengloves/encoding/alpha.hpp
index f2ad57d1..59830803 100644
--- a/lib/opengloves/og_alpha_encoding.hpp
+++ b/lib/opengloves/senseshift/opengloves/encoding/alpha.hpp
@@ -13,9 +13,12 @@
 
 #include <og_protocol.hpp>
 #include <senseshift/logging.hpp>
+#include <senseshift/opengloves/interface.hpp>
+
+namespace SenseShift::OpenGloves {
+    class AlphaEncodingService : public IEncoding {
+        using Command = ::OpenGloves::Command;
 
-namespace OpenGloves {
-    class AlphaEncodingService {
       public:
         inline static constexpr frozen::string valueSymbols = "0123456789";
         inline static const auto commandMap = frozen::make_map<std::string, Command>({
@@ -33,15 +36,18 @@ namespace OpenGloves {
           // clang-format on
         });
 
-        AlphaEncodingService() = default;
+        AlphaEncodingService() : writeBuffer(new char[256]){};
+
+        virtual const std::map<Command, uint16_t> deserialize(const std::string& buffer) const override;
 
-        static std::map<Command, uint16_t> splitCommands(std::string input_string);
+        virtual const std::string serialize(const std::vector<::OpenGloves::IStringEncodedMemoizedSensor*>& sensors
+        ) override;
 
       private:
-        CommandCallback callback = nullptr;
+        char* writeBuffer;
 
         static void splitCommand(
           const std::string& input_string, size_t start, size_t end, std::map<Command, uint16_t>& commands
         );
     };
-} // namespace OpenGloves
+} // namespace SenseShift::OpenGloves
diff --git a/lib/opengloves/senseshift/opengloves/interface.hpp b/lib/opengloves/senseshift/opengloves/interface.hpp
new file mode 100644
index 00000000..17815845
--- /dev/null
+++ b/lib/opengloves/senseshift/opengloves/interface.hpp
@@ -0,0 +1,21 @@
+#pragma once
+
+#include <cstddef>
+#include <cstdint>
+
+#include "og_protocol.hpp"
+
+namespace SenseShift::OpenGloves {
+    struct ITransport {
+        virtual void setup(){};
+        virtual size_t send(const char* buffer, size_t length) = 0;
+        virtual bool hasData() = 0;
+        virtual size_t read(char* buffer, size_t length) = 0;
+    };
+
+    struct IEncoding {
+        virtual const std::string serialize(const std::vector<::OpenGloves::IStringEncodedMemoizedSensor*>& sensors
+        ) = 0;
+        virtual const std::map<::OpenGloves::Command, uint16_t> deserialize(const std::string& buffer) const = 0;
+    };
+} // namespace SenseShift::OpenGloves
diff --git a/lib/opengloves/sensor/og_sensor.hpp b/lib/opengloves/sensor/og_sensor.hpp
index 42c722de..caa4a9c8 100644
--- a/lib/opengloves/sensor/og_sensor.hpp
+++ b/lib/opengloves/sensor/og_sensor.hpp
@@ -82,7 +82,6 @@ namespace OpenGloves {
     size_t StringEncodedMemoizedSensor<FingerValue>::encodeString(char* buffer) const
     {
         size_t offset = 0;
-
         offset += snprintf(buffer + offset, 6, "%c%d", this->type, this->value.getTotalCurl());
 
         if (this->value.curl.size() > 1) {
diff --git a/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp b/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp
new file mode 100644
index 00000000..b198aac5
--- /dev/null
+++ b/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp
@@ -0,0 +1,121 @@
+#pragma once
+
+#include <BluetoothSerial.h>
+#include <HardwareSerial.h>
+#include <Print.h>
+#include <WiFi.h>
+
+#include <og_protocol.hpp>
+#include <senseshift/opengloves/interface.hpp>
+
+namespace SenseShift::OpenGloves {
+    class IStreamTransport : public ITransport {
+        using IStringEncodedMemoizedSensor = ::OpenGloves::IStringEncodedMemoizedSensor;
+
+      protected:
+        Stream* channel;
+        char* buffer = new char[256];
+
+      public:
+        IStreamTransport(Stream* channel) : channel(channel){};
+
+        size_t send(const char* buffer, size_t length) override
+        {
+            if (!this->isReady()) {
+                return 0;
+            }
+
+            const auto written = this->channel->write(buffer);
+            this->channel->flush();
+
+            return written;
+        }
+
+        virtual bool isReady() = 0;
+
+        virtual bool hasData() override
+        {
+            return this->isReady() && this->channel != nullptr && this->channel->available() > 0;
+        }
+
+        virtual size_t read(char* buffer, size_t length)
+        {
+            if (!this->hasData()) {
+                return false;
+            }
+
+            size_t bytesRead = this->channel->readBytesUntil('\n', buffer, length);
+            buffer[bytesRead] = '\0';
+
+            return bytesRead;
+        }
+    };
+
+    class StreamTransport : public IStreamTransport {
+      public:
+        StreamTransport(Stream& channel) : IStreamTransport(&channel){};
+        StreamTransport(Stream* channel) : IStreamTransport(channel){};
+
+        void setup() override { this->mReady = true; }
+
+        bool isReady() override { return this->channel != nullptr && this->mReady; }
+
+      private:
+        bool mReady = false;
+    };
+
+    class BluetoothSerialTransport : public IStreamTransport {
+      private:
+        std::string name;
+
+      public:
+        BluetoothSerialTransport(BluetoothSerial& channel, std::string name) : IStreamTransport(&channel), name(name){};
+
+        void setup() override
+        {
+            auto* serial = static_cast<BluetoothSerial*>(this->channel);
+            if (serial->isReady()) {
+                return;
+            }
+
+            serial->begin(name.c_str());
+        }
+
+        bool isReady() override
+        {
+            auto* serial = static_cast<BluetoothSerial*>(this->channel);
+            return serial->isReady() && serial->hasClient();
+        }
+    };
+
+    class WiFiSerialTransport : public IStreamTransport {
+      public:
+        WiFiSerialTransport(WiFiServer& server) : IStreamTransport(nullptr), m_server(server){};
+
+        void setup() override
+        {
+            auto* client = static_cast<WiFiClient*>(this->channel);
+            if (client != nullptr) {
+                if (client->connected()) {
+                    return;
+                }
+            }
+
+            this->m_server.begin();
+            this->channel = new WiFiClient(this->m_server.available());
+        }
+
+        bool isReady() override
+        {
+            if (this->channel == nullptr) {
+                return false;
+            }
+
+            auto* client = static_cast<WiFiClient*>(this->channel);
+            return client->connected();
+        }
+
+      private:
+        WiFiServer& m_server;
+    };
+} // namespace SenseShift::OpenGloves
diff --git a/lib/opengloves_serial/og_serial_communication.hpp b/lib/opengloves_serial/og_serial_communication.hpp
deleted file mode 100644
index 800b70e0..00000000
--- a/lib/opengloves_serial/og_serial_communication.hpp
+++ /dev/null
@@ -1,137 +0,0 @@
-#pragma once
-
-#include <BluetoothSerial.h>
-#include <HardwareSerial.h>
-#include <Print.h>
-#include <WiFi.h>
-
-#include <og_protocol.hpp>
-
-namespace OpenGloves {
-    class ISerialCommunication : public ICommunication {
-      protected:
-        Print& channel;
-        char* buffer = new char[256];
-
-      public:
-        ISerialCommunication(Print& channel) : channel(channel){};
-
-        void send(std::vector<IStringEncodedMemoizedSensor*>& sensors) override
-        {
-            if (!this->isReady()) {
-                return;
-            }
-
-            // Encode all of the sensors into a single string.
-            size_t length = encodeAll(buffer, sensors);
-            // Send the encoded string over serial.
-            this->channel.print(buffer);
-            this->channel.flush();
-        }
-
-        virtual bool isReady() = 0;
-
-        static size_t encodeAll(char* buffer, std::vector<IStringEncodedMemoizedSensor*>& sensors)
-        {
-            size_t offset = 0;
-            // Loop over all of the encoders and encode them to the output string.
-            for (size_t i = 0; i < sensors.size(); i++) {
-                // The offset is the total charecters already added to the string.
-                offset += sensors[i]->encodeString(buffer + offset);
-            }
-
-            // Add a new line to the end of the encoded string.
-            buffer[offset++] = '\n';
-            buffer[offset] = '\0';
-
-            return offset;
-        }
-
-        virtual bool hasData() = 0;
-
-        virtual size_t readCommand(char* buffer, size_t length) = 0;
-    };
-
-    class SerialCommunication : public ISerialCommunication {
-      private:
-        unsigned long baud;
-        bool ready = false;
-
-      public:
-        SerialCommunication(HardwareSerial& channel, unsigned long baud) : baud(baud), ISerialCommunication(channel){};
-
-        void setup() override
-        {
-            if (this->ready) {
-                return;
-            }
-
-            static_cast<HardwareSerial&>(this->channel).begin(this->baud);
-            this->ready = true;
-        }
-
-        bool isReady() override { return this->ready; }
-
-        bool hasData() override
-        {
-            return this->isReady() && static_cast<HardwareSerial&>(this->channel).available() > 0;
-        }
-
-        size_t readCommand(char* buffer, size_t length) override
-        {
-            if (!this->hasData()) {
-                return false;
-            }
-
-            size_t bytesRead = static_cast<HardwareSerial&>(this->channel).readBytesUntil('\n', buffer, length);
-            buffer[bytesRead] = '\0';
-
-            return bytesRead;
-        }
-    };
-
-    class BTSerialCommunication : public ISerialCommunication {
-      private:
-        std::string name;
-
-      public:
-        BTSerialCommunication(BluetoothSerial& channel, std::string name) : ISerialCommunication(channel), name(name){};
-
-        void setup() override
-        {
-            auto& serial = static_cast<BluetoothSerial&>(this->channel);
-            if (serial.isReady()) {
-                return;
-            }
-
-            // serial.register_callback([](esp_spp_cb_event_t event, esp_spp_cb_param_t *param) {
-            //     log_i("Bluetooth event: %d", event);
-            // });
-            // serial.setTimeout(4);
-            serial.begin(name.c_str());
-        }
-
-        bool isReady() override
-        {
-            auto& serial = static_cast<BluetoothSerial&>(this->channel);
-            return serial.isReady() && serial.hasClient();
-        }
-
-        bool hasData() override
-        {
-            return this->isReady() && static_cast<BluetoothSerial&>(this->channel).available() > 0;
-        }
-
-        size_t readCommand(char* buffer, size_t length) override
-        {
-            if (!this->hasData()) {
-                return false;
-            }
-
-            size_t bytesRead = static_cast<BluetoothSerial&>(this->channel).readBytesUntil('\n', buffer, length);
-            buffer[bytesRead] = '\0';
-
-            return bytesRead;
-        }
-    };
-} // namespace OpenGloves
diff --git a/lib/opengloves_task/opengloves_task.hpp b/lib/opengloves_task/opengloves_task.hpp
index 630d6d8a..5d4b90db 100644
--- a/lib/opengloves_task/opengloves_task.hpp
+++ b/lib/opengloves_task/opengloves_task.hpp
@@ -4,15 +4,15 @@
 
 #include <optional>
 
-#include <og_alpha_encoding.hpp>
 #include <og_ffb.hpp>
-#include <og_serial_communication.hpp>
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/input/sensor/digital.hpp>
 #include <senseshift/calibration.hpp>
 #include <senseshift/freertos/task.hpp>
 #include <senseshift/input/sensor.hpp>
 #include <senseshift/input/sensor/joystick.hpp>
+#include <senseshift/opengloves/encoding/alpha.hpp>
+#include <senseshift/opengloves/interface.hpp>
 #include <senseshift/utility.hpp>
 #include <sensor/og_finger.hpp>
 #include <sensor/og_gesture.hpp>
@@ -52,7 +52,7 @@ namespace OpenGloves {
          */
         OpenGlovesTrackingTask(
           OpenGlovesTrackingTaskConfig& config,
-          ICommunication& communication,
+          ::SenseShift::OpenGloves::ITransport& communication,
           HandSensors& fingers,
           std::vector<StringEncodedMemoizedSensor<bool>*>& buttons,
           std::vector<StringEncodedMemoizedSensor<uint16_t>*>& joysticks,
@@ -133,7 +133,10 @@ namespace OpenGloves {
         OpenGlovesTrackingTaskConfig& config;
 
         HandSensors& fingers;
-        ICommunication& communication;
+        ::SenseShift::OpenGloves::ITransport& communication;
+        ::SenseShift::OpenGloves::AlphaEncodingService encodingService =
+          ::SenseShift::OpenGloves::AlphaEncodingService();
+
         std::vector<StringEncodedMemoizedSensor<bool>*>& buttons;
         std::vector<StringEncodedMemoizedSensor<uint16_t>*>& joysticks;
         std::vector<IStringEncodedMemoizedSensor*>& otherSensors;
@@ -194,7 +197,8 @@ namespace OpenGloves {
                 }
 
                 // Send the sensor values.
-                this->communication.send(this->allSensors);
+                auto command = this->encodingService.serialize(this->allSensors);
+                this->communication.send(command.c_str(), command.size());
 
                 // Check if the calibration has finished.
                 if (!(this->config.alwaysCalibrate) && calibrationStarted > 0 && (now - calibrationStarted) > CALIBRATION_DURATION) {
@@ -220,7 +224,7 @@ namespace OpenGloves {
 
       public:
         OpenGlovesForceFeedbackTask(
-          ICommunication& communication,
+          ::SenseShift::OpenGloves::ITransport& communication,
           HandActuators& actuators,
           size_t updateRate,
           SenseShift::FreeRTOS::TaskConfig taskConfig
@@ -240,9 +244,11 @@ namespace OpenGloves {
         };
 
       private:
-        ICommunication& communication;
+        ::SenseShift::OpenGloves::ITransport& communication;
         HandActuators& actuators;
         size_t updateIntervalMs;
+        ::SenseShift::OpenGloves::AlphaEncodingService encodingService =
+          ::SenseShift::OpenGloves::AlphaEncodingService();
 
         void setup()
         {
@@ -278,14 +284,14 @@ namespace OpenGloves {
                 auto now = millis();
 
                 if (this->communication.hasData()) {
-                    auto bytesRead = this->communication.readCommand(commandBuffer, sizeof(commandBuffer));
+                    auto bytesRead = this->communication.read(commandBuffer, sizeof(commandBuffer));
                     if (bytesRead == 0) {
                         continue;
                     }
 
                     command.assign(commandBuffer, bytesRead);
 
-                    auto commands = AlphaEncodingService::splitCommands(command);
+                    auto commands = this->encodingService.deserialize(command);
 
                     for (auto& [command, value] : commands) {
                         this->handleCommand(command, value);
diff --git a/test/test_opengloves_alpha_encoding/main.cpp b/test/test_opengloves_alpha_encoding/main.cpp
index 7f5f7200..1f71136c 100644
--- a/test/test_opengloves_alpha_encoding/main.cpp
+++ b/test/test_opengloves_alpha_encoding/main.cpp
@@ -1,7 +1,8 @@
-#include <og_alpha_encoding.hpp>
+#include <senseshift/opengloves/encoding/alpha.hpp>
 #include <unity.h>
 
 using namespace OpenGloves;
+using namespace SenseShift::OpenGloves;
 
 void testSplitCommands(void)
 {
@@ -90,8 +91,10 @@ void testSplitCommands(void)
         },
     };
 
+    auto encoding_service = AlphaEncodingService();
+
     for (auto& [input_string, expected_commands] : input_strings) {
-        std::map<Command, uint16_t> commands = AlphaEncodingService::splitCommands(input_string);
+        std::map<Command, uint16_t> commands = encoding_service.deserialize(input_string);
 
         TEST_ASSERT_EQUAL_size_t_MESSAGE(
           expected_commands.size(),

From efae0e039051dd806d45ac6f4ca18cbcdcaccca9 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <lm@devolt.one>
Date: Sun, 3 Sep 2023 23:40:49 +0400
Subject: [PATCH 28/82] feat(Input): add averaged sensor

---
 .../mode_configs/opengloves/opengloves.cpp    | 32 +++++++++++++------
 lib/io/senseshift/input/sensor.hpp            | 26 +++++++++++++++
 2 files changed, 48 insertions(+), 10 deletions(-)

diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp
index 95c5f9c0..706e3b57 100644
--- a/firmware/mode_configs/opengloves/opengloves.cpp
+++ b/firmware/mode_configs/opengloves/opengloves.cpp
@@ -53,13 +53,16 @@
 #define FINGER_MIDDLE_ENABLED (defined(PIN_FINGER_MIDDLE) && (PIN_FINGER_MIDDLE != -1))
 #define FINGER_RING_ENABLED (defined(PIN_FINGER_RING) && (PIN_FINGER_RING != -1))
 #define FINGER_PINKY_ENABLED (defined(PIN_FINGER_PINKY) && (PIN_FINGER_PINKY != -1))
-#define FINGER_CLASS(type, curl_pin, curl_invert, curl_calib)                  \
-    FingerSensor(                                                              \
-      new ::SenseShift::Input::CalibratedSimpleSensor<uint16_t>(               \
-        new ::SenseShift::Arduino::Input::AnalogSensor<curl_invert>(curl_pin), \
-        new curl_calib()                                                       \
-      ),                                                                       \
-      type                                                                     \
+#define FINGER_CLASS(type, curl_pin, curl_invert, curl_calib)                    \
+    FingerSensor(                                                                \
+      new ::SenseShift::Input::CalibratedSimpleSensor<uint16_t>(                 \
+        new ::SenseShift::Input::AverageSensor<uint16_t>(                        \
+          new ::SenseShift::Arduino::Input::AnalogSensor<curl_invert>(curl_pin), \
+          5                                                                      \
+        ),                                                                       \
+        new curl_calib()                                                         \
+      ),                                                                         \
+      type                                                                       \
     )
 #define FINGER_THUMB_SPLAY (FINGER_THUMB_ENABLED && defined(PIN_FINGER_THUMB_SPLAY) && (PIN_FINGER_THUMB_SPLAY != -1))
 #define FINGER_INDEX_SPLAY (FINGER_INDEX_ENABLED && defined(PIN_FINGER_INDEX_SPLAY) && (PIN_FINGER_INDEX_SPLAY != -1))
@@ -70,11 +73,17 @@
 #define FINGER_SPLAY_CLASS(type, curl_pin, curl_invert, curl_calib, splay_pin, splay_invert, splay_calib) \
     FingerSensor(                                                                                         \
       new ::SenseShift::Input::CalibratedSimpleSensor<uint16_t>(                                          \
-        new ::SenseShift::Arduino::Input::AnalogSensor<curl_invert>(curl_pin),                            \
+        new ::SenseShift::Input::AverageSensor<uint16_t>(                                                 \
+          new ::SenseShift::Arduino::Input::AnalogSensor<curl_invert>(curl_pin),                          \
+          5                                                                                               \
+        ),                                                                                                \
         new curl_calib()                                                                                  \
       ),                                                                                                  \
       new ::SenseShift::Input::CalibratedSimpleSensor<uint16_t>(                                          \
-        new ::SenseShift::Arduino::Input::AnalogSensor<splay_invert>(splay_pin),                          \
+        new ::SenseShift::Input::AverageSensor<uint16_t>( \
+          new ::SenseShift::Arduino::Input::AnalogSensor<splay_invert>(splay_pin), \
+          5 \
+        ),                          \
         new splay_calib()                                                                                 \
       ),                                                                                                  \
       type                                                                                                \
@@ -90,7 +99,10 @@
 #define JOYSTICK_CLASS(type, pin, invert, deadzone)                  \
     StringEncodedMemoizedSensor<uint16_t>(                           \
       new ::SenseShift::Input::JoystickAxisSensor<uint16_t>(         \
-        new ::SenseShift::Arduino::Input::AnalogSensor<invert>(pin), \
+        new ::SenseShift::Input::AverageSensor<uint16_t>(                \
+            new ::SenseShift::Arduino::Input::AnalogSensor<invert>(pin), \
+            5                                                            \
+        ),                                                               \
         deadzone                                                     \
       ),                                                             \
       type                                                           \
diff --git a/lib/io/senseshift/input/sensor.hpp b/lib/io/senseshift/input/sensor.hpp
index a4f5e160..c3ce962c 100644
--- a/lib/io/senseshift/input/sensor.hpp
+++ b/lib/io/senseshift/input/sensor.hpp
@@ -108,4 +108,30 @@ namespace SenseShift::Input {
             return this->calibrator->calibrate(value);
         }
     };
+
+    template <typename _Tp>
+    class AverageSensor : public ISimpleSensor<_Tp>
+    {
+        static_assert(std::is_arithmetic<_Tp>::value, "AverageSensor only supports arithmetic types");
+
+      public:
+        AverageSensor(ISimpleSensor<_Tp>* sensor, size_t samples) : sensor(sensor), samples(samples){ }
+
+        void init() override { this->sensor->init(); };
+
+        _Tp getValue() override
+        {
+            // TODO: another type for sum?
+            double sum = 0;
+            for (size_t i = 0; i < this->samples; i++) {
+                sum += this->sensor->getValue();
+            }
+
+            return sum / this->samples;
+        }
+
+      private:
+        ISimpleSensor<_Tp>* sensor;
+        size_t samples;
+    };
 } // namespace SenseShift::Input

From e28d5dfa45e84c1f403cbbce70d7c2b81f77d0a6 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <lm@devolt.one>
Date: Sun, 3 Sep 2023 23:56:08 +0400
Subject: [PATCH 29/82] refactor(OpenGloves): auto-configure transport

---
 .../mode_configs/opengloves/opengloves.cpp    | 163 +--------------
 .../senseshift/opengloves/autoconfig.hpp      | 185 ++++++++++++++++++
 .../opengloves/transport/stream.hpp           |   7 +-
 3 files changed, 188 insertions(+), 167 deletions(-)
 create mode 100644 lib/opengloves/senseshift/opengloves/autoconfig.hpp

diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp
index 706e3b57..ff39cdff 100644
--- a/firmware/mode_configs/opengloves/opengloves.cpp
+++ b/firmware/mode_configs/opengloves/opengloves.cpp
@@ -6,157 +6,12 @@
 #include <senseshift/calibration.hpp>
 #include <senseshift/input/sensor.hpp>
 #include <senseshift/input/sensor/joystick.hpp>
+#include <senseshift/opengloves/autoconfig.hpp>
 #include <senseshift/opengloves/transport/stream.hpp>
 #include <senseshift/utility.hpp>
 #include <sensor/og_finger.hpp>
 #include <sensor/og_gesture.hpp>
 
-#pragma region Communication
-
-#ifndef OPENGLOVES_COMMUNCATION
-#define OPENGLOVES_COMMUNCATION OPENGLOVES_COMM_SERIAL
-#endif
-
-#ifndef SERIAL_PORT
-#define SERIAL_PORT Serial
-#endif
-
-#ifndef SERIAL_BAUDRATE
-#define SERIAL_BAUDRATE 115200
-#endif
-
-#ifndef BTSERIAL_PREFIX
-#define BTSERIAL_PREFIX "SenseShift_OG"
-#endif
-
-#pragma endregion
-
-#pragma region Calibration
-
-#ifndef CALIBRATION_CURL
-#define CALIBRATION_CURL ::SenseShift::Calibration::MinMaxCalibrator<uint16_t, 0, ANALOG_MAX>
-#endif
-#ifndef CALIBRATION_SPLAY
-#define CALIBRATION_SPLAY ::SenseShift::Calibration::MinMaxCalibrator<uint16_t, 0, ANALOG_MAX>
-#endif
-
-#ifndef CALIBRATION_DURATION
-#define CALIBRATION_DURATION 2000 // duration in milliseconds
-#endif
-
-#pragma endregion
-
-#pragma region Fingers
-
-#define FINGER_THUMB_ENABLED (defined(PIN_FINGER_THUMB) && (PIN_FINGER_THUMB != -1))
-#define FINGER_INDEX_ENABLED (defined(PIN_FINGER_INDEX) && (PIN_FINGER_INDEX != -1))
-#define FINGER_MIDDLE_ENABLED (defined(PIN_FINGER_MIDDLE) && (PIN_FINGER_MIDDLE != -1))
-#define FINGER_RING_ENABLED (defined(PIN_FINGER_RING) && (PIN_FINGER_RING != -1))
-#define FINGER_PINKY_ENABLED (defined(PIN_FINGER_PINKY) && (PIN_FINGER_PINKY != -1))
-#define FINGER_CLASS(type, curl_pin, curl_invert, curl_calib)                    \
-    FingerSensor(                                                                \
-      new ::SenseShift::Input::CalibratedSimpleSensor<uint16_t>(                 \
-        new ::SenseShift::Input::AverageSensor<uint16_t>(                        \
-          new ::SenseShift::Arduino::Input::AnalogSensor<curl_invert>(curl_pin), \
-          5                                                                      \
-        ),                                                                       \
-        new curl_calib()                                                         \
-      ),                                                                         \
-      type                                                                       \
-    )
-#define FINGER_THUMB_SPLAY (FINGER_THUMB_ENABLED && defined(PIN_FINGER_THUMB_SPLAY) && (PIN_FINGER_THUMB_SPLAY != -1))
-#define FINGER_INDEX_SPLAY (FINGER_INDEX_ENABLED && defined(PIN_FINGER_INDEX_SPLAY) && (PIN_FINGER_INDEX_SPLAY != -1))
-#define FINGER_MIDDLE_SPLAY \
-    (FINGER_MIDDLE_ENABLED && defined(PIN_FINGER_MIDDLE_SPLAY) && (PIN_FINGER_MIDDLE_SPLAY != -1))
-#define FINGER_RING_SPLAY (FINGER_RING_ENABLED && defined(PIN_FINGER_RING_SPLAY) && (PIN_FINGER_RING_SPLAY != -1))
-#define FINGER_PINKY_SPLAY (FINGER_PINKY_ENABLED && defined(PIN_FINGER_PINKY_SPLAY) && (PIN_FINGER_PINKY_SPLAY != -1))
-#define FINGER_SPLAY_CLASS(type, curl_pin, curl_invert, curl_calib, splay_pin, splay_invert, splay_calib) \
-    FingerSensor(                                                                                         \
-      new ::SenseShift::Input::CalibratedSimpleSensor<uint16_t>(                                          \
-        new ::SenseShift::Input::AverageSensor<uint16_t>(                                                 \
-          new ::SenseShift::Arduino::Input::AnalogSensor<curl_invert>(curl_pin),                          \
-          5                                                                                               \
-        ),                                                                                                \
-        new curl_calib()                                                                                  \
-      ),                                                                                                  \
-      new ::SenseShift::Input::CalibratedSimpleSensor<uint16_t>(                                          \
-        new ::SenseShift::Input::AverageSensor<uint16_t>( \
-          new ::SenseShift::Arduino::Input::AnalogSensor<splay_invert>(splay_pin), \
-          5 \
-        ),                          \
-        new splay_calib()                                                                                 \
-      ),                                                                                                  \
-      type                                                                                                \
-    )
-
-#pragma endregion
-
-#pragma region Joysticks
-
-#define JOYSTICK_ENABLED \
-    (defined(PIN_JOYSTICK_X) && defined(PIN_JOYSTICK_Y) && (PIN_JOYSTICK_X != -1) && (PIN_JOYSTICK_Y != -1))
-
-#define JOYSTICK_CLASS(type, pin, invert, deadzone)                  \
-    StringEncodedMemoizedSensor<uint16_t>(                           \
-      new ::SenseShift::Input::JoystickAxisSensor<uint16_t>(         \
-        new ::SenseShift::Input::AverageSensor<uint16_t>(                \
-            new ::SenseShift::Arduino::Input::AnalogSensor<invert>(pin), \
-            5                                                            \
-        ),                                                               \
-        deadzone                                                     \
-      ),                                                             \
-      type                                                           \
-    )
-
-#pragma endregion
-
-#pragma region Buttons
-
-#define BUTTON_A_ENABLED (defined(PIN_BUTTON_A) && (PIN_BUTTON_A != -1))
-#define BUTTON_B_ENABLED (defined(PIN_BUTTON_B) && (PIN_BUTTON_B != -1))
-#define BUTTON_MENU_ENABLED (defined(PIN_BUTTON_MENU) && (PIN_BUTTON_MENU != -1))
-#define BUTTON_JOYSTICK_ENABLED (JOYSTICK_ENABLED && defined(PIN_BUTTON_JOYSTICK) && (PIN_BUTTON_JOYSTICK != -1))
-#define BUTTON_CALIBRATE_ENABLED (defined(PIN_BUTTON_CALIBRATE) && (PIN_BUTTON_CALIBRATE != -1))
-#define BUTTON_TRIGGER_ENABLED (!GESTURE_TRIGGER_ENABLED && defined(PIN_BUTTON_TRIGGER) && (PIN_BUTTON_TRIGGER != -1))
-#define BUTTON_GRAB_ENABLED (!GESTURE_GRAB_ENABLED && defined(PIN_BUTTON_GRAB) && (PIN_BUTTON_GRAB != -1))
-#define BUTTON_PINCH_ENABLED (!GESTURE_PINCH_ENABLED && defined(PIN_BUTTON_PINCH) && (PIN_BUTTON_PINCH != -1))
-
-#define BUTTON_CLASS(type, pin, invert) \
-    StringEncodedMemoizedSensor<bool>(new ::SenseShift::Arduino::Input::DigitalSensor<invert>(pin), type)
-
-#pragma endregion
-
-#pragma region Gestures
-
-#ifndef GESTURE_TRIGGER_THRESHOLD
-#define GESTURE_TRIGGER_THRESHOLD (ANALOG_MAX / 2)
-#endif
-
-#ifndef GESTURE_GRAB_THRESHOLD
-#define GESTURE_GRAB_THRESHOLD (ANALOG_MAX / 2)
-#endif
-
-#ifndef GESTURE_PINCH_THRESHOLD
-#define GESTURE_PINCH_THRESHOLD (ANALOG_MAX / 2)
-#endif
-
-#define GESTURE_CLASS(type, sensor) StringEncodedMemoizedSensor<bool>(sensor, type)
-
-#pragma endregion
-
-#ifndef UPDATE_RATE
-#define UPDATE_RATE 90
-#endif
-
-#define FFB_THUMB_ENABLED (defined(PIN_FFB_THUMB) && (PIN_FFB_THUMB != -1))
-#define FFB_INDEX_ENABLED (defined(PIN_FFB_INDEX) && (PIN_FFB_INDEX != -1))
-#define FFB_MIDDLE_ENABLED (defined(PIN_FFB_MIDDLE) && (PIN_FFB_MIDDLE != -1))
-#define FFB_RING_ENABLED (defined(PIN_FFB_RING) && (PIN_FFB_RING != -1))
-#define FFB_PINKY_ENABLED (defined(PIN_FFB_PINKY) && (PIN_FFB_PINKY != -1))
-
-#define FFB_ENABLED \
-    (FFB_THUMB_ENABLED || FFB_INDEX_ENABLED || FFB_MIDDLE_ENABLED || FFB_RING_ENABLED || FFB_PINKY_ENABLED)
-
 using namespace OpenGloves;
 using namespace SenseShift::OpenGloves;
 
@@ -335,21 +190,7 @@ OpenGlovesForceFeedbackTask* ffbTask;
 
 void setupMode()
 {
-#if OPENGLOVES_COMMUNCATION == OPENGLOVES_COMM_SERIAL
-    SERIAL_PORT.begin(SERIAL_BAUDRATE);
-    auto* communication = new StreamTransport(SERIAL_PORT);
-#elif OPENGLOVES_COMMUNCATION == OPENGLOVES_COMM_BTSERIAL
-#ifdef BTSERIAL_NAME
-    std::string name = BTSERIAL_NAME;
-#else
-    char suffix[4];
-    sprintf(suffix, "%04X", (uint16_t) (ESP.getEfuseMac() >> 32));
-    std::string name = BTSERIAL_PREFIX + std::string(suffix);
-    log_i("Generated Bluetooth name: %s", name.c_str());
-#endif
-    BluetoothSerial* bt_serial = new BluetoothSerial();
-    auto* communication = new BTSerialCommunication(*bt_serial, name);
-#endif
+    auto* communication = AutoConfig::setupTransport();
 
     trackingTask = new OpenGlovesTrackingTask(
       config,
diff --git a/lib/opengloves/senseshift/opengloves/autoconfig.hpp b/lib/opengloves/senseshift/opengloves/autoconfig.hpp
new file mode 100644
index 00000000..ed585ef4
--- /dev/null
+++ b/lib/opengloves/senseshift/opengloves/autoconfig.hpp
@@ -0,0 +1,185 @@
+#pragma once
+
+#include <senseshift/opengloves/interface.hpp>
+
+#ifdef ARDUINO
+#include <senseshift/opengloves/transport/stream.hpp>
+#endif
+
+#pragma region Communication
+
+#ifndef OPENGLOVES_COMMUNCATION
+#define OPENGLOVES_COMMUNCATION OPENGLOVES_COMM_SERIAL
+#endif
+
+#ifndef SERIAL_PORT
+#define SERIAL_PORT Serial
+#endif
+
+#ifndef SERIAL_BAUDRATE
+#define SERIAL_BAUDRATE 115200
+#endif
+
+#ifndef BTSERIAL_PREFIX
+#define BTSERIAL_PREFIX "SenseShift_OG"
+#endif
+
+#pragma endregion
+
+#pragma region Calibration
+
+#ifndef CALIBRATION_CURL
+#define CALIBRATION_CURL ::SenseShift::Calibration::MinMaxCalibrator<uint16_t, 0, ANALOG_MAX>
+#endif
+#ifndef CALIBRATION_SPLAY
+#define CALIBRATION_SPLAY ::SenseShift::Calibration::MinMaxCalibrator<uint16_t, 0, ANALOG_MAX>
+#endif
+
+#ifndef CALIBRATION_DURATION
+#define CALIBRATION_DURATION 2000 // duration in milliseconds
+#endif
+
+#pragma endregion
+
+#pragma region Fingers
+
+#define FINGER_THUMB_ENABLED (defined(PIN_FINGER_THUMB) && (PIN_FINGER_THUMB != -1))
+#define FINGER_INDEX_ENABLED (defined(PIN_FINGER_INDEX) && (PIN_FINGER_INDEX != -1))
+#define FINGER_MIDDLE_ENABLED (defined(PIN_FINGER_MIDDLE) && (PIN_FINGER_MIDDLE != -1))
+#define FINGER_RING_ENABLED (defined(PIN_FINGER_RING) && (PIN_FINGER_RING != -1))
+#define FINGER_PINKY_ENABLED (defined(PIN_FINGER_PINKY) && (PIN_FINGER_PINKY != -1))
+#define FINGER_CLASS(type, curl_pin, curl_invert, curl_calib)                    \
+    FingerSensor(                                                                \
+      new ::SenseShift::Input::CalibratedSimpleSensor<uint16_t>(                 \
+        new ::SenseShift::Input::AverageSensor<uint16_t>(                        \
+          new ::SenseShift::Arduino::Input::AnalogSensor<curl_invert>(curl_pin), \
+          5                                                                      \
+        ),                                                                       \
+        new curl_calib()                                                         \
+      ),                                                                         \
+      type                                                                       \
+    )
+#define FINGER_THUMB_SPLAY (FINGER_THUMB_ENABLED && defined(PIN_FINGER_THUMB_SPLAY) && (PIN_FINGER_THUMB_SPLAY != -1))
+#define FINGER_INDEX_SPLAY (FINGER_INDEX_ENABLED && defined(PIN_FINGER_INDEX_SPLAY) && (PIN_FINGER_INDEX_SPLAY != -1))
+#define FINGER_MIDDLE_SPLAY \
+    (FINGER_MIDDLE_ENABLED && defined(PIN_FINGER_MIDDLE_SPLAY) && (PIN_FINGER_MIDDLE_SPLAY != -1))
+#define FINGER_RING_SPLAY (FINGER_RING_ENABLED && defined(PIN_FINGER_RING_SPLAY) && (PIN_FINGER_RING_SPLAY != -1))
+#define FINGER_PINKY_SPLAY (FINGER_PINKY_ENABLED && defined(PIN_FINGER_PINKY_SPLAY) && (PIN_FINGER_PINKY_SPLAY != -1))
+#define FINGER_SPLAY_CLASS(type, curl_pin, curl_invert, curl_calib, splay_pin, splay_invert, splay_calib) \
+    FingerSensor(                                                                                         \
+      new ::SenseShift::Input::CalibratedSimpleSensor<uint16_t>(                                          \
+        new ::SenseShift::Input::AverageSensor<uint16_t>(                                                 \
+          new ::SenseShift::Arduino::Input::AnalogSensor<curl_invert>(curl_pin),                          \
+          5                                                                                               \
+        ),                                                                                                \
+        new curl_calib()                                                                                  \
+      ),                                                                                                  \
+      new ::SenseShift::Input::CalibratedSimpleSensor<uint16_t>(                                          \
+        new ::SenseShift::Input::AverageSensor<uint16_t>( \
+          new ::SenseShift::Arduino::Input::AnalogSensor<splay_invert>(splay_pin), \
+          5 \
+        ),                          \
+        new splay_calib()                                                                                 \
+      ),                                                                                                  \
+      type                                                                                                \
+    )
+
+#pragma endregion
+
+#pragma region Joysticks
+
+#define JOYSTICK_ENABLED \
+    (defined(PIN_JOYSTICK_X) && defined(PIN_JOYSTICK_Y) && (PIN_JOYSTICK_X != -1) && (PIN_JOYSTICK_Y != -1))
+
+#define JOYSTICK_CLASS(type, pin, invert, deadzone)                  \
+    StringEncodedMemoizedSensor<uint16_t>(                           \
+      new ::SenseShift::Input::JoystickAxisSensor<uint16_t>(         \
+        new ::SenseShift::Input::AverageSensor<uint16_t>(                \
+            new ::SenseShift::Arduino::Input::AnalogSensor<invert>(pin), \
+            5                                                            \
+        ),                                                               \
+        deadzone                                                     \
+      ),                                                             \
+      type                                                           \
+    )
+
+#pragma endregion
+
+#pragma region Buttons
+
+#define BUTTON_A_ENABLED (defined(PIN_BUTTON_A) && (PIN_BUTTON_A != -1))
+#define BUTTON_B_ENABLED (defined(PIN_BUTTON_B) && (PIN_BUTTON_B != -1))
+#define BUTTON_MENU_ENABLED (defined(PIN_BUTTON_MENU) && (PIN_BUTTON_MENU != -1))
+#define BUTTON_JOYSTICK_ENABLED (JOYSTICK_ENABLED && defined(PIN_BUTTON_JOYSTICK) && (PIN_BUTTON_JOYSTICK != -1))
+#define BUTTON_CALIBRATE_ENABLED (defined(PIN_BUTTON_CALIBRATE) && (PIN_BUTTON_CALIBRATE != -1))
+#define BUTTON_TRIGGER_ENABLED (!GESTURE_TRIGGER_ENABLED && defined(PIN_BUTTON_TRIGGER) && (PIN_BUTTON_TRIGGER != -1))
+#define BUTTON_GRAB_ENABLED (!GESTURE_GRAB_ENABLED && defined(PIN_BUTTON_GRAB) && (PIN_BUTTON_GRAB != -1))
+#define BUTTON_PINCH_ENABLED (!GESTURE_PINCH_ENABLED && defined(PIN_BUTTON_PINCH) && (PIN_BUTTON_PINCH != -1))
+
+#define BUTTON_CLASS(_type, _pin, _invert) \
+    StringEncodedMemoizedSensor<bool>(new ::SenseShift::Arduino::Input::DigitalSensor<_invert>(_pin), _type)
+
+#pragma endregion
+
+#pragma region Gestures
+
+#ifndef GESTURE_TRIGGER_THRESHOLD
+#define GESTURE_TRIGGER_THRESHOLD (ANALOG_MAX / 2)
+#endif
+
+#ifndef GESTURE_GRAB_THRESHOLD
+#define GESTURE_GRAB_THRESHOLD (ANALOG_MAX / 2)
+#endif
+
+#ifndef GESTURE_PINCH_THRESHOLD
+#define GESTURE_PINCH_THRESHOLD (ANALOG_MAX / 2)
+#endif
+
+#define GESTURE_CLASS(_type, _sensor) StringEncodedMemoizedSensor<bool>(_sensor, _type)
+
+#pragma endregion
+
+#define FFB_THUMB_ENABLED (defined(PIN_FFB_THUMB) && (PIN_FFB_THUMB != -1))
+#define FFB_INDEX_ENABLED (defined(PIN_FFB_INDEX) && (PIN_FFB_INDEX != -1))
+#define FFB_MIDDLE_ENABLED (defined(PIN_FFB_MIDDLE) && (PIN_FFB_MIDDLE != -1))
+#define FFB_RING_ENABLED (defined(PIN_FFB_RING) && (PIN_FFB_RING != -1))
+#define FFB_PINKY_ENABLED (defined(PIN_FFB_PINKY) && (PIN_FFB_PINKY != -1))
+
+#define FFB_ENABLED \
+    (FFB_THUMB_ENABLED || FFB_INDEX_ENABLED || FFB_MIDDLE_ENABLED || FFB_RING_ENABLED || FFB_PINKY_ENABLED)
+
+#ifndef UPDATE_RATE
+#define UPDATE_RATE 90
+#endif
+
+namespace SenseShift::OpenGloves::AutoConfig {
+
+    /**
+     * Setup the transport for the OpenGloves interface.
+     */
+    [[nodiscard]] ITransport* setupTransport(void)
+    {
+#if OPENGLOVES_COMMUNCATION == OPENGLOVES_COMM_SERIAL // Serial
+        auto* pSerial = &SERIAL_PORT;
+        pSerial->begin(SERIAL_BAUDRATE);
+        return new StreamTransport(pSerial);
+#elif OPENGLOVES_COMMUNCATION == OPENGLOVES_COMM_BTSERIAL // Bluetooth Serial
+        std::string name;
+#ifdef BTSERIAL_NAME
+        name = BTSERIAL_NAME;
+#else
+        char suffix[4];
+        snprintf(suffix, 4, "%04X", (uint16_t) (ESP.getEfuseMac() >> 32));
+        name = BTSERIAL_PREFIX + std::string(suffix);
+
+        log_i("Generated Bluetooth name: %s", name.c_str());
+#endif
+        BluetoothSerial* pBtSerial = new BluetoothSerial();
+        pBtSerial->begin(name.c_str());
+        return new BluetoothSerialTransport(*pBtSerial);
+#else // Fallback
+#error "Unsupported communication type"
+        return nullptr;
+#endif
+    }
+} // namespace SenseShift::OpenGloves::AutoConfig
diff --git a/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp b/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp
index b198aac5..f0cb609a 100644
--- a/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp
+++ b/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp
@@ -65,11 +65,8 @@ namespace SenseShift::OpenGloves {
     };
 
     class BluetoothSerialTransport : public IStreamTransport {
-      private:
-        std::string name;
-
       public:
-        BluetoothSerialTransport(BluetoothSerial& channel, std::string name) : IStreamTransport(&channel), name(name){};
+        BluetoothSerialTransport(BluetoothSerial& channel) : IStreamTransport(&channel){};
 
         void setup() override
         {
@@ -77,8 +74,6 @@ namespace SenseShift::OpenGloves {
             if (serial->isReady()) {
                 return;
             }
-
-            serial->begin(name.c_str());
         }
 
         bool isReady() override

From 83e95fea73ca8c8d4920ee5878e1d88037e5ea4a Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <lm@devolt.one>
Date: Sun, 3 Sep 2023 23:56:24 +0400
Subject: [PATCH 30/82] feat(OpenGloves): debug performance

---
 ini/opengloves.ini                      | 4 ++--
 lib/opengloves_task/opengloves_task.hpp | 3 +++
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/ini/opengloves.ini b/ini/opengloves.ini
index 15732211..f4526043 100644
--- a/ini/opengloves.ini
+++ b/ini/opengloves.ini
@@ -5,7 +5,7 @@ platform_packages	=
 framework 	     	= arduino
 board            	= esp32doit-devkit-v1
 upload_speed     	= 921600
-monitor_speed    	= 115200
+monitor_speed    	= 921600
 
 build_flags 		= ${common.build_flags}
 	-D OPENGLOVES
@@ -13,7 +13,7 @@ build_flags 		= ${common.build_flags}
 	; Communication
 	; -D OPENGLOVES_COMMUNCATION=OPENGLOVES_COMM_SERIAL ; OPENGLOVES_COMM_SERIAL, OPENGLOVES_COMM_BTSERIAL
 	; Serial
-	-D SERIAL_BAUDRATE=115200
+	-D SERIAL_BAUDRATE=921600
 	-D SERIAL_PORT=Serial ; Serial, Serial1, Serial2, Serial3
 	; BTSerial
 	'-D BTSERIAL_PREFIX="SenseShift_OG"'
diff --git a/lib/opengloves_task/opengloves_task.hpp b/lib/opengloves_task/opengloves_task.hpp
index 5d4b90db..87afffca 100644
--- a/lib/opengloves_task/opengloves_task.hpp
+++ b/lib/opengloves_task/opengloves_task.hpp
@@ -212,6 +212,9 @@ namespace OpenGloves {
 
                 // Delay until the next update.
                 auto elapsed = millis() - now;
+
+                log_d("Update took %d ms, theoretical max rate is %dHz (target is %dHz)", elapsed, 1000 / elapsed, 1000 / this->config.updateIntervalMs);
+
                 if (elapsed < this->config.updateIntervalMs) {
                     delay(this->config.updateIntervalMs - elapsed);
                 }

From 1f21404aa2d09dfd20717c562b9249f59d1739b0 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <lm@devolt.one>
Date: Mon, 4 Sep 2023 00:25:52 +0400
Subject: [PATCH 31/82] feat(OpenGloves): add BLE stream

---
 .github/scripts/get_firmware_name.sh          |   3 +
 .../mode_configs/opengloves/opengloves.cpp    |  29 ++-
 ini/opengloves.ini                            |   6 +-
 lib/ble_serial/BLESerial.hpp                  | 224 ++++++++++++++++++
 lib/ble_serial/library.json                   |   7 +
 lib/opengloves/og_constants.hpp               |   1 +
 .../senseshift/opengloves/autoconfig.hpp      |   9 +-
 .../opengloves/transport/stream.hpp           |  28 ++-
 lib/opengloves_task/opengloves_task.hpp       |   2 +-
 9 files changed, 282 insertions(+), 27 deletions(-)
 create mode 100644 lib/ble_serial/BLESerial.hpp
 create mode 100644 lib/ble_serial/library.json

diff --git a/.github/scripts/get_firmware_name.sh b/.github/scripts/get_firmware_name.sh
index 63b0b610..36b1bfb9 100755
--- a/.github/scripts/get_firmware_name.sh
+++ b/.github/scripts/get_firmware_name.sh
@@ -62,6 +62,9 @@ getOpenGlovesName() {
     elif [[ $flags =~ OPENGLOVES_COMM_BTSERIAL ]]; then
         echo "::debug::Bluetooth Serial is enabled, appending +bluetooth to the target"
         target="$target+bluetooth"
+    elif [[ $flags =~ OPENGLOVES_COMM_BLESERIAL ]]; then
+        echo "::debug::BLE Serial is enabled, appending +ble to the target"
+        target="$target+ble"
     fi
 
     for flag in "${@:2}"; do
diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp
index ff39cdff..76706b73 100644
--- a/firmware/mode_configs/opengloves/opengloves.cpp
+++ b/firmware/mode_configs/opengloves/opengloves.cpp
@@ -206,22 +206,21 @@ void setupMode()
         .priority = OPENGLOVES_FINGERS_TASK_PRIORITY,
       }
     );
-
-#if FFB_ENABLED
-    ffbTask = new OpenGlovesForceFeedbackTask(
-      *communication,
-      handActuators,
-      UPDATE_RATE,
-      {
-        .name = "OpenGlovesForceFeedbackTask",
-        .stackDepth = 8192,
-        .priority = OPENGLOVES_FINGERS_TASK_PRIORITY,
-      }
-    );
-    ffbTask->begin();
-#endif
-
     trackingTask->begin();
+
+// #if FFB_ENABLED
+//     ffbTask = new OpenGlovesForceFeedbackTask(
+//       *communication,
+//       handActuators,
+//       UPDATE_RATE,
+//       {
+//         .name = "OpenGlovesForceFeedbackTask",
+//         .stackDepth = 8192,
+//         .priority = OPENGLOVES_FINGERS_TASK_PRIORITY,
+//       }
+//     );
+//     ffbTask->begin();
+// #endif
 }
 
 void loopMode()
diff --git a/ini/opengloves.ini b/ini/opengloves.ini
index f4526043..0160ae8d 100644
--- a/ini/opengloves.ini
+++ b/ini/opengloves.ini
@@ -5,15 +5,15 @@ platform_packages	=
 framework 	     	= arduino
 board            	= esp32doit-devkit-v1
 upload_speed     	= 921600
-monitor_speed    	= 921600
+monitor_speed    	= 115200
 
 build_flags 		= ${common.build_flags}
 	-D OPENGLOVES
 
 	; Communication
-	; -D OPENGLOVES_COMMUNCATION=OPENGLOVES_COMM_SERIAL ; OPENGLOVES_COMM_SERIAL, OPENGLOVES_COMM_BTSERIAL
+	; -D OPENGLOVES_COMMUNCATION=OPENGLOVES_COMM_SERIAL ; OPENGLOVES_COMM_SERIAL, OPENGLOVES_COMM_BTSERIAL, OPENGLOVES_COMM_BLESERIAL
 	; Serial
-	-D SERIAL_BAUDRATE=921600
+	-D SERIAL_BAUDRATE=115200
 	-D SERIAL_PORT=Serial ; Serial, Serial1, Serial2, Serial3
 	; BTSerial
 	'-D BTSERIAL_PREFIX="SenseShift_OG"'
diff --git a/lib/ble_serial/BLESerial.hpp b/lib/ble_serial/BLESerial.hpp
new file mode 100644
index 00000000..89572837
--- /dev/null
+++ b/lib/ble_serial/BLESerial.hpp
@@ -0,0 +1,224 @@
+#pragma once
+#ifndef BLESERIAL_H
+#define BLESERIAL_H
+
+#include <Arduino.h>
+#include <BLEDevice.h>
+#include <BLEUtils.h>
+#include <BLEServer.h>
+#include <BLE2902.h>
+
+#define BLESERIAL_ATTRIBUTE_MAX_VALUE_LENGTH 20
+#define BLESERIAL_RECEIVE_BUFFER_SIZE 256
+
+template<typename _Tp, size_t N>
+class ByteRingBuffer {
+  public:
+    using ValueType = _Tp;
+
+    void add(ValueType value) {
+      ringBuffer[newestIndex] = value;
+      newestIndex = (newestIndex + 1) % N;
+      length = min(length + 1, N);
+    }
+
+    int pop() { // pops the oldest value off the ring buffer
+      if (length == 0) {
+        return -1;
+      }
+      ValueType result = ringBuffer[(N + newestIndex - length) % N];
+      length -= 1;
+      return result;
+    }
+
+    void clear() {
+      newestIndex = 0;
+      length = 0;
+    }
+
+    int get(size_t index) { // this.get(0) is the oldest value, this.get(this.getLength() - 1) is the newest value
+      if (index < 0 || index >= length) {
+        return -1;
+      }
+      return ringBuffer[(N + newestIndex - length + index) % N];
+    }
+
+    size_t getLength() { return length; }
+
+  private:
+    ValueType ringBuffer[N];
+    size_t newestIndex = 0;
+    size_t length = 0;
+};
+
+class BLESerialCharacteristicCallbacks;
+class BLESerialServerCallbacks;
+
+class BLESerial : public Stream
+{
+    friend class BLESerialCharacteristicCallbacks;
+    friend class BLESerialServerCallbacks;
+
+  public:
+    inline static const char *SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E";
+    inline static const char *RX_UUID = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E";
+    inline static const char *TX_UUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E";
+
+    BLESerial() : m_receiveBuffer() {}
+
+    virtual int available() override
+    {
+        return m_receiveBuffer.getLength();
+    }
+
+    virtual int peek() override
+    {
+        return m_receiveBuffer.get(0);
+    }
+
+    virtual int read() override
+    {
+        return m_receiveBuffer.pop();
+    }
+
+    virtual size_t write(const uint8_t *buffer, size_t bufferSize) override
+    {
+        if (this->m_pTxCharacteristic == nullptr || !this->connected())
+        {
+            return 0;
+        }
+
+        this->m_pTxCharacteristic->setValue(const_cast<uint8_t *>(buffer), bufferSize);
+        this->flush();
+
+        return bufferSize;
+    }
+
+    virtual size_t write(uint8_t byte) override
+    {
+        if (this->m_pTxCharacteristic == nullptr || !this->connected())
+        {
+            return 0;
+        }
+
+        this->m_pTxCharacteristic->setValue(&byte, 1);
+        this->flush();
+
+        return 1;
+    }
+
+    virtual void flush(void) override
+    {
+        this->m_pTxCharacteristic->notify(true);
+    }
+
+    void begin(const char *deviceName, const char *serviceUuid = SERVICE_UUID, const char *rxUuid = RX_UUID, const char *txUuid = TX_UUID)
+    {
+        // Create the BLE Device
+        log_d("Creating BLE device with name '%s'", deviceName);
+        BLEDevice::init(deviceName);
+
+        BLEServer *pServer = BLEDevice::createServer();
+
+        this->begin(pServer, serviceUuid, rxUuid, txUuid);
+    }
+
+    void begin(BLEServer* pServer, const char *serviceUuid = SERVICE_UUID, const char *rxUuid = RX_UUID, const char *txUuid = TX_UUID)
+    {
+        log_d("Creating BLE service with UUID '%s'", serviceUuid);
+        BLEService *pService = pServer->getServiceByUUID(serviceUuid);
+        if (pService == nullptr) {
+            pService = pServer->createService(serviceUuid);
+        }
+
+        log_d("Creating BLE characteristics with UUIDs '%s' (RX) and '%s' (TX)", rxUuid, txUuid);
+        auto pRxCharacteristic = pService->createCharacteristic(rxUuid, BLECharacteristic::PROPERTY_WRITE);
+        auto pTxCharacteristic = pService->createCharacteristic(txUuid, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
+
+        this->begin(pServer, pRxCharacteristic, pTxCharacteristic);
+
+        pService->start();
+        log_d("Started BLE service");
+
+        BLEAdvertising *pAdvertising = pServer->getAdvertising();
+        pAdvertising->start();
+        log_d("Started BLE advertising");
+    }
+
+    void begin(BLEServer* pServer, BLECharacteristic *pRxCharacteristic, BLECharacteristic *pTxCharacteristic);
+
+    bool connected()
+    {
+        return m_pServer != nullptr && m_pServer->getConnectedCount() > 0;
+    }
+
+  private:
+    BLESerial(BLESerial const &other) = delete; // disable copy constructor
+    void operator=(BLESerial const &other) = delete; // disable assign constructor
+
+    ByteRingBuffer<uint8_t, BLESERIAL_RECEIVE_BUFFER_SIZE> m_receiveBuffer;
+
+    BLEServer *m_pServer;
+    BLECharacteristic *m_pRxCharacteristic;
+    BLECharacteristic *m_pTxCharacteristic;
+};
+
+class BLESerialServerCallbacks : public BLEServerCallbacks
+{
+    void onConnect(BLEServer *pServer) override
+    {
+
+    }
+
+    void onDisconnect(BLEServer *pServer) override
+    {
+        auto* pAdvertising = pServer->getAdvertising();
+        if (pAdvertising == nullptr)
+        {
+            return;
+        }
+        pAdvertising->start();
+    }
+};
+
+class BLESerialCharacteristicCallbacks : public BLECharacteristicCallbacks
+{
+  public:
+    BLESerialCharacteristicCallbacks(BLESerial *bleSerial) : bleSerial(bleSerial) {}
+
+    void onWrite(BLECharacteristic *pCharacteristic)
+    {
+        if (pCharacteristic != bleSerial->m_pRxCharacteristic)
+        {
+            return;
+        }
+
+        std::string rxValue = pCharacteristic->getValue();
+        for (int i = 0; i < rxValue.length(); i++)
+        {
+            bleSerial->m_receiveBuffer.add(rxValue[i]);
+        }
+    }
+
+  private:
+    BLESerial *bleSerial;
+};
+
+void BLESerial::begin(BLEServer* pServer, BLECharacteristic *pRxCharacteristic, BLECharacteristic *pTxCharacteristic)
+{
+    this->m_pServer = pServer;
+    this->m_pRxCharacteristic = pRxCharacteristic;
+    this->m_pTxCharacteristic = pTxCharacteristic;
+
+    // this->m_pRxCharacteristic->setAccessPermissions(ESP_GATT_PERM_WRITE_ENCRYPTED);
+    this->m_pRxCharacteristic->addDescriptor(new BLE2902());
+    this->m_pRxCharacteristic->setCallbacks(new BLESerialCharacteristicCallbacks(this));
+    this->m_pRxCharacteristic->setWriteProperty(true);
+
+    // this->m_pTxCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED);
+    this->m_pTxCharacteristic->addDescriptor(new BLE2902());
+    this->m_pTxCharacteristic->setReadProperty(true);
+    this->m_pTxCharacteristic->setNotifyProperty(true);
+}
+
+#endif // BLESERIAL_H
diff --git a/lib/ble_serial/library.json b/lib/ble_serial/library.json
new file mode 100644
index 00000000..e237d1ef
--- /dev/null
+++ b/lib/ble_serial/library.json
@@ -0,0 +1,7 @@
+{
+    "$schema": "https://raw.githubusercontent.com/platformio/platformio-core/develop/platformio/assets/schema/library.json",
+    "frameworks": "arduino",
+    "platforms": [
+        "espressif32"
+    ]
+}
diff --git a/lib/opengloves/og_constants.hpp b/lib/opengloves/og_constants.hpp
index 6d612517..df163940 100644
--- a/lib/opengloves/og_constants.hpp
+++ b/lib/opengloves/og_constants.hpp
@@ -4,3 +4,4 @@
 
 #define OPENGLOVES_COMM_SERIAL 0x1
 #define OPENGLOVES_COMM_BTSERIAL 0x2
+#define OPENGLOVES_COMM_BLESERIAL 0x3
diff --git a/lib/opengloves/senseshift/opengloves/autoconfig.hpp b/lib/opengloves/senseshift/opengloves/autoconfig.hpp
index ed585ef4..0fbe65fa 100644
--- a/lib/opengloves/senseshift/opengloves/autoconfig.hpp
+++ b/lib/opengloves/senseshift/opengloves/autoconfig.hpp
@@ -163,7 +163,7 @@ namespace SenseShift::OpenGloves::AutoConfig {
         auto* pSerial = &SERIAL_PORT;
         pSerial->begin(SERIAL_BAUDRATE);
         return new StreamTransport(pSerial);
-#elif OPENGLOVES_COMMUNCATION == OPENGLOVES_COMM_BTSERIAL // Bluetooth Serial
+#elif (OPENGLOVES_COMMUNCATION == OPENGLOVES_COMM_BTSERIAL) || (OPENGLOVES_COMMUNCATION == OPENGLOVES_COMM_BLESERIAL) // Bluetooth Serial
         std::string name;
 #ifdef BTSERIAL_NAME
         name = BTSERIAL_NAME;
@@ -174,9 +174,16 @@ namespace SenseShift::OpenGloves::AutoConfig {
 
         log_i("Generated Bluetooth name: %s", name.c_str());
 #endif
+
+#if OPENGLOVES_COMMUNCATION == OPENGLOVES_COMM_BTSERIAL // Bluetooth Classic
         BluetoothSerial* pBtSerial = new BluetoothSerial();
         pBtSerial->begin(name.c_str());
         return new BluetoothSerialTransport(*pBtSerial);
+#elif OPENGLOVES_COMMUNCATION == OPENGLOVES_COMM_BLESERIAL // Bluetooth Low Energy
+        BLESerial* pBleSerial = new BLESerial();
+        pBleSerial->begin(name.c_str());
+        return new BLESerialTransport(*pBleSerial);
+#endif
 #else // Fallback
 #error "Unsupported communication type"
         return nullptr;
diff --git a/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp b/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp
index f0cb609a..72c94048 100644
--- a/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp
+++ b/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp
@@ -1,6 +1,7 @@
 #pragma once
 
 #include <BluetoothSerial.h>
+#include <BLESerial.hpp>
 #include <HardwareSerial.h>
 #include <Print.h>
 #include <WiFi.h>
@@ -25,7 +26,7 @@ namespace SenseShift::OpenGloves {
                 return 0;
             }
 
-            const auto written = this->channel->write(buffer);
+            const auto written = this->channel->write(buffer, length);
             this->channel->flush();
 
             return written;
@@ -68,18 +69,31 @@ namespace SenseShift::OpenGloves {
       public:
         BluetoothSerialTransport(BluetoothSerial& channel) : IStreamTransport(&channel){};
 
-        void setup() override
+        bool isReady() override
         {
             auto* serial = static_cast<BluetoothSerial*>(this->channel);
-            if (serial->isReady()) {
-                return;
-            }
+            return serial->isReady() && serial->hasClient();
         }
 
+        virtual size_t send(const char* buffer, size_t length) override
+        {
+            auto written = this->channel->write(buffer, length);
+
+            // TODO: This is a hack to ensure the data is sent
+            delay(2);
+
+            return written;
+        }
+    };
+
+    class BLESerialTransport : public IStreamTransport {
+      public:
+        BLESerialTransport(BLESerial& channel) : IStreamTransport(&channel){};
+
         bool isReady() override
         {
-            auto* serial = static_cast<BluetoothSerial*>(this->channel);
-            return serial->isReady() && serial->hasClient();
+            auto* serial = static_cast<BLESerial*>(this->channel);
+            return serial->connected();
         }
     };
 
diff --git a/lib/opengloves_task/opengloves_task.hpp b/lib/opengloves_task/opengloves_task.hpp
index 87afffca..c45ea604 100644
--- a/lib/opengloves_task/opengloves_task.hpp
+++ b/lib/opengloves_task/opengloves_task.hpp
@@ -213,7 +213,7 @@ namespace OpenGloves {
                 // Delay until the next update.
                 auto elapsed = millis() - now;
 
-                log_d("Update took %d ms, theoretical max rate is %dHz (target is %dHz)", elapsed, 1000 / elapsed, 1000 / this->config.updateIntervalMs);
+                log_i("Update took %d ms, theoretical max rate is %dHz (target is %dHz)", elapsed, 1000 / elapsed, 1000 / this->config.updateIntervalMs);
 
                 if (elapsed < this->config.updateIntervalMs) {
                     delay(this->config.updateIntervalMs - elapsed);

From f8a9deb3ffb985082e4adcbdec547430b21cf763 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Mon, 4 Sep 2023 01:09:19 +0400
Subject: [PATCH 32/82] style(clang-format): fix formatting

---
 .../mode_configs/opengloves/opengloves.cpp    |  26 +--
 lib/ble_serial/BLESerial.hpp                  | 155 ++++++++----------
 lib/io/senseshift/input/sensor.hpp            |   7 +-
 .../senseshift/opengloves/autoconfig.hpp      |  33 ++--
 .../senseshift/opengloves/encoding/alpha.cpp  |   8 +-
 .../opengloves/transport/stream.hpp           |   2 +-
 lib/opengloves_task/opengloves_task.hpp       |   7 +-
 7 files changed, 116 insertions(+), 122 deletions(-)

diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp
index 76706b73..3ebfca56 100644
--- a/firmware/mode_configs/opengloves/opengloves.cpp
+++ b/firmware/mode_configs/opengloves/opengloves.cpp
@@ -208,19 +208,19 @@ void setupMode()
     );
     trackingTask->begin();
 
-// #if FFB_ENABLED
-//     ffbTask = new OpenGlovesForceFeedbackTask(
-//       *communication,
-//       handActuators,
-//       UPDATE_RATE,
-//       {
-//         .name = "OpenGlovesForceFeedbackTask",
-//         .stackDepth = 8192,
-//         .priority = OPENGLOVES_FINGERS_TASK_PRIORITY,
-//       }
-//     );
-//     ffbTask->begin();
-// #endif
+#if FFB_ENABLED
+    ffbTask = new OpenGlovesForceFeedbackTask(
+      *communication,
+      handActuators,
+      UPDATE_RATE,
+      {
+        .name = "OpenGlovesForceFeedbackTask",
+        .stackDepth = 8192,
+        .priority = OPENGLOVES_FINGERS_TASK_PRIORITY,
+      }
+    );
+    ffbTask->begin();
+#endif
 }
 
 void loopMode()
diff --git a/lib/ble_serial/BLESerial.hpp b/lib/ble_serial/BLESerial.hpp
index 89572837..781e3840 100644
--- a/lib/ble_serial/BLESerial.hpp
+++ b/lib/ble_serial/BLESerial.hpp
@@ -3,10 +3,10 @@
 #define BLESERIAL_H
 
 #include <Arduino.h>
+#include <BLE2902.h>
 #include <BLEDevice.h>
-#include <BLEUtils.h>
 #include <BLEServer.h>
-#include <BLE2902.h>
+#include <BLEUtils.h>
 
 #define BLESERIAL_ATTRIBUTE_MAX_VALUE_LENGTH 20
 #define BLESERIAL_RECEIVE_BUFFER_SIZE 256
@@ -16,31 +16,35 @@ class ByteRingBuffer {
   public:
     using ValueType = _Tp;
 
-    void add(ValueType value) {
-      ringBuffer[newestIndex] = value;
-      newestIndex = (newestIndex + 1) % N;
-      length = min(length + 1, N);
+    void add(ValueType value)
+    {
+        ringBuffer[newestIndex] = value;
+        newestIndex = (newestIndex + 1) % N;
+        length = min(length + 1, N);
     }
 
-    int pop() { // pops the oldest value off the ring buffer
-      if (length == 0) {
-        return -1;
-      }
-      ValueType result = ringBuffer[(N + newestIndex - length) % N];
-      length -= 1;
-      return result;
+    int pop()
+    { // pops the oldest value off the ring buffer
+        if (length == 0) {
+            return -1;
+        }
+        ValueType result = ringBuffer[(N + newestIndex - length) % N];
+        length -= 1;
+        return result;
     }
 
-    void clear() {
-      newestIndex = 0;
-      length = 0;
+    void clear()
+    {
+        newestIndex = 0;
+        length = 0;
     }
 
-    int get(size_t index) { // this.get(0) is the oldest value, this.get(this.getLength() - 1) is the newest value
-      if (index < 0 || index >= length) {
-        return -1;
-      }
-      return ringBuffer[(N + newestIndex - length + index) % N];
+    int get(size_t index)
+    { // this.get(0) is the oldest value, this.get(this.getLength() - 1) is the newest value
+        if (index < 0 || index >= length) {
+            return -1;
+        }
+        return ringBuffer[(N + newestIndex - length + index) % N];
     }
 
     size_t getLength() { return length; }
@@ -54,41 +58,30 @@ class ByteRingBuffer {
 class BLESerialCharacteristicCallbacks;
 class BLESerialServerCallbacks;
 
-class BLESerial : public Stream
-{
+class BLESerial : public Stream {
     friend class BLESerialCharacteristicCallbacks;
     friend class BLESerialServerCallbacks;
 
   public:
-    inline static const char *SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E";
-    inline static const char *RX_UUID = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E";
-    inline static const char *TX_UUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E";
+    inline static const char* SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E";
+    inline static const char* RX_UUID = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E";
+    inline static const char* TX_UUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E";
 
     BLESerial() : m_receiveBuffer() {}
 
-    virtual int available() override
-    {
-        return m_receiveBuffer.getLength();
-    }
+    virtual int available() override { return m_receiveBuffer.getLength(); }
 
-    virtual int peek() override
-    {
-        return m_receiveBuffer.get(0);
-    }
+    virtual int peek() override { return m_receiveBuffer.get(0); }
 
-    virtual int read() override
-    {
-        return m_receiveBuffer.pop();
-    }
+    virtual int read() override { return m_receiveBuffer.pop(); }
 
-    virtual size_t write(const uint8_t *buffer, size_t bufferSize) override
+    virtual size_t write(const uint8_t* buffer, size_t bufferSize) override
     {
-        if (this->m_pTxCharacteristic == nullptr || !this->connected())
-        {
+        if (this->m_pTxCharacteristic == nullptr || !this->connected()) {
             return 0;
         }
 
-        this->m_pTxCharacteristic->setValue(const_cast<uint8_t *>(buffer), bufferSize);
+        this->m_pTxCharacteristic->setValue(const_cast<uint8_t*>(buffer), bufferSize);
         this->flush();
 
         return bufferSize;
@@ -96,8 +89,7 @@ class BLESerial : public Stream
 
     virtual size_t write(uint8_t byte) override
     {
-        if (this->m_pTxCharacteristic == nullptr || !this->connected())
-        {
+        if (this->m_pTxCharacteristic == nullptr || !this->connected()) {
             return 0;
         }
 
@@ -107,104 +99,101 @@ class BLESerial : public Stream
         return 1;
     }
 
-    virtual void flush(void) override
-    {
-        this->m_pTxCharacteristic->notify(true);
-    }
+    virtual void flush(void) override { this->m_pTxCharacteristic->notify(true); }
 
-    void begin(const char *deviceName, const char *serviceUuid = SERVICE_UUID, const char *rxUuid = RX_UUID, const char *txUuid = TX_UUID)
+    void begin(
+      const char* deviceName,
+      const char* serviceUuid = SERVICE_UUID,
+      const char* rxUuid = RX_UUID,
+      const char* txUuid = TX_UUID
+    )
     {
         // Create the BLE Device
         log_d("Creating BLE device with name '%s'", deviceName);
         BLEDevice::init(deviceName);
 
-        BLEServer *pServer = BLEDevice::createServer();
+        BLEServer* pServer = BLEDevice::createServer();
 
         this->begin(pServer, serviceUuid, rxUuid, txUuid);
     }
 
-    void begin(BLEServer* pServer, const char *serviceUuid = SERVICE_UUID, const char *rxUuid = RX_UUID, const char *txUuid = TX_UUID)
+    void begin(
+      BLEServer* pServer,
+      const char* serviceUuid = SERVICE_UUID,
+      const char* rxUuid = RX_UUID,
+      const char* txUuid = TX_UUID
+    )
     {
         log_d("Creating BLE service with UUID '%s'", serviceUuid);
-        BLEService *pService = pServer->getServiceByUUID(serviceUuid);
+        BLEService* pService = pServer->getServiceByUUID(serviceUuid);
         if (pService == nullptr) {
             pService = pServer->createService(serviceUuid);
         }
 
         log_d("Creating BLE characteristics with UUIDs '%s' (RX) and '%s' (TX)", rxUuid, txUuid);
         auto pRxCharacteristic = pService->createCharacteristic(rxUuid, BLECharacteristic::PROPERTY_WRITE);
-        auto pTxCharacteristic = pService->createCharacteristic(txUuid, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
+        auto pTxCharacteristic =
+          pService->createCharacteristic(txUuid, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
 
         this->begin(pServer, pRxCharacteristic, pTxCharacteristic);
 
         pService->start();
         log_d("Started BLE service");
 
-        BLEAdvertising *pAdvertising = pServer->getAdvertising();
+        BLEAdvertising* pAdvertising = pServer->getAdvertising();
         pAdvertising->start();
         log_d("Started BLE advertising");
     }
 
-    void begin(BLEServer* pServer, BLECharacteristic *pRxCharacteristic, BLECharacteristic *pTxCharacteristic);
+    void begin(BLEServer* pServer, BLECharacteristic* pRxCharacteristic, BLECharacteristic* pTxCharacteristic);
 
-    bool connected()
-    {
-        return m_pServer != nullptr && m_pServer->getConnectedCount() > 0;
-    }
+    bool connected() { return m_pServer != nullptr && m_pServer->getConnectedCount() > 0; }
 
   private:
-    BLESerial(BLESerial const &other) = delete; // disable copy constructor
-    void operator=(BLESerial const &other) = delete; // disable assign constructor
+    BLESerial(BLESerial const& other) = delete;      // disable copy constructor
+    void operator=(BLESerial const& other) = delete; // disable assign constructor
 
     ByteRingBuffer<uint8_t, BLESERIAL_RECEIVE_BUFFER_SIZE> m_receiveBuffer;
 
-    BLEServer *m_pServer;
-    BLECharacteristic *m_pRxCharacteristic;
-    BLECharacteristic *m_pTxCharacteristic;
+    BLEServer* m_pServer;
+    BLECharacteristic* m_pRxCharacteristic;
+    BLECharacteristic* m_pTxCharacteristic;
 };
 
-class BLESerialServerCallbacks : public BLEServerCallbacks
-{
-    void onConnect(BLEServer *pServer) override
-    {
-
-    }
+class BLESerialServerCallbacks : public BLEServerCallbacks {
+    void onConnect(BLEServer* pServer) override {}
 
-    void onDisconnect(BLEServer *pServer) override
+    void onDisconnect(BLEServer* pServer) override
     {
         auto* pAdvertising = pServer->getAdvertising();
-        if (pAdvertising == nullptr)
-        {
+        if (pAdvertising == nullptr) {
             return;
         }
         pAdvertising->start();
     }
 };
 
-class BLESerialCharacteristicCallbacks : public BLECharacteristicCallbacks
-{
+class BLESerialCharacteristicCallbacks : public BLECharacteristicCallbacks {
   public:
-    BLESerialCharacteristicCallbacks(BLESerial *bleSerial) : bleSerial(bleSerial) {}
+    BLESerialCharacteristicCallbacks(BLESerial* bleSerial) : bleSerial(bleSerial) {}
 
-    void onWrite(BLECharacteristic *pCharacteristic)
+    void onWrite(BLECharacteristic* pCharacteristic)
     {
-        if (pCharacteristic != bleSerial->m_pRxCharacteristic)
-        {
+        if (pCharacteristic != bleSerial->m_pRxCharacteristic) {
             return;
         }
 
         std::string rxValue = pCharacteristic->getValue();
-        for (int i = 0; i < rxValue.length(); i++)
-        {
+        for (int i = 0; i < rxValue.length(); i++) {
             bleSerial->m_receiveBuffer.add(rxValue[i]);
         }
     }
 
   private:
-    BLESerial *bleSerial;
+    BLESerial* bleSerial;
 };
 
-void BLESerial::begin(BLEServer* pServer, BLECharacteristic *pRxCharacteristic, BLECharacteristic *pTxCharacteristic)
+void BLESerial::begin(BLEServer* pServer, BLECharacteristic* pRxCharacteristic, BLECharacteristic* pTxCharacteristic)
 {
     this->m_pServer = pServer;
     this->m_pRxCharacteristic = pRxCharacteristic;
diff --git a/lib/io/senseshift/input/sensor.hpp b/lib/io/senseshift/input/sensor.hpp
index c3ce962c..d1fe8b51 100644
--- a/lib/io/senseshift/input/sensor.hpp
+++ b/lib/io/senseshift/input/sensor.hpp
@@ -109,13 +109,12 @@ namespace SenseShift::Input {
         }
     };
 
-    template <typename _Tp>
-    class AverageSensor : public ISimpleSensor<_Tp>
-    {
+    template<typename _Tp>
+    class AverageSensor : public ISimpleSensor<_Tp> {
         static_assert(std::is_arithmetic<_Tp>::value, "AverageSensor only supports arithmetic types");
 
       public:
-        AverageSensor(ISimpleSensor<_Tp>* sensor, size_t samples) : sensor(sensor), samples(samples){ }
+        AverageSensor(ISimpleSensor<_Tp>* sensor, size_t samples) : sensor(sensor), samples(samples) {}
 
         void init() override { this->sensor->init(); };
 
diff --git a/lib/opengloves/senseshift/opengloves/autoconfig.hpp b/lib/opengloves/senseshift/opengloves/autoconfig.hpp
index 0fbe65fa..cebdefe2 100644
--- a/lib/opengloves/senseshift/opengloves/autoconfig.hpp
+++ b/lib/opengloves/senseshift/opengloves/autoconfig.hpp
@@ -75,10 +75,10 @@
         new curl_calib()                                                                                  \
       ),                                                                                                  \
       new ::SenseShift::Input::CalibratedSimpleSensor<uint16_t>(                                          \
-        new ::SenseShift::Input::AverageSensor<uint16_t>( \
-          new ::SenseShift::Arduino::Input::AnalogSensor<splay_invert>(splay_pin), \
-          5 \
-        ),                          \
+        new ::SenseShift::Input::AverageSensor<uint16_t>(                                                 \
+          new ::SenseShift::Arduino::Input::AnalogSensor<splay_invert>(splay_pin),                        \
+          5                                                                                               \
+        ),                                                                                                \
         new splay_calib()                                                                                 \
       ),                                                                                                  \
       type                                                                                                \
@@ -91,16 +91,16 @@
 #define JOYSTICK_ENABLED \
     (defined(PIN_JOYSTICK_X) && defined(PIN_JOYSTICK_Y) && (PIN_JOYSTICK_X != -1) && (PIN_JOYSTICK_Y != -1))
 
-#define JOYSTICK_CLASS(type, pin, invert, deadzone)                  \
-    StringEncodedMemoizedSensor<uint16_t>(                           \
-      new ::SenseShift::Input::JoystickAxisSensor<uint16_t>(         \
-        new ::SenseShift::Input::AverageSensor<uint16_t>(                \
-            new ::SenseShift::Arduino::Input::AnalogSensor<invert>(pin), \
-            5                                                            \
-        ),                                                               \
-        deadzone                                                     \
-      ),                                                             \
-      type                                                           \
+#define JOYSTICK_CLASS(type, pin, invert, deadzone)                    \
+    StringEncodedMemoizedSensor<uint16_t>(                             \
+      new ::SenseShift::Input::JoystickAxisSensor<uint16_t>(           \
+        new ::SenseShift::Input::AverageSensor<uint16_t>(              \
+          new ::SenseShift::Arduino::Input::AnalogSensor<invert>(pin), \
+          5                                                            \
+        ),                                                             \
+        deadzone                                                       \
+      ),                                                               \
+      type                                                             \
     )
 
 #pragma endregion
@@ -163,7 +163,8 @@ namespace SenseShift::OpenGloves::AutoConfig {
         auto* pSerial = &SERIAL_PORT;
         pSerial->begin(SERIAL_BAUDRATE);
         return new StreamTransport(pSerial);
-#elif (OPENGLOVES_COMMUNCATION == OPENGLOVES_COMM_BTSERIAL) || (OPENGLOVES_COMMUNCATION == OPENGLOVES_COMM_BLESERIAL) // Bluetooth Serial
+#elif (OPENGLOVES_COMMUNCATION == OPENGLOVES_COMM_BTSERIAL) \
+  || (OPENGLOVES_COMMUNCATION == OPENGLOVES_COMM_BLESERIAL) // Bluetooth Serial
         std::string name;
 #ifdef BTSERIAL_NAME
         name = BTSERIAL_NAME;
@@ -175,7 +176,7 @@ namespace SenseShift::OpenGloves::AutoConfig {
         log_i("Generated Bluetooth name: %s", name.c_str());
 #endif
 
-#if OPENGLOVES_COMMUNCATION == OPENGLOVES_COMM_BTSERIAL // Bluetooth Classic
+#if OPENGLOVES_COMMUNCATION == OPENGLOVES_COMM_BTSERIAL    // Bluetooth Classic
         BluetoothSerial* pBtSerial = new BluetoothSerial();
         pBtSerial->begin(name.c_str());
         return new BluetoothSerialTransport(*pBtSerial);
diff --git a/lib/opengloves/senseshift/opengloves/encoding/alpha.cpp b/lib/opengloves/senseshift/opengloves/encoding/alpha.cpp
index 702a157c..ad1f4a1e 100644
--- a/lib/opengloves/senseshift/opengloves/encoding/alpha.cpp
+++ b/lib/opengloves/senseshift/opengloves/encoding/alpha.cpp
@@ -3,8 +3,8 @@
 #include <string.h>
 
 namespace SenseShift::OpenGloves {
-    const std::map<AlphaEncodingService::Command, uint16_t> AlphaEncodingService::deserialize(const std::string& input_string
-    ) const
+    const std::map<AlphaEncodingService::Command, uint16_t>
+      AlphaEncodingService::deserialize(const std::string& input_string) const
     {
         std::map<Command, uint16_t> commands;
 
@@ -58,8 +58,8 @@ namespace SenseShift::OpenGloves {
         commands[command] = number;
     }
 
-    const std::string AlphaEncodingService::serialize(const std::vector<::OpenGloves::IStringEncodedMemoizedSensor*>& sensors
-    )
+    const std::string
+      AlphaEncodingService::serialize(const std::vector<::OpenGloves::IStringEncodedMemoizedSensor*>& sensors)
     {
         memset(this->writeBuffer, 0, 256);
         this->writeBuffer[0] = '\0';
diff --git a/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp b/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp
index 72c94048..f249f954 100644
--- a/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp
+++ b/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp
@@ -1,7 +1,7 @@
 #pragma once
 
-#include <BluetoothSerial.h>
 #include <BLESerial.hpp>
+#include <BluetoothSerial.h>
 #include <HardwareSerial.h>
 #include <Print.h>
 #include <WiFi.h>
diff --git a/lib/opengloves_task/opengloves_task.hpp b/lib/opengloves_task/opengloves_task.hpp
index c45ea604..80f2cd9e 100644
--- a/lib/opengloves_task/opengloves_task.hpp
+++ b/lib/opengloves_task/opengloves_task.hpp
@@ -213,7 +213,12 @@ namespace OpenGloves {
                 // Delay until the next update.
                 auto elapsed = millis() - now;
 
-                log_i("Update took %d ms, theoretical max rate is %dHz (target is %dHz)", elapsed, 1000 / elapsed, 1000 / this->config.updateIntervalMs);
+                log_i(
+                  "Update took %d ms, theoretical max rate is %dHz (target is %dHz)",
+                  elapsed,
+                  1000 / elapsed,
+                  1000 / this->config.updateIntervalMs
+                );
 
                 if (elapsed < this->config.updateIntervalMs) {
                     delay(this->config.updateIntervalMs - elapsed);

From b3ef92ad7c2e0e42b4dd4bf40d46167a4592705a Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Mon, 4 Sep 2023 01:09:50 +0400
Subject: [PATCH 33/82] ci(GitHub): cover BLE transport

---
 .github/workflows/ci.yml                | 22 +++++++++++++++++++---
 ini/opengloves.ini                      |  2 +-
 lib/opengloves_task/opengloves_task.hpp |  2 +-
 platformio.ini                          |  2 ++
 4 files changed, 23 insertions(+), 5 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1119c892..fbba23fe 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -16,7 +16,7 @@ on:
 
 jobs:
   build-bhaptics:
-    name: Build ${{ matrix.target }} on ${{ matrix.os }} ${{ matrix.coverage && 'with coverage' || 'without coverage' }}, -D ${{ matrix.battery_flag }} -D ${{ matrix.serial_plotter_flag }} -D ${{ matrix.nimble_flag }}
+    name: Build ${{ matrix.target }} ${{ matrix.coverage && 'with coverage' || 'without coverage' }}, -D ${{ matrix.battery_flag }} -D ${{ matrix.serial_plotter_flag }} -D ${{ matrix.nimble_flag }}
     runs-on: ${{ matrix.os }}
     strategy:
       fail-fast: false
@@ -135,7 +135,8 @@ jobs:
       - name: Change memory segments
         if: matrix.coverage
         run: |
-          sed -i "s/len\s=\s0x2c200\s-\s0xdb5c/len = 289888/" ~/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/ld/memory.ld
+          sed -i "s/len\s=\s0x2c200\s-\s0xdb5c/len = 2898880/" ~/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/ld/memory.ld
+          sed -i "/\[env\]/p; s/\[env\]/board_build.partitions = huge_app.csv/" platformio.ini
 
       - name: Build
         run: |
@@ -159,6 +160,7 @@ jobs:
           retention-days: 1
 
   build-opengloves:
+    name: Build ${{ matrix.target }} ${{ matrix.coverage && 'with coverage' || 'without coverage' }}, -D ${{ matrix.comm_flag }} -D ${{ matrix.curl_calibration_flag }}
     runs-on: ${{ matrix.os }}
     strategy:
       fail-fast: false
@@ -193,6 +195,10 @@ jobs:
             target: indexer-csf
             comm_flag: COMMUNICATION_PROTOCOL=OPENGLOVES_COMM_BTSERIAL
             coverage: true
+          - os: ubuntu-latest
+            target: indexer-csf
+            comm_flag: COMMUNICATION_PROTOCOL=OPENGLOVES_COMM_BLESERIAL
+            coverage: true
 
     steps:
       - uses: actions/checkout@v3
@@ -269,14 +275,23 @@ jobs:
       - name: Change memory segments
         if: matrix.coverage
         run: |
-          sed -i "s/len\s=\s0x2c200\s-\s0xdb5c/len = 289888/" ~/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/ld/memory.ld
+          sed -i "s/len\s=\s0x2c200\s-\s0xdb5c/len = 2898880/" ~/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/ld/memory.ld
+          sed -i "/\[env\]/p; s/\[env\]/board_build.partitions = huge_app.csv/" platformio.ini
 
       - name: Build
+        if: matrix.coverage == false
         run: |
           echo "::group::platformio.ini"
           cat platformio.ini
           echo "::endgroup::"
           pio run --environment ${{matrix.target}}
+      - name: Build (debug)
+        if: matrix.coverage
+        run: |
+          echo "::group::platformio.ini"
+          cat platformio.ini
+          echo "::endgroup::"
+          pio debug --environment ${{matrix.target}}
 
       - name: Upload firmware Artifact
         if: matrix.coverage == false
@@ -420,6 +435,7 @@ jobs:
 
   wokwi:
     needs:
+      - build-bhaptics
       - build-opengloves
       - test
     runs-on: ubuntu-latest
diff --git a/ini/opengloves.ini b/ini/opengloves.ini
index 0160ae8d..15e91d29 100644
--- a/ini/opengloves.ini
+++ b/ini/opengloves.ini
@@ -11,7 +11,7 @@ build_flags 		= ${common.build_flags}
 	-D OPENGLOVES
 
 	; Communication
-	; -D OPENGLOVES_COMMUNCATION=OPENGLOVES_COMM_SERIAL ; OPENGLOVES_COMM_SERIAL, OPENGLOVES_COMM_BTSERIAL, OPENGLOVES_COMM_BLESERIAL
+	-D OPENGLOVES_COMMUNCATION=OPENGLOVES_COMM_BLESERIAL ; OPENGLOVES_COMM_SERIAL, OPENGLOVES_COMM_BTSERIAL, OPENGLOVES_COMM_BLESERIAL
 	; Serial
 	-D SERIAL_BAUDRATE=115200
 	-D SERIAL_PORT=Serial ; Serial, Serial1, Serial2, Serial3
diff --git a/lib/opengloves_task/opengloves_task.hpp b/lib/opengloves_task/opengloves_task.hpp
index 80f2cd9e..a04c2f11 100644
--- a/lib/opengloves_task/opengloves_task.hpp
+++ b/lib/opengloves_task/opengloves_task.hpp
@@ -213,7 +213,7 @@ namespace OpenGloves {
                 // Delay until the next update.
                 auto elapsed = millis() - now;
 
-                log_i(
+                log_d(
                   "Update took %d ms, theoretical max rate is %dHz (target is %dHz)",
                   elapsed,
                   1000 / elapsed,
diff --git a/platformio.ini b/platformio.ini
index d2a48669..6787808b 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -46,6 +46,8 @@ check_tool  = clangtidy
 check_flags =
 	clangtidy: --config-file=./.clang-tidy --fix
 
+debug_build_flags = -Os
+
 [env:native]
 platform 		 = native
 

From 1b91c221309e83a9f7b94967781772c5277af34a Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Mon, 4 Sep 2023 09:22:16 +0400
Subject: [PATCH 34/82] feat(IO): add Median Filter sensor

---
 lib/io/senseshift/input/sensor.hpp            | 26 ++++++++++
 .../senseshift/opengloves/autoconfig.hpp      | 48 +++++++++----------
 test/test_io_sensor/main.cpp                  | 44 +++++++++++++++--
 3 files changed, 89 insertions(+), 29 deletions(-)

diff --git a/lib/io/senseshift/input/sensor.hpp b/lib/io/senseshift/input/sensor.hpp
index d1fe8b51..2520cd76 100644
--- a/lib/io/senseshift/input/sensor.hpp
+++ b/lib/io/senseshift/input/sensor.hpp
@@ -133,4 +133,30 @@ namespace SenseShift::Input {
         ISimpleSensor<_Tp>* sensor;
         size_t samples;
     };
+
+    template<typename _Tp, size_t _Samples>
+    class StaticMedianSensor : public ISimpleSensor<_Tp> {
+        static_assert(std::is_arithmetic<_Tp>::value, "StaticMedianSensor only supports arithmetic types");
+        static_assert(_Samples % 2 == 1, "StaticMedianSensor only supports odd sample sizes");
+
+      public:
+        StaticMedianSensor(ISimpleSensor<_Tp>* sensor) : sensor(sensor) {}
+
+        void init() override { this->sensor->init(); };
+
+        _Tp getValue() override
+        {
+            for (size_t i = 0; i < _Samples; i++) {
+                this->values[i] = this->sensor->getValue();
+            }
+
+            std::sort(this->values, this->values + _Samples);
+
+            return this->values[_Samples / 2];
+        }
+
+      private:
+        _Tp values[_Samples];
+        ISimpleSensor<_Tp>* sensor;
+    };
 } // namespace SenseShift::Input
diff --git a/lib/opengloves/senseshift/opengloves/autoconfig.hpp b/lib/opengloves/senseshift/opengloves/autoconfig.hpp
index cebdefe2..191a8c06 100644
--- a/lib/opengloves/senseshift/opengloves/autoconfig.hpp
+++ b/lib/opengloves/senseshift/opengloves/autoconfig.hpp
@@ -48,16 +48,15 @@
 #define FINGER_MIDDLE_ENABLED (defined(PIN_FINGER_MIDDLE) && (PIN_FINGER_MIDDLE != -1))
 #define FINGER_RING_ENABLED (defined(PIN_FINGER_RING) && (PIN_FINGER_RING != -1))
 #define FINGER_PINKY_ENABLED (defined(PIN_FINGER_PINKY) && (PIN_FINGER_PINKY != -1))
-#define FINGER_CLASS(type, curl_pin, curl_invert, curl_calib)                    \
-    FingerSensor(                                                                \
-      new ::SenseShift::Input::CalibratedSimpleSensor<uint16_t>(                 \
-        new ::SenseShift::Input::AverageSensor<uint16_t>(                        \
-          new ::SenseShift::Arduino::Input::AnalogSensor<curl_invert>(curl_pin), \
-          5                                                                      \
-        ),                                                                       \
-        new curl_calib()                                                         \
-      ),                                                                         \
-      type                                                                       \
+#define FINGER_CLASS(type, curl_pin, curl_invert, curl_calib)                   \
+    FingerSensor(                                                               \
+      new ::SenseShift::Input::CalibratedSimpleSensor<uint16_t>(                \
+        new ::SenseShift::Input::StaticMedianSensor<uint16_t, 5>(               \
+          new ::SenseShift::Arduino::Input::AnalogSensor<curl_invert>(curl_pin) \
+        ),                                                                      \
+        new curl_calib()                                                        \
+      ),                                                                        \
+      type                                                                      \
     )
 #define FINGER_THUMB_SPLAY (FINGER_THUMB_ENABLED && defined(PIN_FINGER_THUMB_SPLAY) && (PIN_FINGER_THUMB_SPLAY != -1))
 #define FINGER_INDEX_SPLAY (FINGER_INDEX_ENABLED && defined(PIN_FINGER_INDEX_SPLAY) && (PIN_FINGER_INDEX_SPLAY != -1))
@@ -68,16 +67,14 @@
 #define FINGER_SPLAY_CLASS(type, curl_pin, curl_invert, curl_calib, splay_pin, splay_invert, splay_calib) \
     FingerSensor(                                                                                         \
       new ::SenseShift::Input::CalibratedSimpleSensor<uint16_t>(                                          \
-        new ::SenseShift::Input::AverageSensor<uint16_t>(                                                 \
-          new ::SenseShift::Arduino::Input::AnalogSensor<curl_invert>(curl_pin),                          \
-          5                                                                                               \
+        new ::SenseShift::Input::StaticMedianSensor<uint16_t, 5>(                                         \
+          new ::SenseShift::Arduino::Input::AnalogSensor<curl_invert>(curl_pin)                           \
         ),                                                                                                \
         new curl_calib()                                                                                  \
       ),                                                                                                  \
       new ::SenseShift::Input::CalibratedSimpleSensor<uint16_t>(                                          \
-        new ::SenseShift::Input::AverageSensor<uint16_t>(                                                 \
-          new ::SenseShift::Arduino::Input::AnalogSensor<splay_invert>(splay_pin),                        \
-          5                                                                                               \
+        new ::SenseShift::Input::StaticMedianSensor<uint16_t, 5>(                                         \
+          new ::SenseShift::Arduino::Input::AnalogSensor<splay_invert>(splay_pin)                         \
         ),                                                                                                \
         new splay_calib()                                                                                 \
       ),                                                                                                  \
@@ -91,16 +88,15 @@
 #define JOYSTICK_ENABLED \
     (defined(PIN_JOYSTICK_X) && defined(PIN_JOYSTICK_Y) && (PIN_JOYSTICK_X != -1) && (PIN_JOYSTICK_Y != -1))
 
-#define JOYSTICK_CLASS(type, pin, invert, deadzone)                    \
-    StringEncodedMemoizedSensor<uint16_t>(                             \
-      new ::SenseShift::Input::JoystickAxisSensor<uint16_t>(           \
-        new ::SenseShift::Input::AverageSensor<uint16_t>(              \
-          new ::SenseShift::Arduino::Input::AnalogSensor<invert>(pin), \
-          5                                                            \
-        ),                                                             \
-        deadzone                                                       \
-      ),                                                               \
-      type                                                             \
+#define JOYSTICK_CLASS(type, pin, invert, deadzone)                   \
+    StringEncodedMemoizedSensor<uint16_t>(                            \
+      new ::SenseShift::Input::JoystickAxisSensor<uint16_t>(          \
+        new ::SenseShift::Input::StaticMedianSensor<uint16_t, 5>(     \
+          new ::SenseShift::Arduino::Input::AnalogSensor<invert>(pin) \
+        ),                                                            \
+        deadzone                                                      \
+      ),                                                              \
+      type                                                            \
     )
 
 #pragma endregion
diff --git a/test/test_io_sensor/main.cpp b/test/test_io_sensor/main.cpp
index c0b75acb..74b4ace3 100644
--- a/test/test_io_sensor/main.cpp
+++ b/test/test_io_sensor/main.cpp
@@ -5,10 +5,8 @@ using namespace SenseShift::Input;
 using namespace SenseShift::Calibration;
 
 class TestAnalogSensor : public ISimpleSensor<int> {
-  private:
-    int count = 0;
-
   public:
+    int count = 0;
     int setupCounter = 0;
 
     void init() override { this->setupCounter++; };
@@ -72,12 +70,52 @@ void test_calibrated_sensor(void)
     TEST_ASSERT_EQUAL_INT(1, calibrator->resetCounter);
 }
 
+void test_average_sensor(void)
+{
+    auto inner = new TestAnalogSensor();
+    auto sensor = new AverageSensor<int>(inner, 3);
+
+    TEST_ASSERT_EQUAL_INT(0, inner->setupCounter);
+    sensor->init();
+    TEST_ASSERT_EQUAL_INT(1, inner->setupCounter);
+
+    // TODO: mock inner sensor, to return more interesting values
+    TEST_ASSERT_EQUAL_INT(2, sensor->getValue());  // (1 + 2 + 3) / 3 = 2
+    TEST_ASSERT_EQUAL_INT(5, sensor->getValue());  // (4 + 5 + 6) / 3 = 5
+    TEST_ASSERT_EQUAL_INT(8, sensor->getValue());  // (7 + 8 + 9) / 3 = 8
+    TEST_ASSERT_EQUAL_INT(11, sensor->getValue()); // (10 + 11 + 12) / 3 = 11
+
+    inner->count = 0;
+    sensor = new AverageSensor<int>(inner, 10);
+
+    TEST_ASSERT_EQUAL_INT(5, sensor->getValue()); // (1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10) / 10 = 5
+}
+
+void test_static_median_sensor(void)
+{
+    auto inner = new TestAnalogSensor();
+    auto sensor = new StaticMedianSensor<int, 3>(inner);
+
+    TEST_ASSERT_EQUAL_INT(0, inner->setupCounter);
+    sensor->init();
+    TEST_ASSERT_EQUAL_INT(1, inner->setupCounter);
+
+    // lmao, literally the same as average sensor
+    // TODO: mock inner sensor, to return more interesting values
+    TEST_ASSERT_EQUAL_INT(2, sensor->getValue());  // (1, 2, 3) = 2
+    TEST_ASSERT_EQUAL_INT(5, sensor->getValue());  // (4, 5, 6) = 5
+    TEST_ASSERT_EQUAL_INT(8, sensor->getValue());  // (7, 8, 9) = 8
+    TEST_ASSERT_EQUAL_INT(11, sensor->getValue()); // (10, 11, 12) = 11
+}
+
 int process(void)
 {
     UNITY_BEGIN();
 
     RUN_TEST(test_memoized_sensor);
     RUN_TEST(test_calibrated_sensor);
+    RUN_TEST(test_average_sensor);
+    RUN_TEST(test_static_median_sensor);
 
     return UNITY_END();
 }

From 3161613f18985f04751fb0bfb4a36367797b6915 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Mon, 4 Sep 2023 19:54:36 +0400
Subject: [PATCH 35/82] refactor: decouple buffer from BLE Serial

---
 ini/opengloves.ini             |   2 +-
 lib/ble_serial/BLESerial.hpp   |  66 ++++---------
 lib/util/senseshift/buffer.hpp | 123 ++++++++++++++++++++++++
 test/test_util_buffer/main.cpp | 169 +++++++++++++++++++++++++++++++++
 4 files changed, 313 insertions(+), 47 deletions(-)
 create mode 100644 lib/util/senseshift/buffer.hpp
 create mode 100644 test/test_util_buffer/main.cpp

diff --git a/ini/opengloves.ini b/ini/opengloves.ini
index 15e91d29..0160ae8d 100644
--- a/ini/opengloves.ini
+++ b/ini/opengloves.ini
@@ -11,7 +11,7 @@ build_flags 		= ${common.build_flags}
 	-D OPENGLOVES
 
 	; Communication
-	-D OPENGLOVES_COMMUNCATION=OPENGLOVES_COMM_BLESERIAL ; OPENGLOVES_COMM_SERIAL, OPENGLOVES_COMM_BTSERIAL, OPENGLOVES_COMM_BLESERIAL
+	; -D OPENGLOVES_COMMUNCATION=OPENGLOVES_COMM_SERIAL ; OPENGLOVES_COMM_SERIAL, OPENGLOVES_COMM_BTSERIAL, OPENGLOVES_COMM_BLESERIAL
 	; Serial
 	-D SERIAL_BAUDRATE=115200
 	-D SERIAL_PORT=Serial ; Serial, Serial1, Serial2, Serial3
diff --git a/lib/ble_serial/BLESerial.hpp b/lib/ble_serial/BLESerial.hpp
index 781e3840..8e4cd4bc 100644
--- a/lib/ble_serial/BLESerial.hpp
+++ b/lib/ble_serial/BLESerial.hpp
@@ -8,52 +8,12 @@
 #include <BLEServer.h>
 #include <BLEUtils.h>
 
-#define BLESERIAL_ATTRIBUTE_MAX_VALUE_LENGTH 20
-#define BLESERIAL_RECEIVE_BUFFER_SIZE 256
-
-template<typename _Tp, size_t N>
-class ByteRingBuffer {
-  public:
-    using ValueType = _Tp;
-
-    void add(ValueType value)
-    {
-        ringBuffer[newestIndex] = value;
-        newestIndex = (newestIndex + 1) % N;
-        length = min(length + 1, N);
-    }
-
-    int pop()
-    { // pops the oldest value off the ring buffer
-        if (length == 0) {
-            return -1;
-        }
-        ValueType result = ringBuffer[(N + newestIndex - length) % N];
-        length -= 1;
-        return result;
-    }
-
-    void clear()
-    {
-        newestIndex = 0;
-        length = 0;
-    }
-
-    int get(size_t index)
-    { // this.get(0) is the oldest value, this.get(this.getLength() - 1) is the newest value
-        if (index < 0 || index >= length) {
-            return -1;
-        }
-        return ringBuffer[(N + newestIndex - length + index) % N];
-    }
+#include <algorithm>
 
-    size_t getLength() { return length; }
+#include <senseshift/buffer.hpp>
 
-  private:
-    ValueType ringBuffer[N];
-    size_t newestIndex = 0;
-    size_t length = 0;
-};
+#define BLESERIAL_ATTRIBUTE_MAX_VALUE_LENGTH 20
+#define BLESERIAL_RECEIVE_BUFFER_SIZE 256
 
 class BLESerialCharacteristicCallbacks;
 class BLESerialServerCallbacks;
@@ -80,6 +40,20 @@ class BLESerial : public Stream {
         if (this->m_pTxCharacteristic == nullptr || !this->connected()) {
             return 0;
         }
+        // uint16_t mtu = BLEDevice::getMTU();
+        // uint16_t packetSize = mtu > 3 ? mtu - 3 : 20;
+
+        // chunk the buffer into packets
+        // for (size_t i = 0; i < bufferSize; i += packetSize) {
+        //     auto chunkSize = static_cast<uint16_t>(std::min(static_cast<size_t>(packetSize), bufferSize - i));
+        //     this->m_pTxCharacteristic->setValue(const_cast<uint8_t*>(buffer + i), chunkSize);
+        //     this->flush();
+
+        //     // delay if not last packet
+        //     if (i + chunkSize < bufferSize) {
+        //         delay(10);
+        //     }
+        // }
 
         this->m_pTxCharacteristic->setValue(const_cast<uint8_t*>(buffer), bufferSize);
         this->flush();
@@ -131,7 +105,7 @@ class BLESerial : public Stream {
         }
 
         log_d("Creating BLE characteristics with UUIDs '%s' (RX) and '%s' (TX)", rxUuid, txUuid);
-        auto pRxCharacteristic = pService->createCharacteristic(rxUuid, BLECharacteristic::PROPERTY_WRITE);
+        auto pRxCharacteristic = pService->createCharacteristic(rxUuid, BLECharacteristic::PROPERTY_WRITE_NR);
         auto pTxCharacteristic =
           pService->createCharacteristic(txUuid, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
 
@@ -153,7 +127,7 @@ class BLESerial : public Stream {
     BLESerial(BLESerial const& other) = delete;      // disable copy constructor
     void operator=(BLESerial const& other) = delete; // disable assign constructor
 
-    ByteRingBuffer<uint8_t, BLESERIAL_RECEIVE_BUFFER_SIZE> m_receiveBuffer;
+    SenseShift::RingBuffer<uint8_t, BLESERIAL_RECEIVE_BUFFER_SIZE> m_receiveBuffer;
 
     BLEServer* m_pServer;
     BLECharacteristic* m_pRxCharacteristic;
diff --git a/lib/util/senseshift/buffer.hpp b/lib/util/senseshift/buffer.hpp
new file mode 100644
index 00000000..4b21a1cd
--- /dev/null
+++ b/lib/util/senseshift/buffer.hpp
@@ -0,0 +1,123 @@
+#pragma once
+
+#include <algorithm>
+#include <stddef.h>
+
+namespace SenseShift {
+    template<typename _Tp>
+    struct IBuffer {
+        using ValueType = _Tp;
+
+        virtual void add(ValueType value) = 0;
+        virtual void clear() = 0;
+        virtual ValueType pop() = 0;
+        virtual ValueType get(size_t index) = 0;
+        virtual size_t getLength() = 0;
+    };
+
+    template<typename _Tp, size_t N>
+    class RingBuffer : public IBuffer<_Tp> {
+      public:
+        using ValueType = _Tp;
+
+        void add(ValueType value) override
+        {
+            this->mRingBuffer[this->mNewestIndex] = value;
+            this->mNewestIndex = (this->mNewestIndex + 1) % N;
+            this->mLength = std::min(this->mLength + 1, N);
+        }
+
+        void clear() override
+        {
+            this->mNewestIndex = 0;
+            this->mLength = 0;
+        }
+
+        /**
+         * @brief Remove the oldest value from the buffer and return it.
+         * If the buffer is empty, `-1` is returned.
+         */
+        ValueType pop() override
+        {
+            if (this->mLength == 0) {
+                return -1;
+            }
+            ValueType result = this->mRingBuffer[(N + this->mNewestIndex - this->mLength) % N];
+            this->mLength -= 1;
+            return result;
+        }
+
+        /**
+         * @brief Get the value at the given index.
+         *
+         * `get(0)` is the oldest value, `get(this.getLength() - 1)` is the newest value
+         */
+        ValueType get(size_t index) override
+        {
+            if (index < 0 || index >= this->mLength) {
+                return -1;
+            }
+            return this->mRingBuffer[(N + this->mNewestIndex - this->mLength + index) % N];
+        }
+
+        size_t getLength() override { return this->mLength; }
+
+      private:
+        ValueType mRingBuffer[N];
+        size_t mNewestIndex = 0;
+        size_t mLength = 0;
+    };
+
+    template<typename _Tp, size_t N>
+    class FlatBuffer : public IBuffer<_Tp> {
+      public:
+        using ValueType = _Tp;
+
+        void add(ValueType value) override
+        {
+            if (this->mLength == N) {
+                return;
+            }
+            this->mBuffer[this->mLength] = value;
+            this->mLength += 1;
+        }
+
+        void clear() override { this->mLength = 0; }
+
+        /**
+         * @brief Remove the oldest value from the buffer and return it.
+         * If the buffer is empty, `-1` is returned.
+         */
+        ValueType pop() override
+        {
+            if (this->mLength == 0) {
+                return -1;
+            }
+            ValueType result = this->mBuffer[0];
+            for (size_t i = 1; i < this->mLength; i++) {
+                this->mBuffer[i - 1] = this->mBuffer[i];
+            }
+            this->mLength -= 1;
+            return result;
+        }
+
+        /**
+         * @brief Get the value at the given index.
+         *
+         * `get(0)` is the oldest value, `get(this.getLength() - 1)` is the newest value
+         */
+        ValueType get(size_t index) override
+        {
+            if (index < 0 || index >= this->mLength) {
+                return -1;
+            }
+            return this->mBuffer[index];
+        }
+
+        size_t getLength() override { return this->mLength; }
+
+      private:
+        ValueType mBuffer[N];
+        size_t mLength = 0;
+    };
+} // namespace SenseShift
diff --git a/test/test_util_buffer/main.cpp b/test/test_util_buffer/main.cpp
new file mode 100644
index 00000000..595c6184
--- /dev/null
+++ b/test/test_util_buffer/main.cpp
@@ -0,0 +1,169 @@
+#include <senseshift/utility.hpp>
+#include <unity.h>
+
+#include <senseshift/buffer.hpp>
+
+using namespace SenseShift;
+
+void test_ring_buffer(void)
+{
+    auto buffer = RingBuffer<char, 5>();
+
+    TEST_ASSERT_EQUAL_UINT8(0, buffer.getLength());
+
+    buffer.add('a'); // [] => [a]
+    TEST_ASSERT_EQUAL_UINT8(1, buffer.getLength());
+    TEST_ASSERT_EQUAL_INT8('a', buffer.get(0));
+    TEST_ASSERT_EQUAL_UINT8(-1, buffer.get(1));
+    TEST_ASSERT_EQUAL_UINT8(-1, buffer.get(2));
+
+    // pop value and check length
+    TEST_ASSERT_EQUAL_INT8('a', buffer.pop()); // [a] => []
+    TEST_ASSERT_EQUAL_UINT8(0, buffer.getLength());
+
+    // add 5 values and check length
+    buffer.add('a'); // []           => [a]
+    buffer.add('b'); // [a]          => [a, b]
+    buffer.add('c'); // [a, b]       => [a, b, c]
+    buffer.add('d'); // [a, b, c]    => [a, b, c, d]
+    buffer.add('e'); // [a, b, c, d] => [a, b, c, d, e]
+    TEST_ASSERT_EQUAL_UINT8(5, buffer.getLength());
+
+    // check values
+    TEST_ASSERT_EQUAL_INT8('a', buffer.get(0));
+    TEST_ASSERT_EQUAL_INT8('b', buffer.get(1));
+    TEST_ASSERT_EQUAL_INT8('c', buffer.get(2));
+    TEST_ASSERT_EQUAL_INT8('d', buffer.get(3));
+    TEST_ASSERT_EQUAL_INT8('e', buffer.get(4));
+
+    // check values after pop
+    TEST_ASSERT_EQUAL_INT8('a', buffer.pop()); // [a, b, c, d, e] => [b, c, d, e]
+    TEST_ASSERT_EQUAL_INT8('b', buffer.pop()); // [b, c, d, e]    => [c, d, e]
+
+    TEST_ASSERT_EQUAL_INT8('c', buffer.get(0));
+    TEST_ASSERT_EQUAL_INT8('d', buffer.get(1));
+    TEST_ASSERT_EQUAL_INT8('e', buffer.get(2));
+
+    // check clear
+    buffer.clear();
+    TEST_ASSERT_EQUAL_UINT8(0, buffer.getLength());
+    TEST_ASSERT_EQUAL_INT8(-1, buffer.get(0));
+    TEST_ASSERT_EQUAL_INT8(-1, buffer.get(1));
+    TEST_ASSERT_EQUAL_INT8(-1, buffer.get(2));
+    TEST_ASSERT_EQUAL_INT8(-1, buffer.get(3));
+
+    // check overflow
+    buffer.add('c'); // []              => [c]
+    buffer.add('d'); // [c]             => [c, d]
+    buffer.add('e'); // [c, d]          => [c, d, e]
+    buffer.add('f'); // [c, d, e]       => [c, d, e, f]
+    buffer.add('g'); // [c, d, e, f]    => [c, d, e, f, g]
+    buffer.add('h'); // [c, d, e, f, g] => [d, e, f, g, h]
+
+    // Buffer must shift values to the left when it is full
+    TEST_ASSERT_EQUAL_INT8('d', buffer.get(0));
+    TEST_ASSERT_EQUAL_INT8('e', buffer.get(1));
+    TEST_ASSERT_EQUAL_INT8('f', buffer.get(2));
+    TEST_ASSERT_EQUAL_INT8('g', buffer.get(3));
+    TEST_ASSERT_EQUAL_INT8('h', buffer.get(4));
+
+    // check underflow
+    TEST_ASSERT_EQUAL_INT8('d', buffer.pop()); // [d, e, f, g, h] => [e, f, g, h]
+    TEST_ASSERT_EQUAL_INT8('e', buffer.pop()); // [e, f, g, h]    => [f, g, h]
+    TEST_ASSERT_EQUAL_INT8('f', buffer.pop()); // [f, g, h]       => [g, h]
+    TEST_ASSERT_EQUAL_INT8('g', buffer.pop()); // [g, h]          => [h]
+    TEST_ASSERT_EQUAL_INT8('h', buffer.pop()); // [h]             => []
+}
+
+void test_flat_buffer(void)
+{
+    auto buffer = FlatBuffer<char, 5>();
+
+    TEST_ASSERT_EQUAL_UINT8(0, buffer.getLength());
+
+    buffer.add('a'); // [] => [a]
+    TEST_ASSERT_EQUAL_UINT8(1, buffer.getLength());
+    TEST_ASSERT_EQUAL_INT8('a', buffer.get(0));
+    TEST_ASSERT_EQUAL_UINT8(-1, buffer.get(1));
+    TEST_ASSERT_EQUAL_UINT8(-1, buffer.get(2));
+
+    // pop value and check length
+    TEST_ASSERT_EQUAL_INT8('a', buffer.pop()); // [a] => []
+    TEST_ASSERT_EQUAL_UINT8(0, buffer.getLength());
+
+    // add 5 values and check length
+    buffer.add('a'); // []           => [a]
+    buffer.add('b'); // [a]          => [a, b]
+    buffer.add('c'); // [a, b]       => [a, b, c]
+    buffer.add('d'); // [a, b, c]    => [a, b, c, d]
+    buffer.add('e'); // [a, b, c, d] => [a, b, c, d, e]
+    TEST_ASSERT_EQUAL_UINT8(5, buffer.getLength());
+
+    // check values
+    TEST_ASSERT_EQUAL_INT8('a', buffer.get(0));
+    TEST_ASSERT_EQUAL_INT8('b', buffer.get(1));
+    TEST_ASSERT_EQUAL_INT8('c', buffer.get(2));
+    TEST_ASSERT_EQUAL_INT8('d', buffer.get(3));
+    TEST_ASSERT_EQUAL_INT8('e', buffer.get(4));
+
+    // check values after pop
+    TEST_ASSERT_EQUAL_INT8('a', buffer.pop()); // [a, b, c, d, e] => [b, c, d, e]
+    TEST_ASSERT_EQUAL_INT8('b', buffer.pop()); // [b, c, d, e]    => [c, d, e]
+
+    TEST_ASSERT_EQUAL_INT8('c', buffer.get(0));
+    TEST_ASSERT_EQUAL_INT8('d', buffer.get(1));
+    TEST_ASSERT_EQUAL_INT8('e', buffer.get(2));
+    TEST_ASSERT_EQUAL_INT8(-1, buffer.get(3));
+
+    // check clear
+    buffer.clear();
+    TEST_ASSERT_EQUAL_UINT8(0, buffer.getLength());
+    TEST_ASSERT_EQUAL_INT8(-1, buffer.get(0));
+    TEST_ASSERT_EQUAL_INT8(-1, buffer.get(1));
+    TEST_ASSERT_EQUAL_INT8(-1, buffer.get(2));
+
+    // check overflow
+    buffer.add('c'); // []              => [c]
+    buffer.add('d'); // [c]             => [c, d]
+    buffer.add('e'); // [c, d]          => [c, d, e]
+    buffer.add('f'); // [c, d, e]       => [c, d, e, f]
+    buffer.add('g'); // [c, d, e, f]    => [c, d, e, f, g]
+    buffer.add('h'); // [c, d, e, f, g] => [c, d, e, f, g]
+
+    // Buffer must not change values when it is full
+    TEST_ASSERT_EQUAL_INT8('c', buffer.get(0));
+    TEST_ASSERT_EQUAL_INT8('d', buffer.get(1));
+    TEST_ASSERT_EQUAL_INT8('e', buffer.get(2));
+    TEST_ASSERT_EQUAL_INT8('f', buffer.get(3));
+    TEST_ASSERT_EQUAL_INT8('g', buffer.get(4));
+}
+
+int process(void)
+{
+    UNITY_BEGIN();
+
+    RUN_TEST(test_ring_buffer);
+    RUN_TEST(test_flat_buffer);
+
+    return UNITY_END();
+}
+
+#ifdef ARDUINO
+
+#include <Arduino.h>
+
+void setup(void)
+{
+    process();
+}
+
+void loop(void) {}
+
+#else
+
+int main(int argc, char** argv)
+{
+    return process();
+}
+
+#endif

From 295ecc50790115b95137c91576fcd2b892895c54 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <leon.03.99@gmail.com>
Date: Wed, 6 Sep 2023 16:20:37 +0400
Subject: [PATCH 36/82] refactor(BLE): optimize Serial lib

---
 lib/ble_serial/BLESerial.hpp | 182 +++++++++++++++++++++++++++--------
 lib/ble_serial/library.json  |   2 +
 2 files changed, 143 insertions(+), 41 deletions(-)

diff --git a/lib/ble_serial/BLESerial.hpp b/lib/ble_serial/BLESerial.hpp
index 8e4cd4bc..6b0fbe54 100644
--- a/lib/ble_serial/BLESerial.hpp
+++ b/lib/ble_serial/BLESerial.hpp
@@ -40,20 +40,6 @@ class BLESerial : public Stream {
         if (this->m_pTxCharacteristic == nullptr || !this->connected()) {
             return 0;
         }
-        // uint16_t mtu = BLEDevice::getMTU();
-        // uint16_t packetSize = mtu > 3 ? mtu - 3 : 20;
-
-        // chunk the buffer into packets
-        // for (size_t i = 0; i < bufferSize; i += packetSize) {
-        //     auto chunkSize = static_cast<uint16_t>(std::min(static_cast<size_t>(packetSize), bufferSize - i));
-        //     this->m_pTxCharacteristic->setValue(const_cast<uint8_t*>(buffer + i), chunkSize);
-        //     this->flush();
-
-        //     // delay if not last packet
-        //     if (i + chunkSize < bufferSize) {
-        //         delay(10);
-        //     }
-        // }
 
         this->m_pTxCharacteristic->setValue(const_cast<uint8_t*>(buffer), bufferSize);
         this->flush();
@@ -75,22 +61,35 @@ class BLESerial : public Stream {
 
     virtual void flush(void) override { this->m_pTxCharacteristic->notify(true); }
 
+    /**
+     * Begin BLE serial. This will create and start BLE server, service and characteristics.
+     *
+     * @note This will manage the BLE server, service and characteristics. If you want to manage them yourself, use the
+     * other begin().
+     *
+     * @param deviceName Name of the BLE device
+     * @param serviceUuid UUID of the BLE service
+     * @param rxUuid UUID of the BLE characteristic for receiving data
+     * @param txUuid UUID of the BLE characteristic for transmitting data
+     */
     void begin(
       const char* deviceName,
       const char* serviceUuid = SERVICE_UUID,
       const char* rxUuid = RX_UUID,
       const char* txUuid = TX_UUID
-    )
-    {
-        // Create the BLE Device
-        log_d("Creating BLE device with name '%s'", deviceName);
-        BLEDevice::init(deviceName);
-
-        BLEServer* pServer = BLEDevice::createServer();
-
-        this->begin(pServer, serviceUuid, rxUuid, txUuid);
-    }
-
+    );
+
+    /**
+     * Begin BLE serial. This will create and start BLE service and characteristics.
+     *
+     * @note This will manage the BLE service and characteristics. If you want to manage them yourself, use the other
+     * begin().
+     *
+     * @param pServer BLE server instance
+     * @param serviceUuid UUID of the BLE service
+     * @param rxUuid UUID of the BLE characteristic for receiving data
+     * @param txUuid UUID of the BLE characteristic for transmitting data
+     */
     void begin(
       BLEServer* pServer,
       const char* serviceUuid = SERVICE_UUID,
@@ -98,46 +97,124 @@ class BLESerial : public Stream {
       const char* txUuid = TX_UUID
     )
     {
-        log_d("Creating BLE service with UUID '%s'", serviceUuid);
         BLEService* pService = pServer->getServiceByUUID(serviceUuid);
         if (pService == nullptr) {
+            log_d("Creating BLE service with UUID '%s'", serviceUuid);
             pService = pServer->createService(serviceUuid);
+        } else {
+            log_w("BLE service with UUID '%s' already exists", serviceUuid);
         }
 
-        log_d("Creating BLE characteristics with UUIDs '%s' (RX) and '%s' (TX)", rxUuid, txUuid);
-        auto pRxCharacteristic = pService->createCharacteristic(rxUuid, BLECharacteristic::PROPERTY_WRITE_NR);
-        auto pTxCharacteristic =
-          pService->createCharacteristic(txUuid, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
+        // Store the service so we know if we're managing it
+        this->m_pService = pService;
 
-        this->begin(pServer, pRxCharacteristic, pTxCharacteristic);
+        this->begin(pService, rxUuid, txUuid);
 
         pService->start();
         log_d("Started BLE service");
+    }
 
-        BLEAdvertising* pAdvertising = pServer->getAdvertising();
-        pAdvertising->start();
-        log_d("Started BLE advertising");
+    /**
+     * Begin BLE serial. This will create and start BLE characteristics.
+     *
+     * @note If you want to create characteristics yourself, use the other begin().
+     *
+     * @param pService BLE service instance
+     * @param rxUuid UUID of the BLE characteristic for receiving data
+     * @param txUuid UUID of the BLE characteristic for transmitting data
+     */
+    void begin(BLEService* pService, const char* rxUuid = RX_UUID, const char* txUuid = TX_UUID)
+    {
+        auto pRxCharacteristic = pService->getCharacteristic(rxUuid);
+        if (pRxCharacteristic == nullptr) {
+            log_d("Creating BLE characteristic with UUIDs '%s' (RX)", rxUuid);
+            pRxCharacteristic = pService->createCharacteristic(rxUuid, BLECharacteristic::PROPERTY_WRITE_NR);
+        } else {
+            log_w("BLE characteristic with UUID '%s' (RX) already exists", rxUuid);
+        }
+
+        auto pTxCharacteristic = pService->getCharacteristic(txUuid);
+        if (pTxCharacteristic == nullptr) {
+            log_d("Creating BLE characteristic with UUIDs '%s' (TX)", txUuid);
+            pTxCharacteristic = pService->createCharacteristic(
+              txUuid,
+              BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY
+            );
+        } else {
+            log_w("BLE characteristic with UUID '%s' (TX) already exists", txUuid);
+        }
+
+        this->begin(pRxCharacteristic, pTxCharacteristic);
     }
 
-    void begin(BLEServer* pServer, BLECharacteristic* pRxCharacteristic, BLECharacteristic* pTxCharacteristic);
+    /**
+     * Begin BLE serial. This will setup the BLE characteristics.
+     *
+     * @param pServer BLE server instance
+     * @param pRxCharacteristic BLE characteristic instance for receiving data
+     * @param pTxCharacteristic BLE characteristic instance for transmitting data
+     */
+    void begin(BLECharacteristic* pRxCharacteristic, BLECharacteristic* pTxCharacteristic);
+
+    void end()
+    {
+        if (this->m_pService != nullptr) {
+            this->m_pService->stop();
+        }
+
+        if (this->m_pServer != nullptr) {
+            this->m_pServer->getAdvertising()->stop();
+        }
+
+        this->m_pServer = nullptr;
+    }
 
     bool connected() { return m_pServer != nullptr && m_pServer->getConnectedCount() > 0; }
 
+    BLECharacteristic* getRxCharacteristic() { return m_pRxCharacteristic; }
+
+    BLECharacteristic* getTxCharacteristic() { return m_pTxCharacteristic; }
+
   private:
     BLESerial(BLESerial const& other) = delete;      // disable copy constructor
     void operator=(BLESerial const& other) = delete; // disable assign constructor
 
     SenseShift::RingBuffer<uint8_t, BLESERIAL_RECEIVE_BUFFER_SIZE> m_receiveBuffer;
 
-    BLEServer* m_pServer;
-    BLECharacteristic* m_pRxCharacteristic;
-    BLECharacteristic* m_pTxCharacteristic;
+    /**
+     * BLE server instance
+     * @note This is only used if the BLESerial instance is managing the BLE server
+     */
+    BLEServer* m_pServer = nullptr;
+
+    /**
+     * BLE service instance
+     * @note This is only used if the BLESerial instance is managing the BLE service
+     */
+    BLEService* m_pService = nullptr;
+
+    /**
+     * BLE characteristic instance for receiving data
+     */
+    BLECharacteristic* m_pRxCharacteristic = nullptr;
+
+    /**
+     * BLE characteristic instance for transmitting data
+     */
+    BLECharacteristic* m_pTxCharacteristic = nullptr;
 };
 
 class BLESerialServerCallbacks : public BLEServerCallbacks {
-    void onConnect(BLEServer* pServer) override {}
+  public:
+    BLESerialServerCallbacks(BLESerial* bleSerial) : bleSerial(bleSerial) {}
 
-    void onDisconnect(BLEServer* pServer) override
+    void onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t* param) override
+    {
+        uint16_t conn_id = param->connect.conn_id;
+        pServer->updatePeerMTU(conn_id, BLESERIAL_ATTRIBUTE_MAX_VALUE_LENGTH);
+    }
+
+    void onDisconnect(BLEServer* pServer, esp_ble_gatts_cb_param_t* param) override
     {
         auto* pAdvertising = pServer->getAdvertising();
         if (pAdvertising == nullptr) {
@@ -145,6 +222,9 @@ class BLESerialServerCallbacks : public BLEServerCallbacks {
         }
         pAdvertising->start();
     }
+
+  private:
+    BLESerial* bleSerial;
 };
 
 class BLESerialCharacteristicCallbacks : public BLECharacteristicCallbacks {
@@ -167,9 +247,29 @@ class BLESerialCharacteristicCallbacks : public BLECharacteristicCallbacks {
     BLESerial* bleSerial;
 };
 
-void BLESerial::begin(BLEServer* pServer, BLECharacteristic* pRxCharacteristic, BLECharacteristic* pTxCharacteristic)
+void BLESerial::begin(const char* deviceName, const char* serviceUuid, const char* rxUuid, const char* txUuid)
 {
+    // Create the BLE Device
+    log_d("Initializing BLE device with name '%s'", deviceName);
+    BLEDevice::init(deviceName);
+
+    log_d("Creating BLE server");
+    BLEServer* pServer = BLEDevice::createServer();
+    pServer->setCallbacks(new BLESerialServerCallbacks(this));
+
+    // Store the server so we know if we're managing it
     this->m_pServer = pServer;
+
+    this->begin(pServer, serviceUuid, rxUuid, txUuid);
+
+    BLEAdvertising* pAdvertising = pServer->getAdvertising();
+    pAdvertising->start();
+    log_d("Started BLE advertising");
+}
+
+void BLESerial::begin(BLECharacteristic* pRxCharacteristic, BLECharacteristic* pTxCharacteristic)
+{
+    // Store the characteristics so we know if we're managing them
     this->m_pRxCharacteristic = pRxCharacteristic;
     this->m_pTxCharacteristic = pTxCharacteristic;
 
diff --git a/lib/ble_serial/library.json b/lib/ble_serial/library.json
index e237d1ef..7c2d9618 100644
--- a/lib/ble_serial/library.json
+++ b/lib/ble_serial/library.json
@@ -1,5 +1,7 @@
 {
     "$schema": "https://raw.githubusercontent.com/platformio/platformio-core/develop/platformio/assets/schema/library.json",
+    "name": "BLE Serial",
+    "description": "Arduino BLE Serial Stream library",
     "frameworks": "arduino",
     "platforms": [
         "espressif32"

From 82d996cc1e126785eea14e6a73e66dde7acb306c Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Wed, 6 Sep 2023 22:48:55 +0400
Subject: [PATCH 37/82] docs: add Wokwi debug guide

---
 .devcontainer/Dockerfile                      |  1 +
 .devcontainer/devcontainer.json               | 22 +++++++++++++--
 .../lucidgloves-prototype3+serial/test.yaml   |  6 ++--
 .../lucidgloves-prototype3+serial/wokwi.toml  |  2 ++
 docs/DEVELOPMENT.md                           | 28 +++++++++++++++++++
 5 files changed, 54 insertions(+), 5 deletions(-)

diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index eac4101c..01f13c27 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -22,6 +22,7 @@ RUN apt-get update \
     python3-venv \
     python3-distutils \
     python3-setuptools \
+    libpython2.7-dev \
     srecord \
     udev \
     xz-utils \
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 4ec06d5d..c629151a 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -1,6 +1,8 @@
 {
 	// "name": "SenseShift",
-	"dockerFile": "Dockerfile",
+	"build": {
+		"dockerfile": "Dockerfile"
+	},
 	"runArgs": [
 		"--privileged"
 	],
@@ -18,17 +20,33 @@
 		"vscode": {
 			"settings": {
 				"terminal.integrated.defaultProfile.linux": "zsh",
+				"editor.formatOnPaste": false,
 				"editor.formatOnSave": true,
+				"editor.formatOnType": true,
 				"platformio-ide.useBuiltinPIOCore": false,
 				"platformio-ide.useBuiltinPython": false,
 				"platformio-ide.disablePIOHomeStartup": true,
-				"platformio-ide.pioHomeServerHttpPort": 8008
+				"platformio-ide.pioHomeServerHttpPort": 8008,
+				"platformio-ide.autoOpenPlatformIOIniFile": false,
+				"files.exclude": {
+				  "**/.git": true,
+				  "**/.DS_Store": true
+				},
+				"files.associations": {
+				  "**/.vscode/*.json": "jsonc"
+				},
+				"C_Cpp.clang_format_path": "clang-format"
 			},
 			"extensions": [
 				"ms-vscode.cpptools",
+				"redhat.vscode-yaml",
+
 				"platformio.platformio-ide",
 				"Wokwi.wokwi-vscode",
+
 				"xaver.clang-format",
+				"editorconfig.editorconfig",
+
 				"GitHub.copilot",
 				"GitHub.copilot-labs",
 				"GitHub.copilot-chat"
diff --git a/.wokwi/lucidgloves-prototype3+serial/test.yaml b/.wokwi/lucidgloves-prototype3+serial/test.yaml
index 0639c5cc..4cd7043b 100644
--- a/.wokwi/lucidgloves-prototype3+serial/test.yaml
+++ b/.wokwi/lucidgloves-prototype3+serial/test.yaml
@@ -68,9 +68,9 @@ steps:
       value: 1
   - wait-serial: 'A4095B4095C4095D4095E4095F2047G2047ILM' # L is for the Grab gesture
 
-  # Release Thumb finger
+  # Partially Release Thumb finger
   - set-control:
       part-id: pot-thumb
       control: position
-      value: 0
-  - wait-serial: 'A0B4095C4095D4095E4095F2047G2047IL'
+      value: 0.25
+  - wait-serial: 'A1024B4095C4095D4095E4095F2047G2047IL'
diff --git a/.wokwi/lucidgloves-prototype3+serial/wokwi.toml b/.wokwi/lucidgloves-prototype3+serial/wokwi.toml
index 54e28de8..5878bf60 100644
--- a/.wokwi/lucidgloves-prototype3+serial/wokwi.toml
+++ b/.wokwi/lucidgloves-prototype3+serial/wokwi.toml
@@ -2,3 +2,5 @@
 version = 1
 firmware = "../../.pio/build/lucidgloves-prototype3/firmware.bin"
 elf = "../../.pio/build/lucidgloves-prototype3/firmware.elf"
+
+gdbServerPort=3333
diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md
index e7d53aef..dafd8ccf 100644
--- a/docs/DEVELOPMENT.md
+++ b/docs/DEVELOPMENT.md
@@ -1,3 +1,31 @@
+# Debugging
+
+## Debugging in Wokwi
+
+Run and debug firmware in Wokwi Simulator
+
+1. Run this command:
+   ```shell
+   pio debug -e lucidgloves-prototype3
+   ```
+2. `Ctrl+Shift+P` => `> Wokwi: Start Simulator and Wait for Debugger`,
+3. Add launch option (this step is required until PlatformIO fixes this issue: [#3824](https://github.com/platformio/platformio-core/issues/3824)): 
+   ```json
+   ...
+   
+    {
+        "name": "Wokwi GDB - lucidgloves-prototype3",
+        "type": "cppdbg",
+        "request": "launch",
+        "program": "${workspaceFolder}/.pio/build/lucidgloves-prototype3/firmware.elf",
+        "cwd": "${workspaceFolder}",
+        "MIMode": "gdb",
+        "miDebuggerPath": "${userHome}/.platformio/packages/toolchain-xtensa-esp32/bin/xtensa-esp32-elf-gdb",
+        "miDebuggerServerAddress": "localhost:3333",
+    },
+   ...
+   ```
+
 ## Useful Scripts
 
 ### Fix `clang-format`

From d8e45a0318215fc13ee01d8972ac16fb2fcc989d Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Thu, 7 Sep 2023 01:01:40 +0400
Subject: [PATCH 38/82] refactor(OpenGloves): optimize encoding

---
 lib/ble_serial/BLESerial.hpp                  |  2 +-
 .../senseshift/opengloves/encoding/alpha.cpp  | 51 +++++++--------
 .../senseshift/opengloves/encoding/alpha.hpp  | 14 ++--
 .../senseshift/opengloves/interface.hpp       | 11 +++-
 lib/opengloves_task/opengloves_task.hpp       | 10 +--
 lib/util/senseshift/buffer.hpp                | 65 +++++++++++++++----
 lib/util/senseshift/range.hpp                 |  4 +-
 test/test_opengloves_alpha_encoding/main.cpp  |  3 +-
 test/test_util_buffer/main.cpp                | 50 +++++++-------
 9 files changed, 129 insertions(+), 81 deletions(-)

diff --git a/lib/ble_serial/BLESerial.hpp b/lib/ble_serial/BLESerial.hpp
index 6b0fbe54..b239eec2 100644
--- a/lib/ble_serial/BLESerial.hpp
+++ b/lib/ble_serial/BLESerial.hpp
@@ -239,7 +239,7 @@ class BLESerialCharacteristicCallbacks : public BLECharacteristicCallbacks {
 
         std::string rxValue = pCharacteristic->getValue();
         for (int i = 0; i < rxValue.length(); i++) {
-            bleSerial->m_receiveBuffer.add(rxValue[i]);
+            bleSerial->m_receiveBuffer.push(rxValue[i]);
         }
     }
 
diff --git a/lib/opengloves/senseshift/opengloves/encoding/alpha.cpp b/lib/opengloves/senseshift/opengloves/encoding/alpha.cpp
index ad1f4a1e..c9dc738d 100644
--- a/lib/opengloves/senseshift/opengloves/encoding/alpha.cpp
+++ b/lib/opengloves/senseshift/opengloves/encoding/alpha.cpp
@@ -3,14 +3,33 @@
 #include <string.h>
 
 namespace SenseShift::OpenGloves {
-    const std::map<AlphaEncodingService::Command, uint16_t>
-      AlphaEncodingService::deserialize(const std::string& input_string) const
+    size_t AlphaEncodingService::serialize(
+      const std::vector<::OpenGloves::IStringEncodedMemoizedSensor*>& sensors, char* buffer
+    ) const
     {
-        std::map<Command, uint16_t> commands;
+        buffer[0] = '\0';
+        size_t offset = 0;
+
+        for (size_t i = 0; i < sensors.size(); i++) {
+            auto* sensor = sensors[i];
+            offset += sensor->encodeString(buffer + offset);
+        }
+
+        buffer[offset++] = '\n';
+        buffer[offset] = '\0';
+
+        return offset;
+    }
+
+    bool AlphaEncodingService::deserialize(
+      const char* buffer, const size_t length, std::map<::OpenGloves::Command, uint16_t>& commands
+    ) const
+    {
+        std::string input_string(buffer, length);
 
         size_t start = 0; // Start of the current command
-        for (size_t i = 0; i < input_string.size(); i++) {
-            char ch = input_string[i];
+        for (size_t i = 0; i < input_string.length(); i++) {
+            const char ch = input_string[i];
 
             // Start a new command if the character is non-numeric or an opening parenthesis
             // and previous character is a numeric character
@@ -22,7 +41,7 @@ namespace SenseShift::OpenGloves {
 
         AlphaEncodingService::splitCommand(input_string, start, input_string.size(), commands);
 
-        return commands;
+        return true;
     }
 
     void AlphaEncodingService::splitCommand(
@@ -57,24 +76,4 @@ namespace SenseShift::OpenGloves {
         Command command = it->second;
         commands[command] = number;
     }
-
-    const std::string
-      AlphaEncodingService::serialize(const std::vector<::OpenGloves::IStringEncodedMemoizedSensor*>& sensors)
-    {
-        memset(this->writeBuffer, 0, 256);
-        this->writeBuffer[0] = '\0';
-
-        size_t offset = 0;
-
-        for (size_t i = 0; i < sensors.size(); i++) {
-            // The offset is the total charecters already added to the string.
-            offset += sensors[i]->encodeString(this->writeBuffer + offset);
-        }
-
-        // Add a newline and terminator to the end of the encoded string.
-        this->writeBuffer[offset++] = '\n';
-        this->writeBuffer[offset] = '\0';
-
-        return std::string(this->writeBuffer, offset);
-    }
 } // namespace SenseShift::OpenGloves
diff --git a/lib/opengloves/senseshift/opengloves/encoding/alpha.hpp b/lib/opengloves/senseshift/opengloves/encoding/alpha.hpp
index 59830803..49d223cf 100644
--- a/lib/opengloves/senseshift/opengloves/encoding/alpha.hpp
+++ b/lib/opengloves/senseshift/opengloves/encoding/alpha.hpp
@@ -15,6 +15,8 @@
 #include <senseshift/logging.hpp>
 #include <senseshift/opengloves/interface.hpp>
 
+#define SENSESHIFT_OPENGLOVES_ALPHA_ENCODING_BUFFER_SIZE 256
+
 namespace SenseShift::OpenGloves {
     class AlphaEncodingService : public IEncoding {
         using Command = ::OpenGloves::Command;
@@ -36,16 +38,16 @@ namespace SenseShift::OpenGloves {
           // clang-format on
         });
 
-        AlphaEncodingService() : writeBuffer(new char[256]){};
+        AlphaEncodingService(){};
 
-        virtual const std::map<Command, uint16_t> deserialize(const std::string& buffer) const override;
+        virtual size_t serialize(const std::vector<::OpenGloves::IStringEncodedMemoizedSensor*>& sensors, char* buffer)
+          const override;
 
-        virtual const std::string serialize(const std::vector<::OpenGloves::IStringEncodedMemoizedSensor*>& sensors
-        ) override;
+        virtual bool deserialize(
+          const char* buffer, const size_t length, std::map<::OpenGloves::Command, uint16_t>& commands
+        ) const override;
 
       private:
-        char* writeBuffer;
-
         static void splitCommand(
           const std::string& input_string, size_t start, size_t end, std::map<Command, uint16_t>& commands
         );
diff --git a/lib/opengloves/senseshift/opengloves/interface.hpp b/lib/opengloves/senseshift/opengloves/interface.hpp
index 17815845..5e94b1e9 100644
--- a/lib/opengloves/senseshift/opengloves/interface.hpp
+++ b/lib/opengloves/senseshift/opengloves/interface.hpp
@@ -3,6 +3,8 @@
 #include <cstddef>
 #include <cstdint>
 
+#include <senseshift/buffer.hpp>
+
 #include "og_protocol.hpp"
 
 namespace SenseShift::OpenGloves {
@@ -14,8 +16,11 @@ namespace SenseShift::OpenGloves {
     };
 
     struct IEncoding {
-        virtual const std::string serialize(const std::vector<::OpenGloves::IStringEncodedMemoizedSensor*>& sensors
-        ) = 0;
-        virtual const std::map<::OpenGloves::Command, uint16_t> deserialize(const std::string& buffer) const = 0;
+        virtual size_t
+          serialize(const std::vector<::OpenGloves::IStringEncodedMemoizedSensor*>& sensors, char* buffer) const = 0;
+
+        virtual bool deserialize(
+          const char* buffer, const size_t length, std::map<::OpenGloves::Command, uint16_t>& commands
+        ) const = 0;
     };
 } // namespace SenseShift::OpenGloves
diff --git a/lib/opengloves_task/opengloves_task.hpp b/lib/opengloves_task/opengloves_task.hpp
index a04c2f11..2f7bc581 100644
--- a/lib/opengloves_task/opengloves_task.hpp
+++ b/lib/opengloves_task/opengloves_task.hpp
@@ -197,8 +197,9 @@ namespace OpenGloves {
                 }
 
                 // Send the sensor values.
-                auto command = this->encodingService.serialize(this->allSensors);
-                this->communication.send(command.c_str(), command.size());
+                char command[256];
+                size_t length = this->encodingService.serialize(this->allSensors, command);
+                this->communication.send(command, length);
 
                 // Check if the calibration has finished.
                 if (!(this->config.alwaysCalibrate) && calibrationStarted > 0 && (now - calibrationStarted) > CALIBRATION_DURATION) {
@@ -297,9 +298,8 @@ namespace OpenGloves {
                         continue;
                     }
 
-                    command.assign(commandBuffer, bytesRead);
-
-                    auto commands = this->encodingService.deserialize(command);
+                    std::map<::OpenGloves::Command, uint16_t> commands = {};
+                    this->encodingService.deserialize(commandBuffer, bytesRead, commands);
 
                     for (auto& [command, value] : commands) {
                         this->handleCommand(command, value);
diff --git a/lib/util/senseshift/buffer.hpp b/lib/util/senseshift/buffer.hpp
index 4b21a1cd..0dd1c85d 100644
--- a/lib/util/senseshift/buffer.hpp
+++ b/lib/util/senseshift/buffer.hpp
@@ -2,17 +2,24 @@
 
 #include <algorithm>
 #include <stddef.h>
+#include <string.h>
 
 namespace SenseShift {
     template<typename _Tp>
     struct IBuffer {
         using ValueType = _Tp;
 
-        virtual void add(ValueType value) = 0;
+        IBuffer() = default;
+        virtual ~IBuffer() = default;
+
+        virtual bool push(const ValueType value) = 0;
+        virtual bool push(const ValueType* values, size_t length) = 0;
         virtual void clear() = 0;
         virtual ValueType pop() = 0;
-        virtual ValueType get(size_t index) = 0;
-        virtual size_t getLength() = 0;
+        virtual ValueType get(size_t index) const = 0;
+        virtual size_t getLength() const = 0;
+
+        virtual ValueType operator[](size_t index) const { return this->get(index); }
     };
 
     template<typename _Tp, size_t N>
@@ -20,11 +27,26 @@ namespace SenseShift {
       public:
         using ValueType = _Tp;
 
-        void add(ValueType value) override
+        bool push(const ValueType value) override
         {
             this->mRingBuffer[this->mNewestIndex] = value;
             this->mNewestIndex = (this->mNewestIndex + 1) % N;
             this->mLength = std::min(this->mLength + 1, N);
+
+            return true;
+        }
+
+        bool push(const ValueType* values, size_t length) override
+        {
+            if (this->mLength + length > N) {
+                return false;
+            }
+
+            for (size_t i = 0; i < length; i++) {
+                this->push(values[i]);
+            }
+
+            return true;
         }
 
         void clear() override
@@ -52,7 +74,7 @@ namespace SenseShift {
          *
          * `get(0)` is the oldest value, `get(this.getLength() - 1)` is the newest value
          */
-        ValueType get(size_t index) override
+        ValueType get(size_t index) const override
         {
             if (index < 0 || index >= this->mLength) {
                 return -1;
@@ -60,7 +82,7 @@ namespace SenseShift {
             return this->mRingBuffer[(N + this->mNewestIndex - this->mLength + index) % N];
         }
 
-        size_t getLength() override { return this->mLength; }
+        size_t getLength() const override { return this->mLength; }
 
       private:
         ValueType mRingBuffer[N];
@@ -69,17 +91,34 @@ namespace SenseShift {
     };
 
     template<typename _Tp, size_t N>
-    class FlatBuffer : public IBuffer<_Tp> {
+    class FixedSizeBuffer : public IBuffer<_Tp> {
       public:
         using ValueType = _Tp;
 
-        void add(ValueType value) override
+        FixedSizeBuffer() = default;
+        virtual ~FixedSizeBuffer() = default;
+
+        bool push(const ValueType value) override
         {
             if (this->mLength == N) {
-                return;
+                return false;
             }
             this->mBuffer[this->mLength] = value;
             this->mLength += 1;
+
+            return true;
+        }
+
+        bool push(const ValueType* values, size_t length) override
+        {
+            if (this->mLength + length > N) {
+                return false;
+            }
+
+            memcpy(this->mBuffer.data() + this->mLength, values, length);
+            this->mLength += length;
+
+            return true;
         }
 
         void clear() override { this->mLength = 0; }
@@ -106,7 +145,7 @@ namespace SenseShift {
          *
          * `get(0)` is the oldest value, `get(this.getLength() - 1)` is the newest value
          */
-        ValueType get(size_t index) override
+        ValueType get(size_t index) const override
         {
             if (index < 0 || index >= this->mLength) {
                 return -1;
@@ -114,10 +153,12 @@ namespace SenseShift {
             return this->mBuffer[index];
         }
 
-        size_t getLength() override { return this->mLength; }
+        ValueType* getData() { return this->mBuffer.data(); }
+
+        size_t getLength() const override { return this->mLength; }
 
       private:
-        ValueType mBuffer[N];
+        std::array<ValueType, N> mBuffer;
         size_t mLength = 0;
     };
 } // namespace SenseShift
diff --git a/lib/util/senseshift/range.hpp b/lib/util/senseshift/range.hpp
index ea563482..6dac8598 100644
--- a/lib/util/senseshift/range.hpp
+++ b/lib/util/senseshift/range.hpp
@@ -5,7 +5,7 @@
 
 namespace SenseShift {
     template<typename _Tp>
-    constexpr _Tp accurateMap(_Tp x, _Tp in_min, _Tp in_max, _Tp out_min, _Tp out_max)
+    constexpr _Tp accurateMap(_Tp x, _Tp in_min, _Tp in_max, _Tp out_min, _Tp out_max) noexcept
     {
         static_assert(std::is_arithmetic<_Tp>::value, "Type must be arithmetic");
 
@@ -21,7 +21,7 @@ namespace SenseShift {
 
     // Same as the above, but both mins are 0.
     template<typename _Tp>
-    constexpr inline _Tp simpleMap(_Tp x, _Tp in_max, _Tp out_max)
+    constexpr inline _Tp simpleMap(_Tp x, _Tp in_max, _Tp out_max) noexcept
     {
         static_assert(std::is_arithmetic<_Tp>::value, "Type must be arithmetic");
 
diff --git a/test/test_opengloves_alpha_encoding/main.cpp b/test/test_opengloves_alpha_encoding/main.cpp
index 1f71136c..68be53cd 100644
--- a/test/test_opengloves_alpha_encoding/main.cpp
+++ b/test/test_opengloves_alpha_encoding/main.cpp
@@ -94,7 +94,8 @@ void testSplitCommands(void)
     auto encoding_service = AlphaEncodingService();
 
     for (auto& [input_string, expected_commands] : input_strings) {
-        std::map<Command, uint16_t> commands = encoding_service.deserialize(input_string);
+        std::map<Command, uint16_t> commands = {};
+        encoding_service.deserialize(input_string.c_str(), input_string.length(), commands);
 
         TEST_ASSERT_EQUAL_size_t_MESSAGE(
           expected_commands.size(),
diff --git a/test/test_util_buffer/main.cpp b/test/test_util_buffer/main.cpp
index 595c6184..40f1221c 100644
--- a/test/test_util_buffer/main.cpp
+++ b/test/test_util_buffer/main.cpp
@@ -11,7 +11,7 @@ void test_ring_buffer(void)
 
     TEST_ASSERT_EQUAL_UINT8(0, buffer.getLength());
 
-    buffer.add('a'); // [] => [a]
+    buffer.push('a'); // [] => [a]
     TEST_ASSERT_EQUAL_UINT8(1, buffer.getLength());
     TEST_ASSERT_EQUAL_INT8('a', buffer.get(0));
     TEST_ASSERT_EQUAL_UINT8(-1, buffer.get(1));
@@ -22,11 +22,11 @@ void test_ring_buffer(void)
     TEST_ASSERT_EQUAL_UINT8(0, buffer.getLength());
 
     // add 5 values and check length
-    buffer.add('a'); // []           => [a]
-    buffer.add('b'); // [a]          => [a, b]
-    buffer.add('c'); // [a, b]       => [a, b, c]
-    buffer.add('d'); // [a, b, c]    => [a, b, c, d]
-    buffer.add('e'); // [a, b, c, d] => [a, b, c, d, e]
+    buffer.push('a'); // []           => [a]
+    buffer.push('b'); // [a]          => [a, b]
+    buffer.push('c'); // [a, b]       => [a, b, c]
+    buffer.push('d'); // [a, b, c]    => [a, b, c, d]
+    buffer.push('e'); // [a, b, c, d] => [a, b, c, d, e]
     TEST_ASSERT_EQUAL_UINT8(5, buffer.getLength());
 
     // check values
@@ -53,12 +53,12 @@ void test_ring_buffer(void)
     TEST_ASSERT_EQUAL_INT8(-1, buffer.get(3));
 
     // check overflow
-    buffer.add('c'); // []              => [c]
-    buffer.add('d'); // [c]             => [c, d]
-    buffer.add('e'); // [c, d]          => [c, d, e]
-    buffer.add('f'); // [c, d, e]       => [c, d, e, f]
-    buffer.add('g'); // [c, d, e, f]    => [c, d, e, f, g]
-    buffer.add('h'); // [c, d, e, f, g] => [d, e, f, g, h]
+    buffer.push('c'); // []              => [c]
+    buffer.push('d'); // [c]             => [c, d]
+    buffer.push('e'); // [c, d]          => [c, d, e]
+    buffer.push('f'); // [c, d, e]       => [c, d, e, f]
+    buffer.push('g'); // [c, d, e, f]    => [c, d, e, f, g]
+    buffer.push('h'); // [c, d, e, f, g] => [d, e, f, g, h]
 
     // Buffer must shift values to the left when it is full
     TEST_ASSERT_EQUAL_INT8('d', buffer.get(0));
@@ -77,11 +77,11 @@ void test_ring_buffer(void)
 
 void test_flat_buffer(void)
 {
-    auto buffer = FlatBuffer<char, 5>();
+    auto buffer = FixedSizeBuffer<char, 5>();
 
     TEST_ASSERT_EQUAL_UINT8(0, buffer.getLength());
 
-    buffer.add('a'); // [] => [a]
+    buffer.push('a'); // [] => [a]
     TEST_ASSERT_EQUAL_UINT8(1, buffer.getLength());
     TEST_ASSERT_EQUAL_INT8('a', buffer.get(0));
     TEST_ASSERT_EQUAL_UINT8(-1, buffer.get(1));
@@ -92,11 +92,11 @@ void test_flat_buffer(void)
     TEST_ASSERT_EQUAL_UINT8(0, buffer.getLength());
 
     // add 5 values and check length
-    buffer.add('a'); // []           => [a]
-    buffer.add('b'); // [a]          => [a, b]
-    buffer.add('c'); // [a, b]       => [a, b, c]
-    buffer.add('d'); // [a, b, c]    => [a, b, c, d]
-    buffer.add('e'); // [a, b, c, d] => [a, b, c, d, e]
+    buffer.push('a'); // []           => [a]
+    buffer.push('b'); // [a]          => [a, b]
+    buffer.push('c'); // [a, b]       => [a, b, c]
+    buffer.push('d'); // [a, b, c]    => [a, b, c, d]
+    buffer.push('e'); // [a, b, c, d] => [a, b, c, d, e]
     TEST_ASSERT_EQUAL_UINT8(5, buffer.getLength());
 
     // check values
@@ -123,12 +123,12 @@ void test_flat_buffer(void)
     TEST_ASSERT_EQUAL_INT8(-1, buffer.get(2));
 
     // check overflow
-    buffer.add('c'); // []              => [c]
-    buffer.add('d'); // [c]             => [c, d]
-    buffer.add('e'); // [c, d]          => [c, d, e]
-    buffer.add('f'); // [c, d, e]       => [c, d, e, f]
-    buffer.add('g'); // [c, d, e, f]    => [c, d, e, f, g]
-    buffer.add('h'); // [c, d, e, f, g] => [c, d, e, f, g]
+    buffer.push('c'); // []              => [c]
+    buffer.push('d'); // [c]             => [c, d]
+    buffer.push('e'); // [c, d]          => [c, d, e]
+    buffer.push('f'); // [c, d, e]       => [c, d, e, f]
+    buffer.push('g'); // [c, d, e, f]    => [c, d, e, f, g]
+    buffer.push('h'); // [c, d, e, f, g] => [c, d, e, f, g]
 
     // Buffer must not change values when it is full
     TEST_ASSERT_EQUAL_INT8('c', buffer.get(0));

From 9acd9a3fd5deda6249edaae75f2200a37b0dfbfd Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Fri, 8 Sep 2023 20:02:20 +0400
Subject: [PATCH 39/82] test(Calibration): cover center point deviation

---
 lib/util/senseshift/buffer.hpp      |  4 ++--
 lib/util/senseshift/calibration.hpp |  5 +----
 test/test_util_calibration/main.cpp | 21 +++++++++++++++++++++
 3 files changed, 24 insertions(+), 6 deletions(-)

diff --git a/lib/util/senseshift/buffer.hpp b/lib/util/senseshift/buffer.hpp
index 0dd1c85d..a6756cb8 100644
--- a/lib/util/senseshift/buffer.hpp
+++ b/lib/util/senseshift/buffer.hpp
@@ -72,7 +72,7 @@ namespace SenseShift {
         /**
          * @brief Get the value at the given index.
          *
-         * `get(0)` is the oldest value, `get(this.getLength() - 1)` is the newest value
+         * @example `get(0)` is the oldest value, `get(this.getLength() - 1)` is the newest value
          */
         ValueType get(size_t index) const override
         {
@@ -143,7 +143,7 @@ namespace SenseShift {
         /**
          * @brief Get the value at the given index.
          *
-         * `get(0)` is the oldest value, `get(this.getLength() - 1)` is the newest value
+         * @example `get(0)` is the oldest value, `get(this.getLength() - 1)` is the newest value
          */
         ValueType get(size_t index) const override
         {
diff --git a/lib/util/senseshift/calibration.hpp b/lib/util/senseshift/calibration.hpp
index 13a9834b..53411728 100644
--- a/lib/util/senseshift/calibration.hpp
+++ b/lib/util/senseshift/calibration.hpp
@@ -84,10 +84,7 @@ namespace SenseShift::Calibration {
     template<typename _Tp, _Tp sensor_max, _Tp driver_max_deviation, _Tp output_min, _Tp output_max>
     class CenterPointDeviationCalibrator : public ICalibrator<_Tp> {
       public:
-        CenterPointDeviationCalibrator() : range_min(sensor_max), range_max(0)
-        {
-#warning "CenterPointDeviationCalibrator is untested and may not work as expected."
-        }
+        CenterPointDeviationCalibrator() : range_min(sensor_max), range_max(0) {}
 
         void reset()
         {
diff --git a/test/test_util_calibration/main.cpp b/test/test_util_calibration/main.cpp
index a79a8bc6..1fa42a23 100644
--- a/test/test_util_calibration/main.cpp
+++ b/test/test_util_calibration/main.cpp
@@ -35,6 +35,26 @@ void test_minmax_calibrator(void)
     TEST_ASSERT_EQUAL_UINT16(2048, calibrator->calibrate(4096));
 }
 
+void test_center_point_deviation_calibrator(void)
+{
+    CenterPointDeviationCalibrator<int, 100, 10, 0, 255> calibrator;
+
+    // Test reset function
+    calibrator.reset();
+
+    // Test update function
+    calibrator.update(50);
+    calibrator.update(75);
+    calibrator.update(25);
+
+    // Test calibrate function
+    TEST_ASSERT_EQUAL_INT(255, calibrator.calibrate(100));
+    TEST_ASSERT_EQUAL_INT(191, calibrator.calibrate(75));
+    TEST_ASSERT_EQUAL_INT(63, calibrator.calibrate(50));
+    TEST_ASSERT_EQUAL_INT(0, calibrator.calibrate(25));
+    TEST_ASSERT_EQUAL_INT(0, calibrator.calibrate(0));
+}
+
 void test_fixed_center_point_deviation_calibrator(void)
 {
     auto calibrator = new FixedCenterPointDeviationCalibrator<uint16_t, 512, 64, 0, 4096>();
@@ -91,6 +111,7 @@ int process(void)
     UNITY_BEGIN();
 
     RUN_TEST(test_minmax_calibrator);
+    RUN_TEST(test_center_point_deviation_calibrator);
     RUN_TEST(test_fixed_center_point_deviation_calibrator);
 
     return UNITY_END();

From 14a2b5b68716bd133e1cc39c745c557c75e9d779 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Fri, 8 Sep 2023 18:34:45 +0000
Subject: [PATCH 40/82] ci(GitHub): add Wokwi for lucidglove proto 4

---
 .github/workflows/ci.yml                      |   1 +
 .../lucidgloves-prototype3+serial/test.yaml   | 122 ++---
 .../diagram.json                              | 485 ++++++++++++++++++
 .../lucidgloves-prototype4+serial/test.yaml   |  76 +++
 .../lucidgloves-prototype4+serial/wokwi.toml  |   6 +
 5 files changed, 629 insertions(+), 61 deletions(-)
 create mode 100644 .wokwi/lucidgloves-prototype4+serial/diagram.json
 create mode 100644 .wokwi/lucidgloves-prototype4+serial/test.yaml
 create mode 100644 .wokwi/lucidgloves-prototype4+serial/wokwi.toml

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1119c892..9c373108 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -428,6 +428,7 @@ jobs:
       matrix:
         variant:
           - lucidgloves-prototype3+serial
+          - lucidgloves-prototype4+serial
 
     steps:
       - uses: actions/checkout@v3
diff --git a/.wokwi/lucidgloves-prototype3+serial/test.yaml b/.wokwi/lucidgloves-prototype3+serial/test.yaml
index 0639c5cc..db653793 100644
--- a/.wokwi/lucidgloves-prototype3+serial/test.yaml
+++ b/.wokwi/lucidgloves-prototype3+serial/test.yaml
@@ -3,74 +3,74 @@ version: 1
 author: Leonid Meleshin
 
 steps:
-  - wait-serial: "A0B0C0D0E0F2047G2047"
+    - wait-serial: "A0B0C0D0E0F2047G2047"
 
-  # Press the 'A' button
-  - set-control:
-      part-id: btn1
-      control: pressed
-      value: 1
-  - wait-serial: 'A0B0C0D0E0F2047G2047J'
+    # Press the 'A' button
+    - set-control:
+          part-id: btn1
+          control: pressed
+          value: 1
+    - wait-serial: "A0B0C0D0E0F2047G2047J"
 
-  # Press the 'B' button
-  - set-control:
-      part-id: btn2
-      control: pressed
-      value: 1
-  - wait-serial: 'A0B0C0D0E0F2047G2047JK'
+    # Press the 'B' button
+    - set-control:
+          part-id: btn2
+          control: pressed
+          value: 1
+    - wait-serial: "A0B0C0D0E0F2047G2047JK"
 
-  # Release the 'A' button
-  - set-control:
-      part-id: btn1
-      control: pressed
-      value: 0
-  - wait-serial: 'A0B0C0D0E0F2047G2047K'
+    # Release the 'A' button
+    - set-control:
+          part-id: btn1
+          control: pressed
+          value: 0
+    - wait-serial: "A0B0C0D0E0F2047G2047K"
 
-  # Release the 'B' button
-  - set-control:
-      part-id: btn2
-      control: pressed
-      value: 0
-  - wait-serial: 'A0B0C0D0E0F2047G2047'
+    # Release the 'B' button
+    - set-control:
+          part-id: btn2
+          control: pressed
+          value: 0
+    - wait-serial: "A0B0C0D0E0F2047G2047"
 
-  # Curl Index finger
-  - set-control:
-      part-id: pot-index
-      control: position
-      value: 1
-  - wait-serial: 'A0B4095C0D0E0F2047G2047I' # I is for Trigger gesture
+    # Curl Index finger
+    - set-control:
+          part-id: pot-index
+          control: position
+          value: 1
+    - wait-serial: "A0B4095C0D0E0F2047G2047I" # I is for Trigger gesture
 
-  # Curl Thumb finger
-  - set-control:
-      part-id: pot-thumb
-      control: position
-      value: 1
-  - wait-serial: 'A4095B4095C0D0E0F2047G2047IM' # M is for Pinch gesture
+    # Curl Thumb finger
+    - set-control:
+          part-id: pot-thumb
+          control: position
+          value: 1
+    - wait-serial: "A4095B4095C0D0E0F2047G2047IM" # M is for Pinch gesture
 
-  # Curl Middle finger
-  - set-control:
-      part-id: pot-middle
-      control: position
-      value: 1
-  - wait-serial: 'A4095B4095C4095D0E0F2047G2047IM'
+    # Curl Middle finger
+    - set-control:
+          part-id: pot-middle
+          control: position
+          value: 1
+    - wait-serial: "A4095B4095C4095D0E0F2047G2047IM"
 
-  # Curl Ring finger
-  - set-control:
-      part-id: pot-ring
-      control: position
-      value: 1
-  - wait-serial: 'A4095B4095C4095D4095E0F2047G2047IM'
+    # Curl Ring finger
+    - set-control:
+          part-id: pot-ring
+          control: position
+          value: 1
+    - wait-serial: "A4095B4095C4095D4095E0F2047G2047IM"
 
-  # Curl Pinky finger
-  - set-control:
-      part-id: pot-pinky
-      control: position
-      value: 1
-  - wait-serial: 'A4095B4095C4095D4095E4095F2047G2047ILM' # L is for the Grab gesture
+    # Curl Pinky finger
+    - set-control:
+          part-id: pot-pinky
+          control: position
+          value: 1
+    - wait-serial: "A4095B4095C4095D4095E4095F2047G2047ILM" # L is for the Grab gesture
 
-  # Release Thumb finger
-  - set-control:
-      part-id: pot-thumb
-      control: position
-      value: 0
-  - wait-serial: 'A0B4095C4095D4095E4095F2047G2047IL'
+    # Release Thumb finger
+    - set-control:
+          part-id: pot-thumb
+          control: position
+          value: 0
+    - wait-serial: "A0B4095C4095D4095E4095F2047G2047IL"
diff --git a/.wokwi/lucidgloves-prototype4+serial/diagram.json b/.wokwi/lucidgloves-prototype4+serial/diagram.json
new file mode 100644
index 00000000..70ab1ebc
--- /dev/null
+++ b/.wokwi/lucidgloves-prototype4+serial/diagram.json
@@ -0,0 +1,485 @@
+{
+    "version": 1,
+    "author": "Leonid Meleshin",
+    "editor": "wokwi",
+    "parts": [
+        {
+            "type": "wokwi-esp32-devkit-v1",
+            "id": "esp",
+            "top": 110.3,
+            "left": 599.8,
+            "attrs": {}
+        },
+        {
+            "type": "wokwi-potentiometer",
+            "id": "pot-thumb",
+            "top": -87.7,
+            "left": -278.6,
+            "attrs": {}
+        },
+        {
+            "type": "wokwi-potentiometer",
+            "id": "pot-index",
+            "top": -87.7,
+            "left": -192.2,
+            "attrs": {}
+        },
+        {
+            "type": "wokwi-potentiometer",
+            "id": "pot-middle",
+            "top": -87.7,
+            "left": -105.8,
+            "attrs": {}
+        },
+        {
+            "type": "wokwi-potentiometer",
+            "id": "pot-ring",
+            "top": -87.7,
+            "left": -19.4,
+            "attrs": {}
+        },
+        {
+            "type": "wokwi-potentiometer",
+            "id": "pot-pinky",
+            "top": -87.7,
+            "left": 67,
+            "attrs": {}
+        },
+        {
+            "type": "wokwi-analog-joystick",
+            "id": "joystick1",
+            "top": -135,
+            "left": 149.4,
+            "attrs": {}
+        },
+        {
+            "type": "wokwi-pushbutton",
+            "id": "btn1",
+            "top": 6.2,
+            "left": 268.8,
+            "attrs": {
+                "color": "green",
+                "key": "a",
+                "bounce": "1",
+                "label": "A"
+            }
+        },
+        {
+            "type": "wokwi-pushbutton",
+            "id": "btn2",
+            "top": 6.2,
+            "left": 355.2,
+            "attrs": {
+                "color": "green",
+                "key": "b",
+                "bounce": "1",
+                "label": "B"
+            }
+        },
+        {
+            "type": "wokwi-pushbutton",
+            "id": "btn3",
+            "top": 6.2,
+            "left": 451.2,
+            "attrs": {
+                "color": "green"
+            }
+        },
+        {
+            "type": "wokwi-servo",
+            "id": "servo1",
+            "top": -30.8,
+            "left": 912,
+            "attrs": {}
+        },
+        {
+            "type": "wokwi-servo",
+            "id": "servo2",
+            "top": 103.6,
+            "left": 912,
+            "attrs": {}
+        },
+        {
+            "type": "wokwi-servo",
+            "id": "servo3",
+            "top": 238,
+            "left": 912,
+            "attrs": {}
+        },
+        {
+            "type": "wokwi-servo",
+            "id": "servo4",
+            "top": 372.4,
+            "left": 912,
+            "attrs": {}
+        },
+        {
+            "type": "wokwi-servo",
+            "id": "servo5",
+            "top": -165.2,
+            "left": 912,
+            "attrs": {}
+        }
+    ],
+    "connections": [
+        [
+            "esp:TX0",
+            "$serialMonitor:RX",
+            "",
+            []
+        ],
+        [
+            "esp:RX0",
+            "$serialMonitor:TX",
+            "",
+            []
+        ],
+        [
+            "pot-thumb:VCC",
+            "pot-index:VCC",
+            "red",
+            [
+                "v28.8",
+                "h76.8"
+            ]
+        ],
+        [
+            "pot-index:VCC",
+            "pot-middle:VCC",
+            "red",
+            [
+                "v28.8",
+                "h76.8"
+            ]
+        ],
+        [
+            "pot-middle:VCC",
+            "pot-ring:VCC",
+            "red",
+            [
+                "v28.8",
+                "h76.8"
+            ]
+        ],
+        [
+            "pot-thumb:GND",
+            "pot-index:GND",
+            "black",
+            [
+                "v38.4",
+                "h76.8"
+            ]
+        ],
+        [
+            "pot-index:GND",
+            "pot-middle:GND",
+            "black",
+            [
+                "v38.4",
+                "h76.8"
+            ]
+        ],
+        [
+            "pot-middle:GND",
+            "pot-ring:GND",
+            "black",
+            [
+                "v38.4",
+                "h76.8"
+            ]
+        ],
+        [
+            "pot-ring:GND",
+            "pot-pinky:GND",
+            "black",
+            [
+                "v38.4",
+                "h76.8"
+            ]
+        ],
+        [
+            "esp:VP",
+            "pot-pinky:SIG",
+            "green",
+            [
+                "h0"
+            ]
+        ],
+        [
+            "esp:VN",
+            "pot-ring:SIG",
+            "green",
+            [
+                "h0"
+            ]
+        ],
+        [
+            "esp:D34",
+            "pot-middle:SIG",
+            "green",
+            [
+                "h0"
+            ]
+        ],
+        [
+            "esp:D35",
+            "pot-index:SIG",
+            "green",
+            [
+                "h0"
+            ]
+        ],
+        [
+            "esp:D32",
+            "pot-thumb:SIG",
+            "green",
+            [
+                "h0"
+            ]
+        ],
+        [
+            "joystick1:GND",
+            "pot-pinky:GND",
+            "black",
+            [
+                "v38.4",
+                "h-124.8"
+            ]
+        ],
+        [
+            "esp:D33",
+            "joystick1:HORZ",
+            "green",
+            [
+                "h0"
+            ]
+        ],
+        [
+            "esp:D25",
+            "joystick1:VERT",
+            "green",
+            [
+                "h0"
+            ]
+        ],
+        [
+            "esp:D26",
+            "joystick1:SEL",
+            "green",
+            [
+                "h0"
+            ]
+        ],
+        [
+            "esp:GND.2",
+            "joystick1:GND",
+            "black",
+            [
+                "h0"
+            ]
+        ],
+        [
+            "esp:VIN",
+            "joystick1:VCC",
+            "red",
+            [
+                "h0"
+            ]
+        ],
+        [
+            "pot-ring:VCC",
+            "pot-pinky:VCC",
+            "red",
+            [
+                "v28.8",
+                "h37.6"
+            ]
+        ],
+        [
+            "joystick1:VCC",
+            "pot-pinky:VCC",
+            "red",
+            [
+                "v28.8",
+                "h-38.4"
+            ]
+        ],
+        [
+            "btn1:2.r",
+            "esp:D27",
+            "green",
+            [
+                "h0.2",
+                "v249.8"
+            ]
+        ],
+        [
+            "btn2:2.r",
+            "esp:D14",
+            "green",
+            [
+                "h0.2",
+                "v259.4"
+            ]
+        ],
+        [
+            "btn3:2.r",
+            "esp:D12",
+            "green",
+            [
+                "h0.2",
+                "v211.4"
+            ]
+        ],
+        [
+            "servo2:GND",
+            "servo3:GND",
+            "black",
+            [
+                "h-19.2",
+                "v134.4"
+            ]
+        ],
+        [
+            "servo3:GND",
+            "servo4:GND",
+            "black",
+            [
+                "h-19.2",
+                "v115.2"
+            ]
+        ],
+        [
+            "joystick1:GND",
+            "btn1:1.l",
+            "black",
+            [
+                "v0"
+            ]
+        ],
+        [
+            "btn1:1.r",
+            "btn2:1.l",
+            "black",
+            [
+                "v0"
+            ]
+        ],
+        [
+            "btn2:1.r",
+            "btn3:1.l",
+            "black",
+            [
+                "v0"
+            ]
+        ],
+        [
+            "servo1:GND",
+            "servo2:GND",
+            "black",
+            [
+                "h-19.2",
+                "v96"
+            ]
+        ],
+        [
+            "servo1:GND",
+            "btn3:1.r",
+            "black",
+            [
+                "h0"
+            ]
+        ],
+        [
+            "servo1:V+",
+            "servo2:V+",
+            "red",
+            [
+                "h-28.8",
+                "v96.1"
+            ]
+        ],
+        [
+            "servo2:V+",
+            "servo3:V+",
+            "red",
+            [
+                "h-28.8",
+                "v86.5"
+            ]
+        ],
+        [
+            "servo3:V+",
+            "servo4:V+",
+            "red",
+            [
+                "h-28.8",
+                "v134.4"
+            ]
+        ],
+        [
+            "esp:D5",
+            "servo4:PWM",
+            "green",
+            [
+                "h38.1",
+                "v239.8"
+            ]
+        ],
+        [
+            "esp:D18",
+            "servo3:PWM",
+            "green",
+            [
+                "h57.3",
+                "v115"
+            ]
+        ],
+        [
+            "esp:D21",
+            "servo1:PWM",
+            "green",
+            [
+                "h38.1",
+                "v-135"
+            ]
+        ],
+        [
+            "esp:D19",
+            "servo2:PWM",
+            "green",
+            [
+                "h57.3",
+                "v-9.9"
+            ]
+        ],
+        [
+            "servo1:V+",
+            "servo5:V+",
+            "red",
+            [
+                "h-28.8",
+                "v-134.3"
+            ]
+        ],
+        [
+            "servo1:GND",
+            "servo5:GND",
+            "black",
+            [
+                "h-19.2",
+                "v-134.4"
+            ]
+        ],
+        [
+            "esp:TX2",
+            "servo5:PWM",
+            "green",
+            [
+                "h18.9",
+                "v-307.3"
+            ]
+        ]
+    ],
+    "dependencies": {}
+}
diff --git a/.wokwi/lucidgloves-prototype4+serial/test.yaml b/.wokwi/lucidgloves-prototype4+serial/test.yaml
new file mode 100644
index 00000000..f89d70f4
--- /dev/null
+++ b/.wokwi/lucidgloves-prototype4+serial/test.yaml
@@ -0,0 +1,76 @@
+name: Test LucidGloves Prototype 4
+version: 1
+author: Leonid Meleshin
+
+steps:
+    - wait-serial: "A0B0C0D0E0F2047G2047"
+
+    # Press the 'A' button
+    - set-control:
+          part-id: btn1
+          control: pressed
+          value: 1
+    - wait-serial: "A0B0C0D0E0F2047G2047J"
+
+    # Press the 'B' button
+    - set-control:
+          part-id: btn2
+          control: pressed
+          value: 1
+    - wait-serial: "A0B0C0D0E0F2047G2047JK"
+
+    # Release the 'A' button
+    - set-control:
+          part-id: btn1
+          control: pressed
+          value: 0
+    - wait-serial: "A0B0C0D0E0F2047G2047K"
+
+    # Release the 'B' button
+    - set-control:
+          part-id: btn2
+          control: pressed
+          value: 0
+    - wait-serial: "A0B0C0D0E0F2047G2047"
+
+    # Curl Index finger
+    - set-control:
+          part-id: pot-index
+          control: position
+          value: 1
+    - wait-serial: "A0B4095C0D0E0F2047G2047I" # I is for Trigger gesture
+
+    # Curl Thumb finger
+    - set-control:
+          part-id: pot-thumb
+          control: position
+          value: 1
+    - wait-serial: "A4095B4095C0D0E0F2047G2047IM" # M is for Pinch gesture
+
+    # Curl Middle finger
+    - set-control:
+          part-id: pot-middle
+          control: position
+          value: 1
+    - wait-serial: "A4095B4095C4095D0E0F2047G2047IM"
+
+    # Curl Ring finger
+    - set-control:
+          part-id: pot-ring
+          control: position
+          value: 1
+    - wait-serial: "A4095B4095C4095D4095E0F2047G2047IM"
+
+    # Curl Pinky finger
+    - set-control:
+          part-id: pot-pinky
+          control: position
+          value: 1
+    - wait-serial: "A4095B4095C4095D4095E4095F2047G2047ILM" # L is for the Grab gesture
+
+    # Partially Release Thumb finger
+    - set-control:
+          part-id: pot-thumb
+          control: position
+          value: 0.25
+    - wait-serial: "A1024B4095C4095D4095E4095F2047G2047IL"
diff --git a/.wokwi/lucidgloves-prototype4+serial/wokwi.toml b/.wokwi/lucidgloves-prototype4+serial/wokwi.toml
new file mode 100644
index 00000000..316b68c7
--- /dev/null
+++ b/.wokwi/lucidgloves-prototype4+serial/wokwi.toml
@@ -0,0 +1,6 @@
+[wokwi]
+version = 1
+firmware = "../../.pio/build/lucidgloves-prototype4/firmware.bin"
+elf = "../../.pio/build/lucidgloves-prototype4/firmware.elf"
+
+gdbServerPort=3333

From 3a2b887b788c51f9bba60dbf90caabec16227724 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Sun, 10 Sep 2023 13:35:32 +0400
Subject: [PATCH 41/82] fix(Wokwi): test scenario indentation

---
 .wokwi/lucidgloves-prototype3+serial/test.yaml | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/.wokwi/lucidgloves-prototype3+serial/test.yaml b/.wokwi/lucidgloves-prototype3+serial/test.yaml
index b5c030bc..95f55e39 100644
--- a/.wokwi/lucidgloves-prototype3+serial/test.yaml
+++ b/.wokwi/lucidgloves-prototype3+serial/test.yaml
@@ -68,9 +68,9 @@ steps:
           value: 1
     - wait-serial: "A4095B4095C4095D4095E4095F2047G2047ILM" # L is for the Grab gesture
 
-  # Partially Release Thumb finger
-  - set-control:
-      part-id: pot-thumb
-      control: position
-      value: 0.25
-  - wait-serial: 'A1024B4095C4095D4095E4095F2047G2047IL'
+    # Partially Release Thumb finger
+    - set-control:
+          part-id: pot-thumb
+          control: position
+          value: 0.25
+    - wait-serial: 'A1024B4095C4095D4095E4095F2047G2047IL'

From a373ccdce2a0f2fb786a32c66b325079a7dc5ab8 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Thu, 28 Sep 2023 20:41:17 +0400
Subject: [PATCH 42/82] chore: add Wakatime to devcontainer

---
 .devcontainer/devcontainer.json | 24 ++++++++++++++----------
 1 file changed, 14 insertions(+), 10 deletions(-)

diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index c629151a..1fee0e22 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -12,10 +12,16 @@
 	"mounts": [
 		"source=/dev/,target=/dev/,type=bind,consistency=consistent"
 	],
-	"postAttachCommand": {
-		"submodules": ["git", "submodule", "update", "--recursive", "--init"],
-		"udev": ["sudo", "service", "udev", "restart"]
+	"updateContentCommand": {
+		"submodule": "git submodule update --init --recursive",
+		"pio": "pio pkg install"
 	},
+	"postAttachCommand": [
+		"sudo",
+		"service",
+		"udev",
+		"restart"
+	],
 	"customizations": {
 		"vscode": {
 			"settings": {
@@ -29,27 +35,25 @@
 				"platformio-ide.pioHomeServerHttpPort": 8008,
 				"platformio-ide.autoOpenPlatformIOIniFile": false,
 				"files.exclude": {
-				  "**/.git": true,
-				  "**/.DS_Store": true
+					"**/.git": true,
+					"**/.DS_Store": true
 				},
 				"files.associations": {
-				  "**/.vscode/*.json": "jsonc"
+					"**/.vscode/*.json": "jsonc"
 				},
 				"C_Cpp.clang_format_path": "clang-format"
 			},
 			"extensions": [
 				"ms-vscode.cpptools",
 				"redhat.vscode-yaml",
-
 				"platformio.platformio-ide",
 				"Wokwi.wokwi-vscode",
-
 				"xaver.clang-format",
 				"editorconfig.editorconfig",
-
 				"GitHub.copilot",
 				"GitHub.copilot-labs",
-				"GitHub.copilot-chat"
+				"GitHub.copilot-chat",
+				"wakatime.vscode-wakatime"
 			]
 		}
 	}

From af9df068fa0c02e9ab7fb20002859ce281049f7f Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Fri, 29 Sep 2023 13:56:06 +0400
Subject: [PATCH 43/82] refactor: remove C-styled `_t` typedefs

---
 examples/bhaptics-ble-bt-serial.cpp           |   4 +-
 firmware/mode_configs/bhaptics/tactal.cpp     |   6 +-
 firmware/mode_configs/bhaptics/tactglove.cpp  |   8 +-
 firmware/mode_configs/bhaptics/tactosy2.cpp   |   6 +-
 firmware/mode_configs/bhaptics/tactosyf.cpp   |   6 +-
 firmware/mode_configs/bhaptics/tactosyh.cpp   |   6 +-
 .../mode_configs/bhaptics/tactsuit_x16.cpp    |  10 +-
 .../bhaptics/tactsuit_x16_pca9685.cpp         |  10 +-
 .../mode_configs/bhaptics/tactsuit_x40.cpp    |   8 +-
 firmware/mode_configs/bhaptics/tactvisor.cpp  |   6 +-
 .../arduino/components/serial_plotter.cpp     |   2 +-
 .../arduino/input/sensor/digital.hpp          |   2 +-
 lib/battery/senseshift/battery.hpp            |   8 +-
 lib/battery/senseshift/battery/sensor.hpp     |   2 +-
 lib/bhaptics/senseshift/bh/devices.hpp        | 294 +++++++++---------
 lib/bhaptics/senseshift/bh/encoding.hpp       |  73 ++---
 .../senseshift/bh/ble/connection.cpp          |  10 +-
 .../senseshift/bh/ble/connection.hpp          |  12 +-
 .../senseshift/freertos/input/sensor.hpp      |  12 +-
 lib/hands/hand_interface.hpp                  |  12 +-
 lib/haptics/senseshift/body/haptics/body.cpp  |   8 +-
 lib/haptics/senseshift/body/haptics/body.hpp  |  16 +-
 .../senseshift/body/haptics/interface.hpp     |  54 ++--
 lib/haptics/senseshift/body/haptics/plane.cpp |  18 +-
 lib/haptics/senseshift/body/haptics/plane.hpp |  57 ++--
 lib/math/senseshift/math/point2.hpp           |  18 +-
 lib/opengloves/og_protocol.hpp                |   8 +-
 lib/opengloves/sensor/og_finger.hpp           |   2 +-
 test/test_bhaptics_encoding/main.cpp          |  28 +-
 test/test_haptics_body/main.cpp               |  10 +-
 test/test_haptics_plane/main.cpp              |  12 +-
 31 files changed, 357 insertions(+), 371 deletions(-)

diff --git a/examples/bhaptics-ble-bt-serial.cpp b/examples/bhaptics-ble-bt-serial.cpp
index 3a532691..9f1f9820 100644
--- a/examples/bhaptics-ble-bt-serial.cpp
+++ b/examples/bhaptics-ble-bt-serial.cpp
@@ -21,8 +21,8 @@ SenseShift* app = &App;
 BluetoothSerial SerialBT;
 BluetoothSerial* btSerial = &SerialBT;
 
-static const size_t bhLayoutSize = BH_LAYOUT_TACTAL_SIZE;
-static const ::SenseShift::Body::Haptics::Position_t* bhLayout[bhLayoutSize] = BH_LAYOUT_TACTAL;
+static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTAL_SIZE;
+static const ::SenseShift::Body::Haptics::Position* bhLayout[bhLayoutSize] = BH_LAYOUT_TACTAL;
 
 class BLECallbacks : public BHBLEConnectionCallbacks {
   public:
diff --git a/firmware/mode_configs/bhaptics/tactal.cpp b/firmware/mode_configs/bhaptics/tactal.cpp
index 2c99a8fc..54bd226f 100644
--- a/firmware/mode_configs/bhaptics/tactal.cpp
+++ b/firmware/mode_configs/bhaptics/tactal.cpp
@@ -25,13 +25,13 @@ using namespace SenseShift::Body::Haptics;
 extern SenseShift::SenseShift App;
 SenseShift::SenseShift* app = &App;
 
-static const size_t bhLayoutSize = BH_LAYOUT_TACTAL_SIZE;
-static const Position_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTAL;
+static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTAL_SIZE;
+static const Position bhLayout[bhLayoutSize] = BH_LAYOUT_TACTAL;
 
 void setupMode()
 {
     // Configure PWM pins to their positions on the face
-    const auto faceOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
+    const auto faceOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
       // clang-format off
       { new ActuatorPWM(32), new ActuatorPWM(33), new ActuatorPWM(25), new ActuatorPWM(26), new ActuatorPWM(27), new ActuatorPWM(14) },
       // clang-format on
diff --git a/firmware/mode_configs/bhaptics/tactglove.cpp b/firmware/mode_configs/bhaptics/tactglove.cpp
index 8d2138f6..3091a5d6 100644
--- a/firmware/mode_configs/bhaptics/tactglove.cpp
+++ b/firmware/mode_configs/bhaptics/tactglove.cpp
@@ -26,10 +26,10 @@ using namespace SenseShift::Body::Haptics;
 extern SenseShift::SenseShift App;
 SenseShift::SenseShift* app = &App;
 
-static const Body::Hands::HandSide_t handSide = Body::Hands::HandSide::SENSESHIFT_HAND_SIDE;
-static const size_t bhLayoutSize = BH_LAYOUT_TACTGLOVE_SIZE;
+static constexpr Body::Hands::HandSide handSide = Body::Hands::HandSide::SENSESHIFT_HAND_SIDE;
+static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTGLOVE_SIZE;
 // clang-format off
-static const OutputLayout_t (&bhLayout)[bhLayoutSize] = handSide == Body::Hands::HandSide::Left ? BH::TactGloveLeftLayout : BH::TactGloveRightLayout;
+static const OutputLayout (&bhLayout)[bhLayoutSize] = handSide == Body::Hands::HandSide::Left ? BH::TactGloveLeftLayout : BH::TactGloveRightLayout;
 // clang-format on
 
 void setupMode()
@@ -56,7 +56,7 @@ void setupMode()
         .serialNumber = BH_SERIAL_NUMBER,
       },
       [](std::string& value) -> void {
-          Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect_t::Vibro);
+          Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect::Vibro);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactosy2.cpp b/firmware/mode_configs/bhaptics/tactosy2.cpp
index 38743a78..20751691 100644
--- a/firmware/mode_configs/bhaptics/tactosy2.cpp
+++ b/firmware/mode_configs/bhaptics/tactosy2.cpp
@@ -25,13 +25,13 @@ using namespace SenseShift::Body::Haptics;
 extern SenseShift::SenseShift App;
 SenseShift::SenseShift* app = &App;
 
-static const size_t bhLayoutSize = BH_LAYOUT_TACTOSY2_SIZE;
-static const Position_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTOSY2;
+static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTOSY2_SIZE;
+static const Position bhLayout[bhLayoutSize] = BH_LAYOUT_TACTOSY2;
 
 void setupMode()
 {
     // Configure PWM pins to their positions on the forearm
-    auto forearmOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
+    auto forearmOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
       // clang-format off
       { new ActuatorPWM(32), new ActuatorPWM(33), new ActuatorPWM(25) },
       { new ActuatorPWM(26), new ActuatorPWM(27), new ActuatorPWM(14) },
diff --git a/firmware/mode_configs/bhaptics/tactosyf.cpp b/firmware/mode_configs/bhaptics/tactosyf.cpp
index 0dd2733f..b7667f0f 100644
--- a/firmware/mode_configs/bhaptics/tactosyf.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyf.cpp
@@ -25,13 +25,13 @@ using namespace SenseShift::Body::Haptics;
 extern SenseShift::SenseShift App;
 SenseShift::SenseShift* app = &App;
 
-static const size_t bhLayoutSize = BH_LAYOUT_TACTOSYF_SIZE;
-static const Position_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTOSYF;
+static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTOSYF_SIZE;
+static const Position bhLayout[bhLayoutSize] = BH_LAYOUT_TACTOSYF;
 
 void setupMode()
 {
     // Configure PWM pins to their positions on the feet
-    auto footOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
+    auto footOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
       // clang-format off
       { new ActuatorPWM(32) },
       { new ActuatorPWM(33) },
diff --git a/firmware/mode_configs/bhaptics/tactosyh.cpp b/firmware/mode_configs/bhaptics/tactosyh.cpp
index ae3de9da..50b46ec9 100644
--- a/firmware/mode_configs/bhaptics/tactosyh.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyh.cpp
@@ -25,13 +25,13 @@ using namespace SenseShift::Body::Haptics;
 extern SenseShift::SenseShift App;
 SenseShift::SenseShift* app = &App;
 
-static const size_t bhLayoutSize = BH_LAYOUT_TACTOSYH_SIZE;
-static const Position_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTOSYH;
+static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTOSYH_SIZE;
+static const Position bhLayout[bhLayoutSize] = BH_LAYOUT_TACTOSYH;
 
 void setupMode()
 {
     // Configure PWM pins to their positions on the hands
-    auto handOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
+    auto handOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
       // clang-format off
       { new ActuatorPWM(32) },
       { new ActuatorPWM(33) },
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
index 88b46536..62fc1e45 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
@@ -25,23 +25,23 @@ using namespace SenseShift::Body::Haptics;
 extern SenseShift::SenseShift App;
 SenseShift::SenseShift* app = &App;
 
-static const size_t bhLayoutSize = BH_LAYOUT_TACTSUITX16_SIZE;
-static const OutputLayout_t bhLayout[BH_LAYOUT_TACTSUITX16_SIZE] = BH_LAYOUT_TACTSUITX16;
+static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTSUITX16_SIZE;
+static const OutputLayout bhLayout[BH_LAYOUT_TACTSUITX16_SIZE] = BH_LAYOUT_TACTSUITX16;
 
 // Ouput indices, responsible for x40 => x16 grouping
-static const size_t layoutGroupsSize = BH_LAYOUT_TACTSUITX16_GROUPS_SIZE;
+static constexpr size_t layoutGroupsSize = BH_LAYOUT_TACTSUITX16_GROUPS_SIZE;
 static const uint8_t layoutGroups[layoutGroupsSize] = BH_LAYOUT_TACTSUITX16_GROUPS;
 
 void setupMode()
 {
     // Configure PWM pins to their positions on the vest
-    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
+    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
       // clang-format off
       { new ActuatorPWM(32), new ActuatorPWM(33), new ActuatorPWM(25), new ActuatorPWM(26) },
       { new ActuatorPWM(27), new ActuatorPWM(14), new ActuatorPWM(12), new ActuatorPWM(13) },
       // clang-format on
     });
-    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
+    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
       // clang-format off
       { new ActuatorPWM(19), new ActuatorPWM(18), new ActuatorPWM(5), new ActuatorPWM(17) },
       { new ActuatorPWM(16), new ActuatorPWM(4), new ActuatorPWM(2), new ActuatorPWM(15)  },
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
index 6782ecee..8d3383e8 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
@@ -25,11 +25,11 @@ using namespace SenseShift::Body::Haptics;
 extern SenseShift::SenseShift App;
 SenseShift::SenseShift* app = &App;
 
-static const size_t bhLayoutSize = BH_LAYOUT_TACTSUITX16_SIZE;
-static const OutputLayout_t bhLayout[BH_LAYOUT_TACTSUITX16_SIZE] = BH_LAYOUT_TACTSUITX16;
+static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTSUITX16_SIZE;
+static const OutputLayout bhLayout[BH_LAYOUT_TACTSUITX16_SIZE] = BH_LAYOUT_TACTSUITX16;
 
 // Ouput indices, responsible for x40 => x16 grouping
-static const size_t layoutGroupsSize = BH_LAYOUT_TACTSUITX16_GROUPS_SIZE;
+static constexpr size_t layoutGroupsSize = BH_LAYOUT_TACTSUITX16_GROUPS_SIZE;
 static const uint8_t layoutGroups[layoutGroupsSize] = BH_LAYOUT_TACTSUITX16_GROUPS;
 
 void setupMode()
@@ -40,13 +40,13 @@ void setupMode()
     pwm->setPWMFreq(PWM_FREQUENCY);
 
     // Assign the pins on the configured PCA9685 to positions on the vest
-    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
+    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
       // clang-format off
       { new ActuatorPCA9685(pwm, 0), new ActuatorPCA9685(pwm, 1), new ActuatorPCA9685(pwm, 2), new ActuatorPCA9685(pwm, 3) },
       { new ActuatorPCA9685(pwm, 4), new ActuatorPCA9685(pwm, 5), new ActuatorPCA9685(pwm, 6), new ActuatorPCA9685(pwm, 7) },
       // clang-format on
     });
-    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
+    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
       // clang-format off
       { new ActuatorPCA9685(pwm, 8),  new ActuatorPCA9685(pwm, 9),  new ActuatorPCA9685(pwm, 10), new ActuatorPCA9685(pwm, 11) },
       { new ActuatorPCA9685(pwm, 12), new ActuatorPCA9685(pwm, 13), new ActuatorPCA9685(pwm, 14), new ActuatorPCA9685(pwm, 15) },
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
index 318e6ef6..13fce314 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
@@ -26,8 +26,8 @@ using namespace SenseShift::Body::Haptics;
 extern SenseShift::SenseShift App;
 SenseShift::SenseShift* app = &App;
 
-static const size_t bhLayoutSize = BH_LAYOUT_TACTSUITX40_SIZE;
-static const OutputLayout_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTSUITX40;
+static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTSUITX40_SIZE;
+static const OutputLayout bhLayout[bhLayoutSize] = BH_LAYOUT_TACTSUITX40;
 
 void setupMode()
 {
@@ -42,7 +42,7 @@ void setupMode()
 
     // Assign the pins on the configured PCA9685s and PWM pins to locations on the
     // vest
-    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
+    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
       // clang-format off
       { new ActuatorPCA9685(pwm0, 0),  new ActuatorPCA9685(pwm0, 1),  new ActuatorPCA9685(pwm0, 2),  new ActuatorPCA9685(pwm0, 3)  },
       { new ActuatorPCA9685(pwm0, 4),  new ActuatorPCA9685(pwm0, 5),  new ActuatorPCA9685(pwm0, 6),  new ActuatorPCA9685(pwm0, 7)  },
@@ -51,7 +51,7 @@ void setupMode()
       { new ActuatorPWM(32),           new ActuatorPWM(33),           new ActuatorPWM(25),           new ActuatorPWM(26)           },
       // clang-format on
     });
-    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
+    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
       // clang-format off
       { new ActuatorPCA9685(pwm1, 0),  new ActuatorPCA9685(pwm1, 1),  new ActuatorPCA9685(pwm1, 2),  new ActuatorPCA9685(pwm1, 3)  },
       { new ActuatorPCA9685(pwm1, 4),  new ActuatorPCA9685(pwm1, 5),  new ActuatorPCA9685(pwm1, 6),  new ActuatorPCA9685(pwm1, 7)  },
diff --git a/firmware/mode_configs/bhaptics/tactvisor.cpp b/firmware/mode_configs/bhaptics/tactvisor.cpp
index 758ab9aa..f7e7ab44 100644
--- a/firmware/mode_configs/bhaptics/tactvisor.cpp
+++ b/firmware/mode_configs/bhaptics/tactvisor.cpp
@@ -25,13 +25,13 @@ using namespace SenseShift::Body::Haptics;
 extern SenseShift::SenseShift App;
 SenseShift::SenseShift* app = &App;
 
-static const size_t bhLayoutSize = BH_LAYOUT_TACTVISOR_SIZE;
-static const Position_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTVISOR;
+static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTVISOR_SIZE;
+static const Position bhLayout[bhLayoutSize] = BH_LAYOUT_TACTVISOR;
 
 void setupMode()
 {
     // Configure PWM pins to their positions on the face
-    auto faceOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
+    auto faceOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
       // clang-format off
       { new ActuatorPWM(32), new ActuatorPWM(33), new ActuatorPWM(25), new ActuatorPWM(26) },
       // clang-format on
diff --git a/lib/arduino/senseshift/arduino/components/serial_plotter.cpp b/lib/arduino/senseshift/arduino/components/serial_plotter.cpp
index bdd1fb38..8cd48978 100644
--- a/lib/arduino/senseshift/arduino/components/serial_plotter.cpp
+++ b/lib/arduino/senseshift/arduino/components/serial_plotter.cpp
@@ -2,7 +2,7 @@
 
 namespace SenseShift::Arduino {
     struct PlaneVisitor {
-        const SenseShift::Body::Haptics::Target_t target;
+        const SenseShift::Body::Haptics::Target target;
         HardwareSerial* serial;
 
         void operator()(const SenseShift::Body::Haptics::VibroPlane* plane) const
diff --git a/lib/arduino/senseshift/arduino/input/sensor/digital.hpp b/lib/arduino/senseshift/arduino/input/sensor/digital.hpp
index 31b77bbf..3cb68b3f 100644
--- a/lib/arduino/senseshift/arduino/input/sensor/digital.hpp
+++ b/lib/arduino/senseshift/arduino/input/sensor/digital.hpp
@@ -5,7 +5,7 @@
 #include <Arduino.h>
 
 namespace SenseShift::Arduino::Input {
-    typedef ::SenseShift::Input::ISimpleSensor<bool> IDigitalSensor;
+    using IDigitalSensor = ::SenseShift::Input::ISimpleSensor<bool>;
 
     template<bool invert = false>
     class DigitalSensor : public IDigitalSensor {
diff --git a/lib/battery/senseshift/battery.hpp b/lib/battery/senseshift/battery.hpp
index 10e51896..a89455fb 100644
--- a/lib/battery/senseshift/battery.hpp
+++ b/lib/battery/senseshift/battery.hpp
@@ -5,13 +5,13 @@
 #include <cstdint>
 
 namespace SenseShift::Battery {
-    typedef struct BatteryState {
+    struct BatteryState {
         uint8_t level;
-    } BatteryState_t;
+    };
 
     class BatteryLevelEvent : public IEvent {
       public:
-        const BatteryState_t& state;
-        BatteryLevelEvent(const BatteryState_t& state) : IEvent(OH_EVENT_BATTERY_LEVEL), state(state){};
+        const BatteryState& state;
+        BatteryLevelEvent(const BatteryState& state) : IEvent(OH_EVENT_BATTERY_LEVEL), state(state){};
     };
 } // namespace SenseShift::Battery
diff --git a/lib/battery/senseshift/battery/sensor.hpp b/lib/battery/senseshift/battery/sensor.hpp
index f2c2968a..01b1ae96 100644
--- a/lib/battery/senseshift/battery/sensor.hpp
+++ b/lib/battery/senseshift/battery/sensor.hpp
@@ -14,7 +14,7 @@ namespace SenseShift::Battery {
     /**
      * Abstract battery sensor
      */
-    typedef ::SenseShift::Input::ISimpleSensor<BatteryState> IBatterySensor;
+    using IBatterySensor = ::SenseShift::Input::ISimpleSensor<BatteryState>;
 
     class NaiveBatterySensor : public IBatterySensor {
       public:
diff --git a/lib/bhaptics/senseshift/bh/devices.hpp b/lib/bhaptics/senseshift/bh/devices.hpp
index b78bbb9f..f4c168de 100644
--- a/lib/bhaptics/senseshift/bh/devices.hpp
+++ b/lib/bhaptics/senseshift/bh/devices.hpp
@@ -7,12 +7,12 @@
 
 #define BH_LAYOUT_TACTSUITX40_SIZE_X 4
 #define BH_LAYOUT_TACTSUITX40_SIZE_Y 5
-#define BH_LAYOUT_TACTSUITX40_MAKE_POINT(x, y)                                                            \
-    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<::SenseShift::Body::Haptics::Coordinate_t>( \
-      x,                                                                                                  \
-      y,                                                                                                  \
-      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTSUITX40_SIZE_X - 1),                      \
-      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTSUITX40_SIZE_Y - 1)                       \
+#define BH_LAYOUT_TACTSUITX40_MAKE_POINT(x, y)                                                          \
+    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<::SenseShift::Body::Haptics::Coordinate>( \
+      x,                                                                                                \
+      y,                                                                                                \
+      (::SenseShift::Body::Haptics::Coordinate)(BH_LAYOUT_TACTSUITX40_SIZE_X - 1),                      \
+      (::SenseShift::Body::Haptics::Coordinate)(BH_LAYOUT_TACTSUITX40_SIZE_Y - 1)                       \
     )
 
 // X * Y for front and back
@@ -20,51 +20,51 @@
 // clang-format off
 #define BH_LAYOUT_TACTSUITX40 {                                                \
     /* Front, left part */                                                     \
-    /*  0 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 0) }, \
-    /*  1 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 0) }, \
-    /*  2 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 1) }, \
-    /*  3 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 1) }, \
-    /*  4 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 2) }, \
-    /*  5 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 2) }, \
-    /*  6 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 3) }, \
-    /*  7 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 3) }, \
-    /*  8 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 4) }, \
-    /*  9 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 4) }, \
+    /*  0 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 0) }, \
+    /*  1 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 0) }, \
+    /*  2 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 1) }, \
+    /*  3 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 1) }, \
+    /*  4 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 2) }, \
+    /*  5 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 2) }, \
+    /*  6 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 3) }, \
+    /*  7 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 3) }, \
+    /*  8 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 4) }, \
+    /*  9 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 4) }, \
                                                                                \
     /* Back */                                                                 \
-    /* 11 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 0) },  \
-    /* 11 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 0) },  \
-    /* 12 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 1) },  \
-    /* 13 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 1) },  \
-    /* 14 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 2) },  \
-    /* 15 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 2) },  \
-    /* 16 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 3) },  \
-    /* 17 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 3) },  \
-    /* 18 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 4) },  \
-    /* 19 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 4) },  \
+    /* 11 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 0) },  \
+    /* 11 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 0) },  \
+    /* 12 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 1) },  \
+    /* 13 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 1) },  \
+    /* 14 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 2) },  \
+    /* 15 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 2) },  \
+    /* 16 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 3) },  \
+    /* 17 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 3) },  \
+    /* 18 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 4) },  \
+    /* 19 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 4) },  \
                                                                                \
-    /* 20 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 0) },  \
-    /* 21 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 0) },  \
-    /* 22 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 1) },  \
-    /* 23 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 1) },  \
-    /* 24 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 2) },  \
-    /* 25 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 2) },  \
-    /* 26 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 3) },  \
-    /* 27 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 3) },  \
-    /* 28 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 4) },  \
-    /* 29 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 4) },  \
+    /* 20 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 0) },  \
+    /* 21 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 0) },  \
+    /* 22 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 1) },  \
+    /* 23 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 1) },  \
+    /* 24 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 2) },  \
+    /* 25 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 2) },  \
+    /* 26 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 3) },  \
+    /* 27 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 3) },  \
+    /* 28 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 4) },  \
+    /* 29 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 4) },  \
                                                                                \
     /* Front, again... Now right part */                                       \
-    /* 30 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 0) }, \
-    /* 31 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 0) }, \
-    /* 32 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 1) }, \
-    /* 33 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 1) }, \
-    /* 34 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 2) }, \
-    /* 35 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 2) }, \
-    /* 36 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 3) }, \
-    /* 37 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 3) }, \
-    /* 38 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 4) }, \
-    /* 39 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 4) }  \
+    /* 30 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 0) }, \
+    /* 31 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 0) }, \
+    /* 32 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 1) }, \
+    /* 33 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 1) }, \
+    /* 34 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 2) }, \
+    /* 35 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 2) }, \
+    /* 36 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 3) }, \
+    /* 37 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 3) }, \
+    /* 38 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 4) }, \
+    /* 39 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 4) }  \
 }
 // clang-format on
 
@@ -74,12 +74,12 @@
 
 #define BH_LAYOUT_TACTSUITX16_SIZE_X 4
 #define BH_LAYOUT_TACTSUITX16_SIZE_Y 2
-#define BH_LAYOUT_TACTSUITX16_MAKE_POINT(x, y)                                                            \
-    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<::SenseShift::Body::Haptics::Coordinate_t>( \
-      x,                                                                                                  \
-      y,                                                                                                  \
-      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTSUITX16_SIZE_X - 1),                      \
-      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTSUITX16_SIZE_Y - 1)                       \
+#define BH_LAYOUT_TACTSUITX16_MAKE_POINT(x, y)                                                          \
+    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<::SenseShift::Body::Haptics::Coordinate>( \
+      x,                                                                                                \
+      y,                                                                                                \
+      (::SenseShift::Body::Haptics::Coordinate)(BH_LAYOUT_TACTSUITX16_SIZE_X - 1),                      \
+      (::SenseShift::Body::Haptics::Coordinate)(BH_LAYOUT_TACTSUITX16_SIZE_Y - 1)                       \
     )
 
 // X16 suit uses the same packets structure as x40 suit and performs motor grouping in firmware
@@ -87,55 +87,55 @@
 // clang-format off
 #define BH_LAYOUT_TACTSUITX16 {                                                         \
     /* Front, left part */                                                              \
-    /*  0 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0) }, /*  0 */ \
-    /*  1 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0) }, /*  1 */ \
-    /*  2 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0) }, /*  4 */ \
-    /*  3 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0) }, /*  5 */ \
+    /*  0 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0) }, /*  0 */ \
+    /*  1 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0) }, /*  1 */ \
+    /*  2 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0) }, /*  4 */ \
+    /*  3 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0) }, /*  5 */ \
                                                                                         \
-    /*  4 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) }, /*  8 */ \
-    /*  5 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) }, /*  9 */ \
-    /*  6 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) }, /* 12 */ \
-    /*  7 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) }, /* 13 */ \
-    /*  8 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) }, /* 16 */ \
-    /*  9 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) }, /* 17 */ \
+    /*  4 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) }, /*  8 */ \
+    /*  5 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) }, /*  9 */ \
+    /*  6 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) }, /* 12 */ \
+    /*  7 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) }, /* 13 */ \
+    /*  8 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) }, /* 16 */ \
+    /*  9 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) }, /* 17 */ \
                                                                                         \
     /* Back */                                                                          \
-    /* 10 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0) }, /*  0 */  \
-    /* 11 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0) }, /*  1 */  \
-    /* 12 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0) }, /*  4 */  \
-    /* 13 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0) }, /*  5 */  \
+    /* 10 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0) }, /*  0 */  \
+    /* 11 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0) }, /*  1 */  \
+    /* 12 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0) }, /*  4 */  \
+    /* 13 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0) }, /*  5 */  \
                                                                                         \
-    /* 14 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) }, /*  8 */  \
-    /* 15 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) }, /*  9 */  \
-    /* 16 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) }, /* 12 */  \
-    /* 17 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) }, /* 13 */  \
-    /* 18 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) }, /* 16 */  \
-    /* 19 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) }, /* 17 */  \
+    /* 14 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) }, /*  8 */  \
+    /* 15 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) }, /*  9 */  \
+    /* 16 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) }, /* 12 */  \
+    /* 17 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) }, /* 13 */  \
+    /* 18 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) }, /* 16 */  \
+    /* 19 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) }, /* 17 */  \
                                                                                         \
-    /* 20 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0) }, /*  2 */  \
-    /* 21 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0) }, /*  3 */  \
-    /* 22 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0) }, /*  4 */  \
-    /* 23 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0) }, /*  7 */  \
+    /* 20 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0) }, /*  2 */  \
+    /* 21 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0) }, /*  3 */  \
+    /* 22 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0) }, /*  4 */  \
+    /* 23 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0) }, /*  7 */  \
                                                                                         \
-    /* 24 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) }, /* 10 */  \
-    /* 25 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) }, /* 11 */  \
-    /* 26 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) }, /* 14 */  \
-    /* 27 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) }, /* 15 */  \
-    /* 28 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) }, /* 18 */  \
-    /* 29 */ { Target_t::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) }, /* 19 */  \
+    /* 24 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) }, /* 10 */  \
+    /* 25 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) }, /* 11 */  \
+    /* 26 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) }, /* 14 */  \
+    /* 27 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) }, /* 15 */  \
+    /* 28 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) }, /* 18 */  \
+    /* 29 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) }, /* 19 */  \
                                                                                         \
     /* Front, again... Now right part */                                                \
-    /* 30 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0) }, /*  2 */ \
-    /* 31 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0) }, /*  3 */ \
-    /* 32 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0) }, /*  4 */ \
-    /* 33 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0) }, /*  7 */ \
+    /* 30 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0) }, /*  2 */ \
+    /* 31 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0) }, /*  3 */ \
+    /* 32 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0) }, /*  4 */ \
+    /* 33 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0) }, /*  7 */ \
                                                                                         \
-    /* 34 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) }, /* 10 */ \
-    /* 35 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) }, /* 11 */ \
-    /* 36 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) }, /* 14 */ \
-    /* 37 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) }, /* 15 */ \
-    /* 38 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) }, /* 18 */ \
-    /* 39 */ { Target_t::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) }, /* 19 */ \
+    /* 34 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) }, /* 10 */ \
+    /* 35 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) }, /* 11 */ \
+    /* 36 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) }, /* 14 */ \
+    /* 37 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) }, /* 15 */ \
+    /* 38 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) }, /* 18 */ \
+    /* 39 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) }, /* 19 */ \
 }
 // clang-format on
 
@@ -152,12 +152,12 @@
 
 #define BH_LAYOUT_TACTAL_SIZE_X 6
 #define BH_LAYOUT_TACTAL_SIZE_Y 1
-#define BH_LAYOUT_TACTAL_MAKE_POINT(x, y)                                                                 \
-    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<::SenseShift::Body::Haptics::Coordinate_t>( \
-      x,                                                                                                  \
-      y,                                                                                                  \
-      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTAL_SIZE_X - 1),                           \
-      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTAL_SIZE_Y - 1)                            \
+#define BH_LAYOUT_TACTAL_MAKE_POINT(x, y)                                                               \
+    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<::SenseShift::Body::Haptics::Coordinate>( \
+      x,                                                                                                \
+      y,                                                                                                \
+      (::SenseShift::Body::Haptics::Coordinate)(BH_LAYOUT_TACTAL_SIZE_X - 1),                           \
+      (::SenseShift::Body::Haptics::Coordinate)(BH_LAYOUT_TACTAL_SIZE_Y - 1)                            \
     )
 
 #define BH_LAYOUT_TACTAL_SIZE (BH_LAYOUT_TACTAL_SIZE_X * BH_LAYOUT_TACTAL_SIZE_Y)
@@ -178,12 +178,12 @@
 
 #define BH_LAYOUT_TACTVISOR_SIZE_X 4
 #define BH_LAYOUT_TACTVISOR_SIZE_Y 1
-#define BH_LAYOUT_TACTVISOR_MAKE_POINT(x, y)                                                              \
-    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<::SenseShift::Body::Haptics::Coordinate_t>( \
-      x,                                                                                                  \
-      y,                                                                                                  \
-      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTVISOR_SIZE_X - 1),                        \
-      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTVISOR_SIZE_Y - 1)                         \
+#define BH_LAYOUT_TACTVISOR_MAKE_POINT(x, y)                                                            \
+    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<::SenseShift::Body::Haptics::Coordinate>( \
+      x,                                                                                                \
+      y,                                                                                                \
+      (::SenseShift::Body::Haptics::Coordinate)(BH_LAYOUT_TACTVISOR_SIZE_X - 1),                        \
+      (::SenseShift::Body::Haptics::Coordinate)(BH_LAYOUT_TACTVISOR_SIZE_Y - 1)                         \
     )
 
 #define BH_LAYOUT_TACTVISOR_SIZE (BH_LAYOUT_TACTVISOR_SIZE_X * BH_LAYOUT_TACTVISOR_SIZE_Y)
@@ -202,12 +202,12 @@
 
 #define BH_LAYOUT_TACTOSY2_SIZE_X 3
 #define BH_LAYOUT_TACTOSY2_SIZE_Y 2
-#define BH_LAYOUT_TACTOSY2_MAKE_POINT(x, y)                                                               \
-    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<::SenseShift::Body::Haptics::Coordinate_t>( \
-      x,                                                                                                  \
-      y,                                                                                                  \
-      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTOSY2_SIZE_X - 1),                         \
-      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTOSY2_SIZE_Y - 1)                          \
+#define BH_LAYOUT_TACTOSY2_MAKE_POINT(x, y)                                                             \
+    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<::SenseShift::Body::Haptics::Coordinate>( \
+      x,                                                                                                \
+      y,                                                                                                \
+      (::SenseShift::Body::Haptics::Coordinate)(BH_LAYOUT_TACTOSY2_SIZE_X - 1),                         \
+      (::SenseShift::Body::Haptics::Coordinate)(BH_LAYOUT_TACTOSY2_SIZE_Y - 1)                          \
     )
 
 #define BH_LAYOUT_TACTOSY2_SIZE (BH_LAYOUT_TACTOSY2_SIZE_X * BH_LAYOUT_TACTOSY2_SIZE_Y)
@@ -224,12 +224,12 @@
 
 #define BH_LAYOUT_TACTOSYH_SIZE_X 1
 #define BH_LAYOUT_TACTOSYH_SIZE_Y 3
-#define BH_LAYOUT_TACTOSYH_MAKE_POINT(x, y)                                                               \
-    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<::SenseShift::Body::Haptics::Coordinate_t>( \
-      x,                                                                                                  \
-      y,                                                                                                  \
-      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTOSYH_SIZE_X - 1),                         \
-      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTOSYH_SIZE_Y - 1)                          \
+#define BH_LAYOUT_TACTOSYH_MAKE_POINT(x, y)                                                             \
+    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<::SenseShift::Body::Haptics::Coordinate>( \
+      x,                                                                                                \
+      y,                                                                                                \
+      (::SenseShift::Body::Haptics::Coordinate)(BH_LAYOUT_TACTOSYH_SIZE_X - 1),                         \
+      (::SenseShift::Body::Haptics::Coordinate)(BH_LAYOUT_TACTOSYH_SIZE_Y - 1)                          \
     )
 
 #define BH_LAYOUT_TACTOSYH_SIZE (BH_LAYOUT_TACTOSYH_SIZE_X * BH_LAYOUT_TACTOSYH_SIZE_Y)
@@ -243,12 +243,12 @@
 
 #define BH_LAYOUT_TACTOSYF_SIZE_X 1
 #define BH_LAYOUT_TACTOSYF_SIZE_Y 3
-#define BH_LAYOUT_TACTOSYF_MAKE_POINT(x, y)                                                               \
-    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<::SenseShift::Body::Haptics::Coordinate_t>( \
-      x,                                                                                                  \
-      y,                                                                                                  \
-      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTOSYF_SIZE_X - 1),                         \
-      (::SenseShift::Body::Haptics::Coordinate_t)(BH_LAYOUT_TACTOSYF_SIZE_Y - 1)                          \
+#define BH_LAYOUT_TACTOSYF_MAKE_POINT(x, y)                                                             \
+    ::SenseShift::Body::Haptics::PlaneMapper_Margin::mapPoint<::SenseShift::Body::Haptics::Coordinate>( \
+      x,                                                                                                \
+      y,                                                                                                \
+      (::SenseShift::Body::Haptics::Coordinate)(BH_LAYOUT_TACTOSYF_SIZE_X - 1),                         \
+      (::SenseShift::Body::Haptics::Coordinate)(BH_LAYOUT_TACTOSYF_SIZE_Y - 1)                          \
     )
 
 #define BH_LAYOUT_TACTOSYF_SIZE (BH_LAYOUT_TACTOSYF_SIZE_X * BH_LAYOUT_TACTOSYF_SIZE_Y)
@@ -264,21 +264,21 @@
 #define BH_LAYOUT_TACTGLOVE_SIZE 6
 // TactGlove (Left) motor positions
 #define BH_LAYOUT_TACTGLOVE_LEFT {                      \
-    { Target_t::HandLeftThumb,  FINGERTIP_POSITION },   \
-    { Target_t::HandLeftIndex,  FINGERTIP_POSITION },   \
-    { Target_t::HandLeftMiddle, FINGERTIP_POSITION },   \
-    { Target_t::HandLeftRing,   FINGERTIP_POSITION },   \
-    { Target_t::HandLeftLittle, FINGERTIP_POSITION },   \
-    { Target_t::HandLeftDorsal, WRIST_MOTOR_POSITION }, \
+    { Target::HandLeftThumb,  FINGERTIP_POSITION },   \
+    { Target::HandLeftIndex,  FINGERTIP_POSITION },   \
+    { Target::HandLeftMiddle, FINGERTIP_POSITION },   \
+    { Target::HandLeftRing,   FINGERTIP_POSITION },   \
+    { Target::HandLeftLittle, FINGERTIP_POSITION },   \
+    { Target::HandLeftDorsal, WRIST_MOTOR_POSITION }, \
 }
 // TactGlove (Right) motor positions
 #define BH_LAYOUT_TACTGLOVE_RIGHT {                     \
-    { Target_t::HandRightThumb,  FINGERTIP_POSITION },  \
-    { Target_t::HandRightIndex,  FINGERTIP_POSITION },  \
-    { Target_t::HandRightMiddle, FINGERTIP_POSITION },  \
-    { Target_t::HandRightRing,   FINGERTIP_POSITION },  \
-    { Target_t::HandRightLittle, FINGERTIP_POSITION },  \
-    { Target_t::HandRightDorsal, WRIST_MOTOR_POSITION } \
+    { Target::HandRightThumb,  FINGERTIP_POSITION },  \
+    { Target::HandRightIndex,  FINGERTIP_POSITION },  \
+    { Target::HandRightMiddle, FINGERTIP_POSITION },  \
+    { Target::HandRightRing,   FINGERTIP_POSITION },  \
+    { Target::HandRightLittle, FINGERTIP_POSITION },  \
+    { Target::HandRightDorsal, WRIST_MOTOR_POSITION } \
 }
 // clang-format on
 
@@ -288,27 +288,27 @@ namespace SenseShift::BH {
     using namespace ::SenseShift::Body::Hands::Haptics;
     using namespace ::SenseShift::Body::Haptics;
 
-    using HandSide_t = ::SenseShift::Body::Hands::HandSide_t;
+    using HandSide = ::SenseShift::Body::Hands::HandSide;
 
-    typedef std::tuple<Target_t, Position_t> OutputLayout_t;
+    using OutputLayout = std::tuple<Target, Position>;
 
     // TactGlove Wrist motor position
-    static constexpr const Position_t WRIST_MOTOR_POSITION(127, 191);
-    static constexpr const OutputLayout_t TactGloveLeftLayout[] = BH_LAYOUT_TACTGLOVE_LEFT;
-    static constexpr const OutputLayout_t TactGloveRightLayout[] = BH_LAYOUT_TACTGLOVE_RIGHT;
+    static constexpr const Position WRIST_MOTOR_POSITION(127, 191);
+    static constexpr const OutputLayout TactGloveLeftLayout[] = BH_LAYOUT_TACTGLOVE_LEFT;
+    static constexpr const OutputLayout TactGloveRightLayout[] = BH_LAYOUT_TACTGLOVE_RIGHT;
 
     inline void addTactGloveActuators(
       HapticBody* hapticBody,
-      const HandSide_t side,
-      VibroPlane::Actuator_t* const thumbActuator,
-      VibroPlane::Actuator_t* const indexActuator,
-      VibroPlane::Actuator_t* const middleActuator,
-      VibroPlane::Actuator_t* const ringActuator,
-      VibroPlane::Actuator_t* const littleActuator,
-      VibroPlane::Actuator_t* const wristActuator
+      const HandSide side,
+      VibroPlane::Actuator* const thumbActuator,
+      VibroPlane::Actuator* const indexActuator,
+      VibroPlane::Actuator* const middleActuator,
+      VibroPlane::Actuator* const ringActuator,
+      VibroPlane::Actuator* const littleActuator,
+      VibroPlane::Actuator* const wristActuator
     )
     {
-        const OutputLayout_t(&layout)[6] = (side == HandSide_t::Left) ? TactGloveLeftLayout : TactGloveRightLayout;
+        const OutputLayout(&layout)[6] = (side == HandSide::Left) ? TactGloveLeftLayout : TactGloveRightLayout;
 
         if (thumbActuator != nullptr) {
             hapticBody->addTarget(
diff --git a/lib/bhaptics/senseshift/bh/encoding.hpp b/lib/bhaptics/senseshift/bh/encoding.hpp
index 3b75f8f0..273b98c7 100644
--- a/lib/bhaptics/senseshift/bh/encoding.hpp
+++ b/lib/bhaptics/senseshift/bh/encoding.hpp
@@ -6,23 +6,23 @@
 namespace SenseShift::BH {
     class Decoder {
       public:
-        using VibroEffectData_t = ::SenseShift::Body::Haptics::VibroEffectData_t;
-        using EffectData_t = ::SenseShift::Body::Haptics::EffectData_t;
+        using VibroEffectData = ::SenseShift::Body::Haptics::VibroEffectData;
+        using EffectData = ::SenseShift::Body::Haptics::EffectData;
 
-        using Effect_t = ::SenseShift::Body::Haptics::Effect_t;
-        using Target_t = ::SenseShift::Body::Haptics::Target_t;
-        using Position_t = ::SenseShift::Body::Haptics::Position_t;
+        using Effect = ::SenseShift::Body::Haptics::Effect;
+        using Target = ::SenseShift::Body::Haptics::Target;
+        using Position = ::SenseShift::Body::Haptics::Position;
 
-        using HapticBody_t = ::SenseShift::Body::Haptics::HapticBody;
+        using HapticBody = ::SenseShift::Body::Haptics::HapticBody;
 
-        typedef std::tuple<Target_t, Position_t> OutputLayout_t;
+        using OutputLayout = std::tuple<Target, Position>;
 
-        static const size_t VEST_LAYOUT_SIZE = 40;
-        static const size_t VEST_PAYLOAD_SIZE = 20;
+        static constexpr size_t VEST_LAYOUT_SIZE = 40;
+        static constexpr size_t VEST_PAYLOAD_SIZE = 20;
 
         template<size_t N>
         static void applyPlain(
-          HapticBody_t* output, const uint8_t (&value)[N], const OutputLayout_t (&layout)[N], const Effect_t effect
+          HapticBody* output, const uint8_t (&value)[N], const OutputLayout (&layout)[N], const Effect effect
         )
         {
             for (size_t i = 0; i < N; i++) {
@@ -40,7 +40,7 @@ namespace SenseShift::BH {
 
         template<size_t N>
         static void
-          applyPlain(HapticBody_t* output, std::string& value, const OutputLayout_t (&layout)[N], const Effect_t effect)
+          applyPlain(HapticBody* output, std::string& value, const OutputLayout (&layout)[N], const Effect effect)
         {
             std::uint8_t buf[N];
             std::size_t copyLength = std::min(value.size(), sizeof(buf));
@@ -54,11 +54,11 @@ namespace SenseShift::BH {
          */
         template<size_t N>
         static void applyPlain(
-          HapticBody_t* output,
+          HapticBody* output,
           const uint8_t (&value)[N],
-          const Position_t (&layout)[N],
-          const Effect_t effect,
-          const Target_t target
+          const Position (&layout)[N],
+          const Effect effect,
+          const Target target
         )
         {
             for (size_t i = 0; i < N; i++) {
@@ -76,11 +76,7 @@ namespace SenseShift::BH {
 
         template<size_t N>
         static void applyPlain(
-          HapticBody_t* output,
-          std::string& value,
-          const Position_t (&layout)[N],
-          const Effect_t effect,
-          const Target_t target
+          HapticBody* output, std::string& value, const Position (&layout)[N], const Effect effect, const Target target
         )
         {
             std::uint8_t buf[N];
@@ -94,31 +90,28 @@ namespace SenseShift::BH {
          * Apply vest-encoded data to the output.
          */
         static void applyVest(
-          HapticBody_t* output,
-          const uint8_t (&value)[VEST_PAYLOAD_SIZE],
-          const OutputLayout_t (&layout)[VEST_LAYOUT_SIZE]
+          HapticBody* output, const uint8_t (&value)[VEST_PAYLOAD_SIZE], const OutputLayout (&layout)[VEST_LAYOUT_SIZE]
         )
         {
             for (size_t i = 0; i < VEST_PAYLOAD_SIZE; i++) {
                 uint8_t byte = value[i];
                 uint actIndex = i * 2;
                 output->effect({
-                  .effect = Effect_t::Vibro,
+                  .effect = Effect::Vibro,
                   .target = std::get<0>(layout[actIndex]),
                   .position = std::get<1>(layout[actIndex]),
-                  .data = effectDataFromByte(Effect_t::Vibro, ((byte >> 4) & 0xf), 15),
+                  .data = effectDataFromByte(Effect::Vibro, ((byte >> 4) & 0xf), 15),
                 });
                 output->effect({
-                  .effect = Effect_t::Vibro,
+                  .effect = Effect::Vibro,
                   .target = std::get<0>(layout[actIndex + 1]),
                   .position = std::get<1>(layout[actIndex + 1]),
-                  .data = effectDataFromByte(Effect_t::Vibro, (byte & 0xf), 15),
+                  .data = effectDataFromByte(Effect::Vibro, (byte & 0xf), 15),
                 });
             }
         }
 
-        static void
-          applyVest(HapticBody_t* output, std::string& value, const OutputLayout_t (&layout)[VEST_LAYOUT_SIZE])
+        static void applyVest(HapticBody* output, std::string& value, const OutputLayout (&layout)[VEST_LAYOUT_SIZE])
         {
             std::uint8_t buf[VEST_PAYLOAD_SIZE];
             std::size_t copyLength = std::min(value.size(), sizeof(buf));
@@ -132,9 +125,9 @@ namespace SenseShift::BH {
          */
         template<size_t N>
         static void applyVestGrouped(
-          HapticBody_t* output,
+          HapticBody* output,
           const uint8_t (&value)[VEST_PAYLOAD_SIZE],
-          const OutputLayout_t (&layout)[VEST_LAYOUT_SIZE],
+          const OutputLayout (&layout)[VEST_LAYOUT_SIZE],
           const uint8_t (&layoutGroups)[N]
         )
         {
@@ -179,19 +172,19 @@ namespace SenseShift::BH {
                 const auto position = std::get<1>(layout[i]);
 
                 output->effect({
-                  .effect = Effect_t::Vibro,
+                  .effect = Effect::Vibro,
                   .target = target,
                   .position = position,
-                  .data = effectDataFromByte(Effect_t::Vibro, result[i], 15),
+                  .data = effectDataFromByte(Effect::Vibro, result[i], 15),
                 });
             }
         }
 
         template<size_t N>
         static void applyVestGrouped(
-          HapticBody_t* output,
+          HapticBody* output,
           std::string& value,
-          const OutputLayout_t (&layout)[VEST_LAYOUT_SIZE],
+          const OutputLayout (&layout)[VEST_LAYOUT_SIZE],
           const uint8_t (&layoutGroups)[N]
         )
         {
@@ -203,15 +196,15 @@ namespace SenseShift::BH {
         }
 
       private:
-        static const EffectData_t
-          effectDataFromByte(const Effect_t effect, const uint8_t byte, const uint8_t maxValue = 100)
+        static const EffectData
+          effectDataFromByte(const Effect effect, const uint8_t byte, const uint8_t maxValue = 100)
         {
             switch (effect) {
-                case Effect_t::Vibro:
-                    return VibroEffectData_t(::SenseShift::simpleMap<VibroEffectData_t::Intensity_t>(
+                case Effect::Vibro:
+                    return VibroEffectData(::SenseShift::simpleMap<VibroEffectData::Intensity>(
                       byte,
                       maxValue,
-                      VibroEffectData_t::INTENSITY_MAX
+                      VibroEffectData::INTENSITY_MAX
                     ));
                 default:
                     throw std::runtime_error("Unknown effect");
diff --git a/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp b/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
index 48e11876..db642a43 100644
--- a/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
+++ b/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
@@ -96,10 +96,10 @@ namespace SenseShift::BH::BLE {
 
     class MotorCharCallbacks : public BLECharacteristicCallbacks {
       private:
-        Connection::MotorHandler_t motorTransformer;
+        Connection::MotorHandler motorTransformer;
 
       public:
-        MotorCharCallbacks(Connection::MotorHandler_t motorTransformer) : motorTransformer(motorTransformer) {}
+        MotorCharCallbacks(Connection::MotorHandler motorTransformer) : motorTransformer(motorTransformer) {}
 
         void onWrite(BLECharacteristic* pCharacteristic) override
         {
@@ -169,9 +169,9 @@ namespace SenseShift::BH::BLE {
               BH_BLE_SERVICE_MOTOR_CHAR_SERIAL_KEY_UUID,
               PROPERTY_READ | PROPERTY_WRITE
             );
-            uint8_t serialNumber[ConnectionConfig_t::SN_LENGTH];
-            memcpy(serialNumber, this->config.serialNumber, ConnectionConfig_t::SN_LENGTH);
-            serialNumberChar->setValue(serialNumber, ConnectionConfig_t::SN_LENGTH);
+            uint8_t serialNumber[ConnectionConfig::SN_LENGTH];
+            memcpy(serialNumber, this->config.serialNumber, ConnectionConfig::SN_LENGTH);
+            serialNumberChar->setValue(serialNumber, ConnectionConfig::SN_LENGTH);
             serialNumberChar->setCallbacks(new LogOutputCharCallbacks());
         }
 
diff --git a/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp b/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
index ea5b1ae6..a0265f74 100644
--- a/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
+++ b/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
@@ -16,13 +16,13 @@
 #endif
 
 namespace SenseShift::BH::BLE {
-    typedef struct ConnectionConfig {
+    struct ConnectionConfig {
         static constexpr size_t SN_LENGTH = 10;
 
         std::string deviceName;
         uint16_t appearance;
         uint8_t serialNumber[SN_LENGTH];
-    } ConnectionConfig_t;
+    };
 
     class ConnectionCallbacks {
       public:
@@ -32,9 +32,9 @@ namespace SenseShift::BH::BLE {
 
     class Connection final : public IEventListener {
       public:
-        typedef std::function<void(std::string&)> MotorHandler_t;
+        using MotorHandler = std::function<void(std::string&)>;
 
-        Connection(const ConnectionConfig_t& config, MotorHandler_t motorHandler, IEventDispatcher* eventDispatcher) :
+        Connection(const ConnectionConfig& config, MotorHandler motorHandler, IEventDispatcher* eventDispatcher) :
           config(config), motorHandler(motorHandler), eventDispatcher(eventDispatcher)
         {
             this->eventDispatcher->addEventListener(this);
@@ -67,8 +67,8 @@ namespace SenseShift::BH::BLE {
         };
 
       private:
-        const ConnectionConfig_t& config;
-        MotorHandler_t motorHandler;
+        const ConnectionConfig& config;
+        MotorHandler motorHandler;
         ::SenseShift::IEventDispatcher* eventDispatcher;
 
         BLEServer* bleServer = nullptr;
diff --git a/lib/freertos/senseshift/freertos/input/sensor.hpp b/lib/freertos/senseshift/freertos/input/sensor.hpp
index cf7a1f45..8209aa3f 100644
--- a/lib/freertos/senseshift/freertos/input/sensor.hpp
+++ b/lib/freertos/senseshift/freertos/input/sensor.hpp
@@ -13,14 +13,14 @@ namespace SenseShift::FreeRTOS::Input {
         friend class Task<SensorUpdateTask>;
 
       private:
-        using Sensor_t = ::SenseShift::ITickable;
+        using Sensor = ::SenseShift::ITickable;
 
       public:
-        SensorUpdateTask(Sensor_t* sensor, std::uint32_t updateDelay, TaskConfig taskConfig) :
+        SensorUpdateTask(Sensor* sensor, std::uint32_t updateDelay, TaskConfig taskConfig) :
           Task<SensorUpdateTask>(taskConfig), updateDelay(updateDelay){};
 
       protected:
-        Sensor_t* sensor;
+        Sensor* sensor;
         std::uint32_t updateDelay;
 
         void run()
@@ -37,10 +37,10 @@ namespace SenseShift::FreeRTOS::Input {
         friend class SensorUpdateTask;
 
       private:
-        using Sensor_t = ::SenseShift::Input::MemoizedSensor<_Tp>;
+        using Sensor = ::SenseShift::Input::MemoizedSensor<_Tp>;
 
       public:
-        TaskedSensor(Sensor_t* sensor, std::uint32_t updateDelay, TaskConfig taskConfig) :
+        TaskedSensor(Sensor* sensor, std::uint32_t updateDelay, TaskConfig taskConfig) :
           SensorUpdateTask(sensor, updateDelay, taskConfig), sensor(sensor){};
 
         void begin() override
@@ -54,6 +54,6 @@ namespace SenseShift::FreeRTOS::Input {
         _Tp getValue() override { return this->sensor->getValue(); };
 
       private:
-        Sensor_t* sensor;
+        Sensor* sensor;
     };
 } // namespace SenseShift::FreeRTOS::Input
diff --git a/lib/hands/hand_interface.hpp b/lib/hands/hand_interface.hpp
index 71249f0c..907a553b 100644
--- a/lib/hands/hand_interface.hpp
+++ b/lib/hands/hand_interface.hpp
@@ -5,23 +5,23 @@
 
 namespace SenseShift::Body {
     namespace Hands {
-        typedef std::uint8_t HandSideIndex_t;
-        typedef enum class HandSide : HandSideIndex_t { Left, Right } HandSide_t;
-        typedef std::uint8_t FingerIndex_t;
-        typedef enum class Finger : FingerIndex_t {
+        using HandSideIndex = std::uint8_t;
+        enum class HandSide : HandSideIndex { Left, Right };
+        using FingerIndex = std::uint8_t;
+        enum class Finger : FingerIndex {
             Thumb,
             Index,
             Middle,
             Ring,
             Little,
-        } Finger_t;
+        };
 
         namespace Haptics {
             /**
              * @brief Helper with position of the haptic device on the fingertip.
              * Distal phalanx of the volar surface of the any finger.
              */
-            static constexpr const ::SenseShift::Body::Haptics::Position_t FINGERTIP_POSITION(127, 16);
+            static constexpr const ::SenseShift::Body::Haptics::Position FINGERTIP_POSITION(127, 16);
         } // namespace Haptics
     }     // namespace Hands
 } // namespace SenseShift::Body
diff --git a/lib/haptics/senseshift/body/haptics/body.cpp b/lib/haptics/senseshift/body/haptics/body.cpp
index ed97efd4..edd3ee6e 100644
--- a/lib/haptics/senseshift/body/haptics/body.cpp
+++ b/lib/haptics/senseshift/body/haptics/body.cpp
@@ -10,22 +10,22 @@ namespace SenseShift::Body::Haptics {
         }
     }
 
-    void HapticBody::effect(const EffectRequest_t& effect)
+    void HapticBody::effect(const EffectRequest& effect)
     {
-        if (effect.effect == Effect::Vibro && std::holds_alternative<VibroEffectData_t>(effect.data)) {
+        if (effect.effect == Effect::Vibro && std::holds_alternative<VibroEffectData>(effect.data)) {
             auto it = this->vibroTargets.find(effect.target);
             if (it == this->vibroTargets.end()) {
                 log_w("No target found for effect: %d", effect.target);
                 return;
             }
 
-            it->second->effect(effect.position, std::get<VibroEffectData_t>(effect.data));
+            it->second->effect(effect.position, std::get<VibroEffectData>(effect.data));
         } else {
             log_w("Non-supported effect type: %d", effect.effect);
         }
     }
 
-    void HapticBody::addTarget(const Target_t target, VibroPlane* plane)
+    void HapticBody::addTarget(const Target target, VibroPlane* plane)
     {
         this->vibroTargets[target] = plane;
         this->allTargets.insert({ target, plane });
diff --git a/lib/haptics/senseshift/body/haptics/body.hpp b/lib/haptics/senseshift/body/haptics/body.hpp
index 2b049c83..b0c9068e 100644
--- a/lib/haptics/senseshift/body/haptics/body.hpp
+++ b/lib/haptics/senseshift/body/haptics/body.hpp
@@ -10,22 +10,22 @@
 namespace SenseShift::Body::Haptics {
     class HapticBody {
       public:
-        typedef std::variant<VibroPlane*> AuctiativePlane_t;
-        typedef std::multimap<Target_t, AuctiativePlane_t> PlaneTargetMap_t;
-        typedef std::map<Target_t, VibroPlane*> VibroTargetMap_t;
+        using AuctiativePlane = std::variant<VibroPlane*>;
+        using PlaneTargetMap = std::multimap<Target, AuctiativePlane>;
+        using VibroTargetMap = std::map<Target, VibroPlane*>;
 
         HapticBody(){};
 
         void setup();
 
-        void effect(const EffectRequest_t&);
+        void effect(const EffectRequest&);
 
-        void addTarget(const Target_t, VibroPlane* plane);
+        void addTarget(const Target, VibroPlane* plane);
 
-        [[nodiscard]] const PlaneTargetMap_t* getTargets() const { return &allTargets; }
+        [[nodiscard]] const PlaneTargetMap* getTargets() const { return &allTargets; }
 
       private:
-        PlaneTargetMap_t allTargets{};
-        VibroTargetMap_t vibroTargets{};
+        PlaneTargetMap allTargets{};
+        VibroTargetMap vibroTargets{};
     };
 } // namespace SenseShift::Body::Haptics
diff --git a/lib/haptics/senseshift/body/haptics/interface.hpp b/lib/haptics/senseshift/body/haptics/interface.hpp
index 4ab0df0e..c4084531 100644
--- a/lib/haptics/senseshift/body/haptics/interface.hpp
+++ b/lib/haptics/senseshift/body/haptics/interface.hpp
@@ -6,18 +6,18 @@
 #include <senseshift/math/point2.hpp>
 
 namespace SenseShift::Body::Haptics {
-    typedef std::uint8_t EffectIntex_t;
-    static const EffectIntex_t EFFECT_INVALID = 0xFF;
-    typedef enum class Effect : EffectIntex_t {
+    using EffectIntex = std::uint8_t;
+    static constexpr EffectIntex EFFECT_INVALID = 0xFF;
+    enum class Effect : EffectIntex {
         Invalid = EFFECT_INVALID,
         Vibro = 0x00,
         // TODO: thermal, etc.
         // Thermal = 0x01,
-    } Effect_t;
+    };
 
-    typedef std::uint8_t TargetIndex_t;
-    static const TargetIndex_t TARGET_INVALID = 0xFF;
-    typedef enum class Target : TargetIndex_t {
+    using TargetIndex = std::uint8_t;
+    static constexpr TargetIndex TARGET_INVALID = 0xFF;
+    enum class Target : TargetIndex {
         Invalid = TARGET_INVALID,
         ChestFront = 0x00,
         ChestBack = 0x01,
@@ -44,38 +44,34 @@ namespace SenseShift::Body::Haptics {
         HandRightDorsal, // Back
 
         // TODO: arms, legs, etc.
-    } Target_t;
+    };
 
-    typedef std::uint8_t Coordinate_t;
-    typedef ::SenseShift::Math::Point2<Coordinate_t> Position_t;
+    using Coordinate = std::uint8_t;
+    using Position = ::SenseShift::Math::Point2<Coordinate>;
 
     // Vibration intensity.
-    typedef struct VibroEffectData {
-        using Intensity_t = std::uint16_t;
-        inline static const Intensity_t INTENSITY_MIN = 0;
-        inline static const Intensity_t INTENSITY_MAX = 4095;
+    struct VibroEffectData {
+        using Intensity = std::uint16_t;
+        static constexpr Intensity INTENSITY_MIN = 0;
+        static constexpr Intensity INTENSITY_MAX = 4095;
 
-        Intensity_t intensity = 0;
+        Intensity intensity = 0;
 
         inline constexpr VibroEffectData() = default;
-        inline constexpr VibroEffectData(const Intensity_t intensity) : intensity(intensity) {}
+        inline constexpr VibroEffectData(const Intensity intensity) : intensity(intensity) {}
         inline constexpr VibroEffectData(const VibroEffectData& other) = default;
 
         inline constexpr operator std::uint16_t() const { return intensity; }
-    } VibroEffectData_t;
+    };
 
     // TODO: thermal, etc.
 
-    typedef std::variant<VibroEffectData_t
-                         // TODO: thermal, etc.
-                         // ThermalEffectData_t
-                         >
-      EffectData_t;
-
-    typedef struct EffectRequest {
-        Effect_t effect = Effect::Invalid;
-        Target_t target = Target::Invalid;
-        Position_t position = Position_t(0, 0);
-        EffectData_t data;
-    } EffectRequest_t;
+    using EffectData = std::variant<VibroEffectData>;
+
+    struct EffectRequest {
+        Effect effect = Effect::Invalid;
+        Target target = Target::Invalid;
+        Position position = Position(0, 0);
+        EffectData data;
+    };
 } // namespace SenseShift::Body::Haptics
diff --git a/lib/haptics/senseshift/body/haptics/plane.cpp b/lib/haptics/senseshift/body/haptics/plane.cpp
index 21614dcc..ab11c81b 100644
--- a/lib/haptics/senseshift/body/haptics/plane.cpp
+++ b/lib/haptics/senseshift/body/haptics/plane.cpp
@@ -6,7 +6,7 @@
 
 namespace SenseShift::Body::Haptics {
     template<typename _Tp, typename _Ta>
-    void ActuativePlane<_Tp, _Ta>::setActuators(const ActuatorMap_t& actuators)
+    void ActuativePlane<_Tp, _Ta>::setActuators(const ActuatorMap& actuators)
     {
         this->actuators.clear();
         for (const auto& [point, actuator] : actuators) {
@@ -33,7 +33,7 @@ namespace SenseShift::Body::Haptics {
     }
 
     template<typename _Tp, typename _Ta>
-    void ActuativePlane<_Tp, _Ta>::effect(const Position_t& pos, const Value_t& val)
+    void ActuativePlane<_Tp, _Ta>::effect(const Position& pos, const Value& val)
     {
         auto it = this->actuators.find(pos);
         if (it == this->actuators.end()) {
@@ -46,15 +46,15 @@ namespace SenseShift::Body::Haptics {
     }
 
     template<typename _Tp, typename _Ta>
-    void ActuativePlane_Closest<_Tp, _Ta>::effect(const Position_t& pos, const Value_t& val)
+    void ActuativePlane_Closest<_Tp, _Ta>::effect(const Position& pos, const Value& val)
     {
         auto& closest = this->findClosestPoint(*this->getAvailablePoints(), pos);
         ActuativePlane<_Tp, _Ta>::effect(closest, val);
     }
 
     template<typename _Tp, typename _Ta>
-    const Position_t&
-      ActuativePlane_Closest<_Tp, _Ta>::findClosestPoint(const PositionSet_t& pts, const Position_t& target) const
+    const Position&
+      ActuativePlane_Closest<_Tp, _Ta>::findClosestPoint(const PositionSet& pts, const Position& target) const
     {
         // check if exact point exists
         auto it = pts.find(target);
@@ -63,7 +63,7 @@ namespace SenseShift::Body::Haptics {
         }
 
         // find closest point by square distance
-        std::multimap<float, Position_t> mp = {};
+        std::multimap<float, Position> mp = {};
         for (const auto& _p : pts) {
             mp.insert({ (target - _p), _p });
         }
@@ -73,8 +73,6 @@ namespace SenseShift::Body::Haptics {
         return nearest->second;
     }
 
-    template class ActuativePlane<VibroEffectData_t, ::SenseShift::Output::IActuator<VibroEffectData_t::Intensity_t>>;
-    template class ActuativePlane_Closest<
-      VibroEffectData_t,
-      ::SenseShift::Output::IActuator<VibroEffectData_t::Intensity_t>>;
+    template class ActuativePlane<VibroEffectData, ::SenseShift::Output::IActuator<VibroEffectData::Intensity>>;
+    template class ActuativePlane_Closest<VibroEffectData, ::SenseShift::Output::IActuator<VibroEffectData::Intensity>>;
 } // namespace SenseShift::Body::Haptics
diff --git a/lib/haptics/senseshift/body/haptics/plane.hpp b/lib/haptics/senseshift/body/haptics/plane.hpp
index 5c2cf7be..b8ac0727 100644
--- a/lib/haptics/senseshift/body/haptics/plane.hpp
+++ b/lib/haptics/senseshift/body/haptics/plane.hpp
@@ -11,7 +11,7 @@
 #include <vector>
 
 namespace SenseShift::Body::Haptics {
-    typedef std::set<Position_t> PositionSet_t;
+    using PositionSet = std::set<Position>;
 
     /**
      * Output "plane" (e.g. Chest, Palm, Finger, etc.)
@@ -20,35 +20,34 @@ namespace SenseShift::Body::Haptics {
      */
     template<typename _Tp, typename _Ta>
     class ActuativePlane {
-        static_assert(std::is_same<_Tp, VibroEffectData_t>());
+        static_assert(std::is_same<_Tp, VibroEffectData>());
 
       public:
-        using Value_t = _Tp;
-        using Actuator_t = _Ta;
+        using Value = _Tp;
+        using Actuator = _Ta;
 
-        typedef std::map<Position_t, Actuator_t*> ActuatorMap_t;
-        typedef std::map<Position_t, Value_t> PositionStateMap_t;
+        using ActuatorMap = std::map<Position, Actuator*>;
+        using PositionStateMap = std::map<Position, Value>;
 
         ActuativePlane() = default;
 
-        ActuativePlane(const ActuatorMap_t& actuators) { this->setActuators(actuators); }
+        ActuativePlane(const ActuatorMap& actuators) { this->setActuators(actuators); }
 
         void setup();
-        virtual void effect(const Position_t&, const Value_t&);
+        virtual void effect(const Position&, const Value&);
 
-        [[nodiscard]] const PositionSet_t* getAvailablePoints() const { return &points; }
-        [[nodiscard]] const PositionStateMap_t* getActuatorStates() const { return &states; }
+        [[nodiscard]] const PositionSet* getAvailablePoints() const { return &points; }
+        [[nodiscard]] const PositionStateMap* getActuatorStates() const { return &states; }
 
       private:
-        PositionSet_t points;
-        ActuatorMap_t actuators{};
-        PositionStateMap_t states{};
+        PositionSet points;
+        ActuatorMap actuators{};
+        PositionStateMap states{};
 
-        void setActuators(const ActuatorMap_t&);
+        void setActuators(const ActuatorMap&);
     };
 
-    typedef ActuativePlane<VibroEffectData_t, ::SenseShift::Output::IActuator<VibroEffectData_t::Intensity_t>>
-      VibroPlane;
+    using VibroPlane = ActuativePlane<VibroEffectData, ::SenseShift::Output::IActuator<VibroEffectData::Intensity>>;
 
     /**
      * Output plane, finds the closest actuator for the given point.
@@ -57,30 +56,30 @@ namespace SenseShift::Body::Haptics {
     template<typename _Tp, typename _Ta>
     class ActuativePlane_Closest : public ActuativePlane<_Tp, _Ta> {
       public:
-        typedef _Tp Value_t;
+        using Value = _Tp;
 
-        ActuativePlane_Closest(const typename ActuativePlane<_Tp, _Ta>::ActuatorMap_t& actuators) :
+        ActuativePlane_Closest(const typename ActuativePlane<_Tp, _Ta>::ActuatorMap& actuators) :
           ActuativePlane<_Tp, _Ta>(actuators)
         {
         }
 
-        void effect(const Position_t&, const Value_t&) override;
+        void effect(const Position&, const Value&) override;
 
       private:
-        [[nodiscard]] const Position_t& findClosestPoint(const PositionSet_t&, const Position_t&) const;
+        [[nodiscard]] const Position& findClosestPoint(const PositionSet&, const Position&) const;
     };
 
-    typedef ActuativePlane_Closest<VibroEffectData_t, ::SenseShift::Output::IActuator<VibroEffectData_t::Intensity_t>>
-      VibroPlane_Closest;
+    using VibroPlane_Closest =
+      ActuativePlane_Closest<VibroEffectData, ::SenseShift::Output::IActuator<VibroEffectData::Intensity>>;
 
     // TODO: configurable margin
     class PlaneMapper_Margin {
       public:
         template<typename _Tp>
-        [[nodiscard]] static constexpr inline std::map<Position_t, _Tp*>
+        [[nodiscard]] static constexpr inline std::map<Position, _Tp*>
           mapMatrixCoordinates(std::vector<std::vector<_Tp*>> map2d)
         {
-            std::map<Position_t, _Tp*> points{};
+            std::map<Position, _Tp*> points{};
 
             size_t y_size = map2d.size();
             size_t y_max = y_size - 1;
@@ -92,7 +91,7 @@ namespace SenseShift::Body::Haptics {
 
                 for (size_t x = 0; x < x_size; ++x) {
                     auto* wr = row.at(x);
-                    Position_t coord = PlaneMapper_Margin::mapPoint<Position_t::Value_t>(x, y, x_max, y_max);
+                    Position coord = PlaneMapper_Margin::mapPoint<Position::Value>(x, y, x_max, y_max);
 
                     points[coord] = wr;
                 }
@@ -109,11 +108,11 @@ namespace SenseShift::Body::Haptics {
         [[nodiscard]] static constexpr inline ::SenseShift::Math::Point2<_Tp>
           mapPoint(_Tp x, _Tp y, _Tp x_max, _Tp y_max)
         {
-            using Point_t = ::SenseShift::Math::Point2<_Tp>;
+            using Point = ::SenseShift::Math::Point2<_Tp>;
 
-            return Point_t(
-              ::SenseShift::accurateMap<_Tp>(x + 1, 0, x_max + 2, Point_t::MIN, Point_t::MAX),
-              ::SenseShift::accurateMap<_Tp>(y + 1, 0, y_max + 2, Point_t::MIN, Point_t::MAX)
+            return Point(
+              ::SenseShift::accurateMap<_Tp>(x + 1, 0, x_max + 2, Point::MIN, Point::MAX),
+              ::SenseShift::accurateMap<_Tp>(y + 1, 0, y_max + 2, Point::MIN, Point::MAX)
             );
         }
     };
diff --git a/lib/math/senseshift/math/point2.hpp b/lib/math/senseshift/math/point2.hpp
index 001d16bf..b91b096b 100644
--- a/lib/math/senseshift/math/point2.hpp
+++ b/lib/math/senseshift/math/point2.hpp
@@ -10,10 +10,10 @@ namespace SenseShift::Math {
           std::is_arithmetic<_Tp>::value, "::SenseShift::Math::Point2 only can be used with arithmetic types"
         );
 
-        typedef _Tp Value_t;
+        using Value = _Tp;
 
-        inline static const _Tp MIN = std::numeric_limits<_Tp>::min();
-        inline static const _Tp MAX = std::numeric_limits<_Tp>::max();
+        static constexpr _Tp MIN = std::numeric_limits<_Tp>::min();
+        static constexpr _Tp MAX = std::numeric_limits<_Tp>::max();
 
         _Tp x, y;
 
@@ -33,10 +33,10 @@ namespace SenseShift::Math {
         }
     };
 
-    typedef Point2<unsigned char> Point2b;
-    typedef Point2<double> Point2d;
-    typedef Point2<float> Point2f;
-    typedef Point2<int> Point2i;
-    typedef Point2<short> Point2s;
-    typedef Point2<unsigned short> Point2w;
+    using Point2b = Point2<unsigned char>;
+    using Point2d = Point2<double>;
+    using Point2f = Point2<float>;
+    using Point2i = Point2<int>;
+    using Point2s = Point2<short>;
+    using Point2w = Point2<unsigned short>;
 }; // namespace SenseShift::Math
diff --git a/lib/opengloves/og_protocol.hpp b/lib/opengloves/og_protocol.hpp
index 3b8dcccc..a8f19e2e 100644
--- a/lib/opengloves/og_protocol.hpp
+++ b/lib/opengloves/og_protocol.hpp
@@ -57,8 +57,8 @@ namespace OpenGloves {
         virtual size_t readCommand(char* buffer, size_t length) = 0;
     };
 
-    typedef uint16_t CommandIndex_t;
-    typedef enum Command : CommandIndex_t {
+    using CommandIndex = uint16_t;
+    enum Command : CommandIndex {
         ThumbCurl,
         ThumbSplay,
 
@@ -73,7 +73,7 @@ namespace OpenGloves {
 
         PinkyCurl,
         PinkySplay,
-    } Command;
+    };
 
-    typedef std::function<void(Command, uint16_t)> CommandCallback;
+    using CommandCallback = std::function<void(Command, uint16_t)>;
 } // namespace OpenGloves
diff --git a/lib/opengloves/sensor/og_finger.hpp b/lib/opengloves/sensor/og_finger.hpp
index 1be50ec0..542d981e 100644
--- a/lib/opengloves/sensor/og_finger.hpp
+++ b/lib/opengloves/sensor/og_finger.hpp
@@ -31,7 +31,7 @@ namespace OpenGloves {
         virtual uint16_t getCurl() = 0;
     };
 
-    typedef SenseShift::Input::ISimpleSensor<FingerValue> IFingerSensor;
+    using IFingerSensor = SenseShift::Input::ISimpleSensor<FingerValue>;
 
     class SimpleFingerSensor : public IFingerSensor, public ICurl {
       public:
diff --git a/test/test_bhaptics_encoding/main.cpp b/test/test_bhaptics_encoding/main.cpp
index 03a560be..0ff45557 100644
--- a/test/test_bhaptics_encoding/main.cpp
+++ b/test/test_bhaptics_encoding/main.cpp
@@ -18,10 +18,10 @@ class TestActuator : public IActuator<uint16_t> {
 
 void test_layout_tactsuitx16(void)
 {
-    static const size_t bhLayoutSize = BH_LAYOUT_TACTSUITX16_SIZE;
-    static const OutputLayout_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTSUITX16;
+    static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTSUITX16_SIZE;
+    static const OutputLayout bhLayout[bhLayoutSize] = BH_LAYOUT_TACTSUITX16;
 
-    static const size_t layoutGroupsSize = BH_LAYOUT_TACTSUITX16_GROUPS_SIZE;
+    static constexpr size_t layoutGroupsSize = BH_LAYOUT_TACTSUITX16_GROUPS_SIZE;
     static const uint8_t layoutGroups[layoutGroupsSize] = BH_LAYOUT_TACTSUITX16_GROUPS;
 
     auto body = new HapticBody();
@@ -43,11 +43,11 @@ void test_layout_tactsuitx16(void)
     TestActuator* actuator14 = new TestActuator();
     TestActuator* actuator15 = new TestActuator();
 
-    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
+    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
       { actuator0, actuator1, actuator2, actuator3 },
       { actuator4, actuator5, actuator6, actuator7 },
     });
-    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
+    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
       { actuator8, actuator9, actuator10, actuator11 },
       { actuator12, actuator13, actuator14, actuator15 },
     });
@@ -86,19 +86,19 @@ void test_layout_tactsuitx16(void)
 
 void test_layout_tactsuitx40(void)
 {
-    static const size_t bhLayoutSize = BH_LAYOUT_TACTSUITX40_SIZE;
-    static const OutputLayout_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTSUITX40;
+    static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTSUITX40_SIZE;
+    static const OutputLayout bhLayout[bhLayoutSize] = BH_LAYOUT_TACTSUITX40;
 
     auto body = new HapticBody();
 
-    std::vector<std::vector<VibroPlane::Actuator_t*>> frontMatrix = {
+    std::vector<std::vector<VibroPlane::Actuator*>> frontMatrix = {
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
     };
-    std::vector<std::vector<VibroPlane::Actuator_t*>> backMatrix = {
+    std::vector<std::vector<VibroPlane::Actuator*>> backMatrix = {
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
@@ -106,8 +106,8 @@ void test_layout_tactsuitx40(void)
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
     };
 
-    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>(frontMatrix);
-    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>(backMatrix);
+    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>(frontMatrix);
+    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>(backMatrix);
 
     auto frontPlane = new VibroPlane(frontOutputs);
     auto backPlane = new VibroPlane(backOutputs);
@@ -169,8 +169,8 @@ void test_layout_tactsuitx40(void)
 
 void test_layout_tactal(void)
 {
-    static const size_t bhLayoutSize = BH_LAYOUT_TACTAL_SIZE;
-    static const ::SenseShift::Body::Haptics::Position_t bhLayout[bhLayoutSize] = BH_LAYOUT_TACTAL;
+    static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTAL_SIZE;
+    static const ::SenseShift::Body::Haptics::Position bhLayout[bhLayoutSize] = BH_LAYOUT_TACTAL;
 
     auto body = new HapticBody();
 
@@ -181,7 +181,7 @@ void test_layout_tactal(void)
     TestActuator* actuator4 = new TestActuator();
     TestActuator* actuator5 = new TestActuator();
 
-    auto outputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator_t>({
+    auto outputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
       { actuator0, actuator1, actuator2, actuator3, actuator4, actuator5 },
     });
     auto plane = new VibroPlane(outputs);
diff --git a/test/test_haptics_body/main.cpp b/test/test_haptics_body/main.cpp
index 5abefa7d..7f00d150 100644
--- a/test/test_haptics_body/main.cpp
+++ b/test/test_haptics_body/main.cpp
@@ -18,7 +18,7 @@ void test_it_sets_up_planes(void)
 {
     auto body = new HapticBody();
 
-    VibroPlane::ActuatorMap_t outputs = {
+    VibroPlane::ActuatorMap outputs = {
         { { 0, 0 }, new TestActuator() },
         { { 0, 1 }, new TestActuator() },
         { { 1, 0 }, new TestActuator() },
@@ -54,25 +54,25 @@ void test_it_handles_effect__vibro(void)
       .effect = Effect::Vibro,
       .target = Target::ChestFront,
       .position = { 0, 0 },
-      .data = (VibroEffectData_t) 64,
+      .data = (VibroEffectData) 64,
     });
     body->effect({
       .effect = Effect::Vibro,
       .target = Target::ChestFront,
       .position = { 0, 1 },
-      .data = (VibroEffectData_t) 128,
+      .data = (VibroEffectData) 128,
     });
     body->effect({
       .effect = Effect::Vibro,
       .target = Target::ChestFront,
       .position = { 1, 0 },
-      .data = (VibroEffectData_t) 192,
+      .data = (VibroEffectData) 192,
     });
     body->effect({
       .effect = Effect::Vibro,
       .target = Target::ChestFront,
       .position = { 1, 1 },
-      .data = (VibroEffectData_t) 255,
+      .data = (VibroEffectData) 255,
     });
 
     TEST_ASSERT_EQUAL(64, actuator1->intensity);
diff --git a/test/test_haptics_plane/main.cpp b/test/test_haptics_plane/main.cpp
index 0be53a48..7c8890cc 100644
--- a/test/test_haptics_plane/main.cpp
+++ b/test/test_haptics_plane/main.cpp
@@ -16,7 +16,7 @@ class TestActuator : public IActuator<uint16_t> {
 
 void test_it_sets_up_actuators(void)
 {
-    VibroPlane::ActuatorMap_t outputs = {
+    VibroPlane::ActuatorMap outputs = {
         { { 0, 0 }, new TestActuator() },
         { { 0, 1 }, new TestActuator() },
         { { 1, 0 }, new TestActuator() },
@@ -39,7 +39,7 @@ void test_it_writes_to_correct_output(void)
     auto actuator = new TestActuator(), actuator2 = new TestActuator(), actuator3 = new TestActuator(),
          actuator4 = new TestActuator();
 
-    VibroPlane::ActuatorMap_t outputs = {
+    VibroPlane::ActuatorMap outputs = {
         { { 0, 0 }, actuator },
         { { 0, 1 }, actuator2 },
         { { 1, 0 }, actuator3 },
@@ -64,7 +64,7 @@ void test_it_updates_state(void)
     auto actuator = new TestActuator(), actuator2 = new TestActuator(), actuator3 = new TestActuator(),
          actuator4 = new TestActuator();
 
-    VibroPlane::ActuatorMap_t outputs = {
+    VibroPlane::ActuatorMap outputs = {
         { { 0, 0 }, actuator },
         { { 0, 1 }, actuator2 },
         { { 1, 0 }, actuator3 },
@@ -96,7 +96,7 @@ void test_closest_it_writes_to_correct_if_exact(void)
     auto actuator = new TestActuator(), actuator2 = new TestActuator(), actuator3 = new TestActuator(),
          actuator4 = new TestActuator();
 
-    VibroPlane_Closest::ActuatorMap_t outputs = {
+    VibroPlane_Closest::ActuatorMap outputs = {
         { { 0, 0 }, actuator },
         { { 0, 1 }, actuator2 },
         { { 1, 0 }, actuator3 },
@@ -121,7 +121,7 @@ void test_closest_it_correctly_finds_closest(void)
     auto actuator = new TestActuator(), actuator2 = new TestActuator(), actuator3 = new TestActuator(),
          actuator4 = new TestActuator();
 
-    VibroPlane_Closest::ActuatorMap_t outputs = {
+    VibroPlane_Closest::ActuatorMap outputs = {
         { { 0, 0 }, actuator },
         { { 0, 64 }, actuator2 },
         { { 64, 0 }, actuator3 },
@@ -144,7 +144,7 @@ void test_closest_it_updates_state(void)
     auto actuator = new TestActuator(), actuator2 = new TestActuator(), actuator3 = new TestActuator(),
          actuator4 = new TestActuator();
 
-    VibroPlane_Closest::ActuatorMap_t outputs = {
+    VibroPlane_Closest::ActuatorMap outputs = {
         { { 0, 0 }, actuator },
         { { 0, 64 }, actuator2 },
         { { 64, 0 }, actuator3 },

From fc0fc5522b6a27e2fe7828fad93ba425981dc499 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Fri, 13 Oct 2023 10:58:59 +0400
Subject: [PATCH 44/82] chore: add VSCode extensions recommends

---
 .vscode/extensions.json | 11 +++++++++++
 1 file changed, 11 insertions(+)
 create mode 100644 .vscode/extensions.json

diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 00000000..8e806412
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,11 @@
+{
+    "recommendations": [
+        "ms-vscode.cpptools",
+        "redhat.vscode-yaml",
+        "platformio.platformio-ide",
+        "Wokwi.wokwi-vscode",
+        "xaver.clang-format",
+        "editorconfig.editorconfig",
+        "wakatime.vscode-wakatime"
+    ]
+}

From 2d125a4e6a9a66bef90a4c7d3f72e4924c8af4a2 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Tue, 24 Oct 2023 02:15:59 +0400
Subject: [PATCH 45/82] ci: change lcov reporter

---
 .github/workflows/ci.yml | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index cb9197de..bade01aa 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -428,10 +428,13 @@ jobs:
             ./build/coverage/
           retention-days: 5
 
-      - uses: manarbenkraouda/lcov-reporter-action@v0.3.5.3
+      - name: Upload coverage reports to Codecov
+        uses: codecov/codecov-action@v3
         with:
-          github-token: ${{ secrets.GITHUB_TOKEN }}
-          lcov-file: ./build/lcov/lcov.info.cleaned
+          token: ${{ secrets.CODECOV_TOKEN }}
+          fail_ci_if_error: true
+          files: ./build/lcov/lcov.info.cleaned
+          flags: unittests
 
   wokwi:
     needs:

From 25356a4f28aa4e327293577244f65ad260c8717d Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Tue, 24 Oct 2023 10:43:07 +0000
Subject: [PATCH 46/82] ci: configure codecov

---
 README.md   | 1 +
 codecov.yml | 3 +++
 2 files changed, 4 insertions(+)
 create mode 100644 codecov.yml

diff --git a/README.md b/README.md
index dc172271..4a655a94 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,7 @@ Build your own DIY haptic vest, haptic gloves, and more!
 [![Developer's Twitter](https://img.shields.io/twitter/follow/leon0399?color=%231DA1F2&label=Developer%20Twitter&logo=twitter)](https://twitter.com/leon0399)
 
 [![PlatformIO CI](https://github.com/senseshift/senseshift-firmware/actions/workflows/ci.yml/badge.svg)](https://github.com/senseshift/senseshift-firmware/actions/workflows/ci.yml)
+[![codecov](https://codecov.io/gh/senseshift/senseshift-firmware/graph/badge.svg?token=8CEuP8vk0f)](https://codecov.io/gh/senseshift/senseshift-firmware)
 [![GitHub bug issues](https://img.shields.io/github/issues/senseshift/senseshift-firmware/bug?color=%23d73a4a)](https://github.com/senseshift/senseshift-firmware/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
 
 [![GPL-3.0](https://img.shields.io/github/license/senseshift/senseshift-firmware)](/LICENSE)
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 00000000..ccc8e521
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,3 @@
+comment:
+  layout: "header, diff, flags, components"  # show component info in the PR comment
+

From 99d96f0fcd8b23d49f798eae079f00ea25ec8f5e Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Sun, 5 Nov 2023 13:55:02 +0400
Subject: [PATCH 47/82] feat(deps): update frozen submodule

---
 lib/frozen | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/frozen b/lib/frozen
index eb113465..3a4f00cb 160000
--- a/lib/frozen
+++ b/lib/frozen
@@ -1 +1 @@
-Subproject commit eb113465791905f29384a9deac6650fc323f4d53
+Subproject commit 3a4f00cb83d580880b8a79864a479e1a14eb8c74

From 92c5d13dbc7a25f17736c5b8b02b25bc3ba73345 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Sat, 30 Dec 2023 18:31:07 +0400
Subject: [PATCH 48/82] chore(PlatformIO): separate configs

---
 .vscode/extensions.json        |  15 +-
 ini/opengloves-indexer.ini     | 175 ++++++++++++++++++++
 ini/opengloves-lucidgloves.ini | 116 +++++++++++++
 ini/opengloves.ini             | 293 ---------------------------------
 platformio.ini                 |   3 +
 5 files changed, 304 insertions(+), 298 deletions(-)
 create mode 100644 ini/opengloves-indexer.ini
 create mode 100644 ini/opengloves-lucidgloves.ini

diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index 8e806412..7e2b2d8e 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -1,11 +1,16 @@
 {
+    // See http://go.microsoft.com/fwlink/?LinkId=827846
+    // for the documentation about the extensions.json format
     "recommendations": [
-        "ms-vscode.cpptools",
-        "redhat.vscode-yaml",
-        "platformio.platformio-ide",
         "Wokwi.wokwi-vscode",
-        "xaver.clang-format",
         "editorconfig.editorconfig",
-        "wakatime.vscode-wakatime"
+        "ms-vscode.cpptools",
+        "platformio.platformio-ide",
+        "redhat.vscode-yaml",
+        "wakatime.vscode-wakatime",
+        "xaver.clang-format"
+    ],
+    "unwantedRecommendations": [
+        "ms-vscode.cpptools-extension-pack"
     ]
 }
diff --git a/ini/opengloves-indexer.ini b/ini/opengloves-indexer.ini
new file mode 100644
index 00000000..7d5cd163
--- /dev/null
+++ b/ini/opengloves-indexer.ini
@@ -0,0 +1,175 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Indexer C
+; Wiring Diagram: https://github.com/Valsvirtuals/Indexer/wiki/wiring-and-pinout
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+[env:indexer-c]
+platform			= ${opengloves.platform}
+platform_packages	= ${opengloves.platform_packages}
+framework			= ${opengloves.framework}
+board				= wemos_d1_mini32
+upload_speed		= ${opengloves.upload_speed}
+monitor_speed		= ${opengloves.monitor_speed}
+
+build_flags 		= ${opengloves.build_flags}
+	; Pins configuration
+	; Comment out to disable
+	-D PIN_FINGER_THUMB=25
+	-D PIN_FINGER_INDEX=14
+	-D PIN_FINGER_MIDDLE=33
+	-D PIN_FINGER_RING=39
+	-D PIN_FINGER_PINKY=36
+
+	-D PIN_JOYSTICK_X=12
+	-D PIN_JOYSTICK_Y=4
+	-D PIN_BUTTON_JOYSTICK=0
+
+	-D PIN_BUTTON_A=2
+	-D PIN_BUTTON_B=11
+	; -D PIN_BUTTON_MENU=5
+	-D PIN_BUTTON_CALIBRATE=27
+	; -D PIN_BUTTON_TRIGGER=19 ; unused if GESTURE_TRIGGER is true
+	; -D PIN_BUTTON_GRAB=18 ; unused if GESTURE_GRAB is true
+	; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
+
+build_unflags 		= ${opengloves.build_unflags}
+build_src_filter	= ${opengloves.build_src_filter}
+	+<mode_configs/opengloves/opengloves.cpp>
+lib_deps    		= ${opengloves.lib_deps}
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Indexer CF
+; Wiring Diagram: https://github.com/Valsvirtuals/Indexer/wiki/wiring-and-pinout
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+[env:indexer-cf]
+platform			= ${opengloves.platform}
+platform_packages	= ${opengloves.platform_packages}
+framework			= ${opengloves.framework}
+board				= wemos_d1_mini32
+upload_speed		= ${opengloves.upload_speed}
+monitor_speed		= ${opengloves.monitor_speed}
+
+build_flags 		= ${opengloves.build_flags}
+	; Pins configuration
+	; Comment out to disable
+	-D PIN_FINGER_THUMB=25
+	-D PIN_FINGER_INDEX=14
+	-D PIN_FINGER_MIDDLE=33
+	-D PIN_FINGER_RING=39
+	-D PIN_FINGER_PINKY=36
+
+	-D PIN_JOYSTICK_X=12
+	-D PIN_JOYSTICK_Y=4
+	-D PIN_BUTTON_JOYSTICK=0
+
+	-D PIN_BUTTON_A=2
+	-D PIN_BUTTON_B=11
+	; -D PIN_BUTTON_MENU=5
+	-D PIN_BUTTON_CALIBRATE=27
+	; -D PIN_BUTTON_TRIGGER=19 ; unused if GESTURE_TRIGGER is true
+	; -D PIN_BUTTON_GRAB=18 ; unused if GESTURE_GRAB is true
+	; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
+
+	-D PIN_FFB_THUMB=16
+	-D PIN_FFB_INDEX=17
+	-D PIN_FFB_MIDDLE=21
+	-D PIN_FFB_RING=22
+	-D PIN_FFB_PINKY=1
+
+build_unflags 		= ${opengloves.build_unflags}
+build_src_filter	= ${opengloves.build_src_filter}
+	+<mode_configs/opengloves/opengloves.cpp>
+lib_deps    		= ${opengloves.lib_deps}
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Indexer CS
+; Wiring Diagram: https://github.com/Valsvirtuals/Indexer/wiki/wiring-and-pinout
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+[env:indexer-cs]
+platform			= ${opengloves.platform}
+platform_packages	= ${opengloves.platform_packages}
+framework			= ${opengloves.framework}
+board				= wemos_d1_mini32
+upload_speed		= ${opengloves.upload_speed}
+monitor_speed		= ${opengloves.monitor_speed}
+
+build_flags 		= ${opengloves.build_flags}
+	; Pins configuration
+	; Comment out to disable
+	-D PIN_FINGER_THUMB=25
+	-D PIN_FINGER_INDEX=14
+	-D PIN_FINGER_MIDDLE=33
+	-D PIN_FINGER_RING=39
+	-D PIN_FINGER_PINKY=36
+
+	-D PIN_FINGER_THUMB_SPLAY=32
+	-D PIN_FINGER_INDEX_SPLAY=13
+	-D PIN_FINGER_MIDDLE_SPLAY=34
+	-D PIN_FINGER_RING_SPLAY=35
+	-D PIN_FINGER_PINKY_SPLAY=26
+
+	-D PIN_JOYSTICK_X=12
+	-D PIN_JOYSTICK_Y=4
+	-D PIN_BUTTON_JOYSTICK=0
+
+	-D PIN_BUTTON_A=2
+	-D PIN_BUTTON_B=11
+	; -D PIN_BUTTON_MENU=5
+	-D PIN_BUTTON_CALIBRATE=27
+	; -D PIN_BUTTON_TRIGGER=19 ; unused if GESTURE_TRIGGER is true
+	; -D PIN_BUTTON_GRAB=18 ; unused if GESTURE_GRAB is true
+	; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
+
+build_unflags 		= ${opengloves.build_unflags}
+build_src_filter	= ${opengloves.build_src_filter}
+	+<mode_configs/opengloves/opengloves.cpp>
+lib_deps    		= ${opengloves.lib_deps}
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Indexer CSF
+; Wiring Diagram: https://github.com/Valsvirtuals/Indexer/wiki/wiring-and-pinout
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+[env:indexer-csf]
+platform			= ${opengloves.platform}
+platform_packages	= ${opengloves.platform_packages}
+framework			= ${opengloves.framework}
+board				= wemos_d1_mini32
+upload_speed		= ${opengloves.upload_speed}
+monitor_speed		= ${opengloves.monitor_speed}
+
+build_flags 		= ${opengloves.build_flags}
+	; Pins configuration
+	; Comment out to disable
+	-D PIN_FINGER_THUMB=25
+	-D PIN_FINGER_INDEX=14
+	-D PIN_FINGER_MIDDLE=33
+	-D PIN_FINGER_RING=39
+	-D PIN_FINGER_PINKY=36
+
+	-D PIN_FINGER_THUMB_SPLAY=32
+	-D PIN_FINGER_INDEX_SPLAY=13
+	-D PIN_FINGER_MIDDLE_SPLAY=34
+	-D PIN_FINGER_RING_SPLAY=35
+	-D PIN_FINGER_PINKY_SPLAY=26
+
+	-D PIN_JOYSTICK_X=12
+	-D PIN_JOYSTICK_Y=4
+	-D PIN_BUTTON_JOYSTICK=0
+
+	-D PIN_BUTTON_A=2
+	-D PIN_BUTTON_B=11
+	; -D PIN_BUTTON_MENU=5
+	-D PIN_BUTTON_CALIBRATE=27
+	; -D PIN_BUTTON_TRIGGER=19 ; unused if GESTURE_TRIGGER is true
+	; -D PIN_BUTTON_GRAB=18 ; unused if GESTURE_GRAB is true
+	; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
+
+	-D PIN_FFB_THUMB=16
+	-D PIN_FFB_INDEX=17
+	-D PIN_FFB_MIDDLE=21
+	-D PIN_FFB_RING=22
+	-D PIN_FFB_PINKY=1
+
+build_unflags 		= ${opengloves.build_unflags}
+build_src_filter	= ${opengloves.build_src_filter}
+	+<mode_configs/opengloves/opengloves.cpp>
+lib_deps    		= ${opengloves.lib_deps}
diff --git a/ini/opengloves-lucidgloves.ini b/ini/opengloves-lucidgloves.ini
new file mode 100644
index 00000000..f4047f55
--- /dev/null
+++ b/ini/opengloves-lucidgloves.ini
@@ -0,0 +1,116 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; LucidGloves Prototype 3
+; Wiring Diagram: https://github.com/LucidVR/lucidgloves/wiki/Prototype-3-Wiring-Diagram
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+[env:lucidgloves-prototype3]
+platform			= ${opengloves.platform}
+platform_packages	= ${opengloves.platform_packages}
+framework			= ${opengloves.framework}
+board				= ${opengloves.board}
+upload_speed		= ${opengloves.upload_speed}
+monitor_speed		= ${opengloves.monitor_speed}
+
+build_flags 		= ${opengloves.build_flags}
+	; Pins configuration
+	; Comment out to disable
+	-D PIN_FINGER_THUMB=32
+	-D PIN_FINGER_INDEX=35
+	-D PIN_FINGER_MIDDLE=34
+	-D PIN_FINGER_RING=39
+	-D PIN_FINGER_PINKY=36
+
+	-D PIN_JOYSTICK_X=33
+	-D PIN_JOYSTICK_Y=25
+	-D PIN_BUTTON_JOYSTICK=26
+
+	-D PIN_BUTTON_A=27
+	-D PIN_BUTTON_B=14
+
+build_unflags 		= ${opengloves.build_unflags}
+build_src_filter	= ${opengloves.build_src_filter}
+	+<mode_configs/opengloves/opengloves.cpp>
+lib_deps    		= ${opengloves.lib_deps}
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; LucidGloves Prototype 4
+; Wiring Diagram: https://github.com/LucidVR/lucidgloves/wiki/Prototype-4-Wiring-Diagram
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+[env:lucidgloves-prototype4]
+platform			= ${opengloves.platform}
+platform_packages	= ${opengloves.platform_packages}
+framework			= ${opengloves.framework}
+board				= ${opengloves.board}
+upload_speed		= ${opengloves.upload_speed}
+monitor_speed		= ${opengloves.monitor_speed}
+
+build_flags 		= ${opengloves.build_flags}
+	; Pins configuration
+	; Comment out to disable
+	-D PIN_FINGER_THUMB=32
+	-D PIN_FINGER_INDEX=35
+	-D PIN_FINGER_MIDDLE=34
+	-D PIN_FINGER_RING=39
+	-D PIN_FINGER_PINKY=36
+
+	-D PIN_JOYSTICK_X=33
+	-D PIN_JOYSTICK_Y=25
+	-D PIN_BUTTON_JOYSTICK=26
+
+	-D PIN_BUTTON_A=27
+	-D PIN_BUTTON_B=14
+	; -D PIN_BUTTON_MENU=27
+	-D PIN_BUTTON_CALIBRATE=12
+	; -D PIN_BUTTON_TRIGGER=12 ; unused if GESTURE_TRIGGER is true
+	; -D PIN_BUTTON_GRAB=13 ; unused if GESTURE_GRAB is true
+	; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
+
+	; todo: add servo pins
+
+build_unflags 		= ${opengloves.build_unflags}
+build_src_filter	= ${opengloves.build_src_filter}
+	+<mode_configs/opengloves/opengloves.cpp>
+lib_deps    		= ${opengloves.lib_deps}
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; LucidGloves Prototype 4 + Force Feedback
+; Wiring Diagram: https://github.com/LucidVR/lucidgloves/wiki/Prototype-4-Wiring-Diagram
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+[env:lucidgloves-prototype4-ffb]
+platform			= ${opengloves.platform}
+platform_packages	= ${opengloves.platform_packages}
+framework			= ${opengloves.framework}
+board				= ${opengloves.board}
+upload_speed		= ${opengloves.upload_speed}
+monitor_speed		= ${opengloves.monitor_speed}
+
+build_flags 		= ${opengloves.build_flags}
+	; Pins configuration
+	; Comment out to disable
+	-D PIN_FINGER_THUMB=32
+	-D PIN_FINGER_INDEX=35
+	-D PIN_FINGER_MIDDLE=34
+	-D PIN_FINGER_RING=39
+	-D PIN_FINGER_PINKY=36
+
+	-D PIN_JOYSTICK_X=33
+	-D PIN_JOYSTICK_Y=25
+	-D PIN_BUTTON_JOYSTICK=26
+
+	-D PIN_BUTTON_A=27
+	-D PIN_BUTTON_B=14
+	; -D PIN_BUTTON_MENU=27
+	-D PIN_BUTTON_CALIBRATE=12
+	; -D PIN_BUTTON_TRIGGER=12 ; unused if GESTURE_TRIGGER is true
+	; -D PIN_BUTTON_GRAB=13 ; unused if GESTURE_GRAB is true
+	; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
+
+	-D PIN_FFB_THUMB=17
+	-D PIN_FFB_INDEX=21
+	-D PIN_FFB_MIDDLE=19
+	-D PIN_FFB_RING=18
+	-D PIN_FFB_PINKY=5
+
+build_unflags 		= ${opengloves.build_unflags}
+build_src_filter	= ${opengloves.build_src_filter}
+	+<mode_configs/opengloves/opengloves.cpp>
+lib_deps    		= ${opengloves.lib_deps}
diff --git a/ini/opengloves.ini b/ini/opengloves.ini
index 0160ae8d..b7bb70cc 100644
--- a/ini/opengloves.ini
+++ b/ini/opengloves.ini
@@ -58,296 +58,3 @@ build_flags 		= ${common.build_flags}
 build_unflags   	= ${common.build_unflags}
 build_src_filter	= ${common.build_src_filter}
 lib_deps    		= ${common.lib_deps}
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-; LucidGloves Prototype 3
-; Wiring Diagram: https://github.com/LucidVR/lucidgloves/wiki/Prototype-3-Wiring-Diagram
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-[env:lucidgloves-prototype3]
-platform			= ${opengloves.platform}
-platform_packages	= ${opengloves.platform_packages}
-framework			= ${opengloves.framework}
-board				= ${opengloves.board}
-upload_speed		= ${opengloves.upload_speed}
-monitor_speed		= ${opengloves.monitor_speed}
-
-build_flags 		= ${opengloves.build_flags}
-	; Pins configuration
-	; Comment out to disable
-	-D PIN_FINGER_THUMB=32
-	-D PIN_FINGER_INDEX=35
-	-D PIN_FINGER_MIDDLE=34
-	-D PIN_FINGER_RING=39
-	-D PIN_FINGER_PINKY=36
-
-	-D PIN_JOYSTICK_X=33
-	-D PIN_JOYSTICK_Y=25
-	-D PIN_BUTTON_JOYSTICK=26
-
-	-D PIN_BUTTON_A=27
-	-D PIN_BUTTON_B=14
-
-build_unflags 		= ${opengloves.build_unflags}
-build_src_filter	= ${opengloves.build_src_filter}
-	+<mode_configs/opengloves/opengloves.cpp>
-lib_deps    		= ${opengloves.lib_deps}
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-; LucidGloves Prototype 4
-; Wiring Diagram: https://github.com/LucidVR/lucidgloves/wiki/Prototype-4-Wiring-Diagram
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-[env:lucidgloves-prototype4]
-platform			= ${opengloves.platform}
-platform_packages	= ${opengloves.platform_packages}
-framework			= ${opengloves.framework}
-board				= ${opengloves.board}
-upload_speed		= ${opengloves.upload_speed}
-monitor_speed		= ${opengloves.monitor_speed}
-
-build_flags 		= ${opengloves.build_flags}
-	; Pins configuration
-	; Comment out to disable
-	-D PIN_FINGER_THUMB=32
-	-D PIN_FINGER_INDEX=35
-	-D PIN_FINGER_MIDDLE=34
-	-D PIN_FINGER_RING=39
-	-D PIN_FINGER_PINKY=36
-
-	-D PIN_JOYSTICK_X=33
-	-D PIN_JOYSTICK_Y=25
-	-D PIN_BUTTON_JOYSTICK=26
-
-	-D PIN_BUTTON_A=27
-	-D PIN_BUTTON_B=14
-	; -D PIN_BUTTON_MENU=27
-	-D PIN_BUTTON_CALIBRATE=12
-	; -D PIN_BUTTON_TRIGGER=12 ; unused if GESTURE_TRIGGER is true
-	; -D PIN_BUTTON_GRAB=13 ; unused if GESTURE_GRAB is true
-	; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
-
-	; todo: add servo pins
-
-build_unflags 		= ${opengloves.build_unflags}
-build_src_filter	= ${opengloves.build_src_filter}
-	+<mode_configs/opengloves/opengloves.cpp>
-lib_deps    		= ${opengloves.lib_deps}
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-; LucidGloves Prototype 4 + Force Feedback
-; Wiring Diagram: https://github.com/LucidVR/lucidgloves/wiki/Prototype-4-Wiring-Diagram
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-[env:lucidgloves-prototype4-ffb]
-platform			= ${opengloves.platform}
-platform_packages	= ${opengloves.platform_packages}
-framework			= ${opengloves.framework}
-board				= ${opengloves.board}
-upload_speed		= ${opengloves.upload_speed}
-monitor_speed		= ${opengloves.monitor_speed}
-
-build_flags 		= ${opengloves.build_flags}
-	; Pins configuration
-	; Comment out to disable
-	-D PIN_FINGER_THUMB=32
-	-D PIN_FINGER_INDEX=35
-	-D PIN_FINGER_MIDDLE=34
-	-D PIN_FINGER_RING=39
-	-D PIN_FINGER_PINKY=36
-
-	-D PIN_JOYSTICK_X=33
-	-D PIN_JOYSTICK_Y=25
-	-D PIN_BUTTON_JOYSTICK=26
-
-	-D PIN_BUTTON_A=27
-	-D PIN_BUTTON_B=14
-	; -D PIN_BUTTON_MENU=27
-	-D PIN_BUTTON_CALIBRATE=12
-	; -D PIN_BUTTON_TRIGGER=12 ; unused if GESTURE_TRIGGER is true
-	; -D PIN_BUTTON_GRAB=13 ; unused if GESTURE_GRAB is true
-	; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
-
-	-D PIN_FFB_THUMB=17
-	-D PIN_FFB_INDEX=21
-	-D PIN_FFB_MIDDLE=19
-	-D PIN_FFB_RING=18
-	-D PIN_FFB_PINKY=5
-
-build_unflags 		= ${opengloves.build_unflags}
-build_src_filter	= ${opengloves.build_src_filter}
-	+<mode_configs/opengloves/opengloves.cpp>
-lib_deps    		= ${opengloves.lib_deps}
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-; Indexer C
-; Wiring Diagram: https://github.com/Valsvirtuals/Indexer/wiki/wiring-and-pinout
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-[env:indexer-c]
-platform			= ${opengloves.platform}
-platform_packages	= ${opengloves.platform_packages}
-framework			= ${opengloves.framework}
-board				= wemos_d1_mini32
-upload_speed		= ${opengloves.upload_speed}
-monitor_speed		= ${opengloves.monitor_speed}
-
-build_flags 		= ${opengloves.build_flags}
-	; Pins configuration
-	; Comment out to disable
-	-D PIN_FINGER_THUMB=25
-	-D PIN_FINGER_INDEX=14
-	-D PIN_FINGER_MIDDLE=33
-	-D PIN_FINGER_RING=39
-	-D PIN_FINGER_PINKY=36
-
-	-D PIN_JOYSTICK_X=12
-	-D PIN_JOYSTICK_Y=4
-	-D PIN_BUTTON_JOYSTICK=0
-
-	-D PIN_BUTTON_A=2
-	-D PIN_BUTTON_B=11
-	; -D PIN_BUTTON_MENU=5
-	-D PIN_BUTTON_CALIBRATE=27
-	; -D PIN_BUTTON_TRIGGER=19 ; unused if GESTURE_TRIGGER is true
-	; -D PIN_BUTTON_GRAB=18 ; unused if GESTURE_GRAB is true
-	; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
-
-build_unflags 		= ${opengloves.build_unflags}
-build_src_filter	= ${opengloves.build_src_filter}
-	+<mode_configs/opengloves/opengloves.cpp>
-lib_deps    		= ${opengloves.lib_deps}
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-; Indexer CF
-; Wiring Diagram: https://github.com/Valsvirtuals/Indexer/wiki/wiring-and-pinout
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-[env:indexer-cf]
-platform			= ${opengloves.platform}
-platform_packages	= ${opengloves.platform_packages}
-framework			= ${opengloves.framework}
-board				= wemos_d1_mini32
-upload_speed		= ${opengloves.upload_speed}
-monitor_speed		= ${opengloves.monitor_speed}
-
-build_flags 		= ${opengloves.build_flags}
-	; Pins configuration
-	; Comment out to disable
-	-D PIN_FINGER_THUMB=25
-	-D PIN_FINGER_INDEX=14
-	-D PIN_FINGER_MIDDLE=33
-	-D PIN_FINGER_RING=39
-	-D PIN_FINGER_PINKY=36
-
-	-D PIN_JOYSTICK_X=12
-	-D PIN_JOYSTICK_Y=4
-	-D PIN_BUTTON_JOYSTICK=0
-
-	-D PIN_BUTTON_A=2
-	-D PIN_BUTTON_B=11
-	; -D PIN_BUTTON_MENU=5
-	-D PIN_BUTTON_CALIBRATE=27
-	; -D PIN_BUTTON_TRIGGER=19 ; unused if GESTURE_TRIGGER is true
-	; -D PIN_BUTTON_GRAB=18 ; unused if GESTURE_GRAB is true
-	; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
-
-	-D PIN_FFB_THUMB=16
-	-D PIN_FFB_INDEX=17
-	-D PIN_FFB_MIDDLE=21
-	-D PIN_FFB_RING=22
-	-D PIN_FFB_PINKY=1
-
-build_unflags 		= ${opengloves.build_unflags}
-build_src_filter	= ${opengloves.build_src_filter}
-	+<mode_configs/opengloves/opengloves.cpp>
-lib_deps    		= ${opengloves.lib_deps}
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-; Indexer CS
-; Wiring Diagram: https://github.com/Valsvirtuals/Indexer/wiki/wiring-and-pinout
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-[env:indexer-cs]
-platform			= ${opengloves.platform}
-platform_packages	= ${opengloves.platform_packages}
-framework			= ${opengloves.framework}
-board				= wemos_d1_mini32
-upload_speed		= ${opengloves.upload_speed}
-monitor_speed		= ${opengloves.monitor_speed}
-
-build_flags 		= ${opengloves.build_flags}
-	; Pins configuration
-	; Comment out to disable
-	-D PIN_FINGER_THUMB=25
-	-D PIN_FINGER_INDEX=14
-	-D PIN_FINGER_MIDDLE=33
-	-D PIN_FINGER_RING=39
-	-D PIN_FINGER_PINKY=36
-
-	-D PIN_FINGER_THUMB_SPLAY=32
-	-D PIN_FINGER_INDEX_SPLAY=13
-	-D PIN_FINGER_MIDDLE_SPLAY=34
-	-D PIN_FINGER_RING_SPLAY=35
-	-D PIN_FINGER_PINKY_SPLAY=26
-
-	-D PIN_JOYSTICK_X=12
-	-D PIN_JOYSTICK_Y=4
-	-D PIN_BUTTON_JOYSTICK=0
-
-	-D PIN_BUTTON_A=2
-	-D PIN_BUTTON_B=11
-	; -D PIN_BUTTON_MENU=5
-	-D PIN_BUTTON_CALIBRATE=27
-	; -D PIN_BUTTON_TRIGGER=19 ; unused if GESTURE_TRIGGER is true
-	; -D PIN_BUTTON_GRAB=18 ; unused if GESTURE_GRAB is true
-	; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
-
-build_unflags 		= ${opengloves.build_unflags}
-build_src_filter	= ${opengloves.build_src_filter}
-	+<mode_configs/opengloves/opengloves.cpp>
-lib_deps    		= ${opengloves.lib_deps}
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-; Indexer CSF
-; Wiring Diagram: https://github.com/Valsvirtuals/Indexer/wiki/wiring-and-pinout
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-[env:indexer-csf]
-platform			= ${opengloves.platform}
-platform_packages	= ${opengloves.platform_packages}
-framework			= ${opengloves.framework}
-board				= wemos_d1_mini32
-upload_speed		= ${opengloves.upload_speed}
-monitor_speed		= ${opengloves.monitor_speed}
-
-build_flags 		= ${opengloves.build_flags}
-	; Pins configuration
-	; Comment out to disable
-	-D PIN_FINGER_THUMB=25
-	-D PIN_FINGER_INDEX=14
-	-D PIN_FINGER_MIDDLE=33
-	-D PIN_FINGER_RING=39
-	-D PIN_FINGER_PINKY=36
-
-	-D PIN_FINGER_THUMB_SPLAY=32
-	-D PIN_FINGER_INDEX_SPLAY=13
-	-D PIN_FINGER_MIDDLE_SPLAY=34
-	-D PIN_FINGER_RING_SPLAY=35
-	-D PIN_FINGER_PINKY_SPLAY=26
-
-	-D PIN_JOYSTICK_X=12
-	-D PIN_JOYSTICK_Y=4
-	-D PIN_BUTTON_JOYSTICK=0
-
-	-D PIN_BUTTON_A=2
-	-D PIN_BUTTON_B=11
-	; -D PIN_BUTTON_MENU=5
-	-D PIN_BUTTON_CALIBRATE=27
-	; -D PIN_BUTTON_TRIGGER=19 ; unused if GESTURE_TRIGGER is true
-	; -D PIN_BUTTON_GRAB=18 ; unused if GESTURE_GRAB is true
-	; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
-
-	-D PIN_FFB_THUMB=16
-	-D PIN_FFB_INDEX=17
-	-D PIN_FFB_MIDDLE=21
-	-D PIN_FFB_RING=22
-	-D PIN_FFB_PINKY=1
-
-build_unflags 		= ${opengloves.build_unflags}
-build_src_filter	= ${opengloves.build_src_filter}
-	+<mode_configs/opengloves/opengloves.cpp>
-lib_deps    		= ${opengloves.lib_deps}
diff --git a/platformio.ini b/platformio.ini
index 6787808b..4815cf7a 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -15,6 +15,9 @@ src_dir       = ./firmware
 extra_configs =
     ini/bhaptics.ini
 	ini/opengloves.ini
+	ini/opengloves-lucidgloves.ini
+	ini/opengloves-indexer.ini
+default_envs  = bhaptics_tactsuit_x16, bhaptics_tactal, bhaptics_tactglove_left, lucidgloves-prototype3
 
 [common]
 build_unflags     =

From 9e33f0bfd8e18d4b94470e930813b01dcf4e67c6 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Fri, 26 Jan 2024 20:58:19 +0400
Subject: [PATCH 49/82] refactor: simplyfy overall setup and structure

---
 docs/DEVELOPMENT.md                           |  32 ++-
 firmware/firmware.cpp                         |   2 +-
 firmware/mode_configs/bhaptics/tactal.cpp     |  19 +-
 firmware/mode_configs/bhaptics/tactglove.cpp  |  27 ++-
 firmware/mode_configs/bhaptics/tactosy2.cpp   |  21 +-
 firmware/mode_configs/bhaptics/tactosyf.cpp   |  23 ++-
 firmware/mode_configs/bhaptics/tactosyh.cpp   |  23 ++-
 .../mode_configs/bhaptics/tactsuit_x16.cpp    |  35 ++--
 .../bhaptics/tactsuit_x16_pca9685.cpp         |  37 ++--
 .../mode_configs/bhaptics/tactsuit_x40.cpp    |  46 ++---
 firmware/mode_configs/bhaptics/tactvisor.cpp  |  19 +-
 .../mode_configs/opengloves/opengloves.cpp    |   2 +-
 firmware/senseshift.cpp                       |  18 +-
 include/senseshift.h                          |  16 +-
 lib/arduino/library.json                      |   3 +-
 .../arduino/components/serial_plotter.cpp     |  28 ---
 .../arduino/components/serial_plotter.hpp     |  65 -------
 .../arduino/input/sensor/analog.hpp           |  23 +--
 .../arduino/input/sensor/binary.hpp           |  29 +++
 .../arduino/input/sensor/digital.hpp          |  34 ----
 .../arduino/output/actuator/pca9685.hpp       |  26 ---
 .../arduino/output/actuator/pwm.hpp           |  47 -----
 .../senseshift/arduino/output/analog.hpp      |  29 +++
 .../senseshift/arduino/output/pca9685.hpp     |  35 ++++
 .../senseshift/arduino/output/ledc.hpp        |  57 ++++++
 lib/battery/senseshift/battery.hpp            |  58 +++++-
 lib/battery/senseshift/battery/sensor.hpp     |   6 +-
 lib/bhaptics/senseshift/bh/devices.hpp        |  95 ++++-----
 lib/bhaptics/senseshift/bh/encoding.hpp       | 183 +++++++++---------
 lib/core/senseshift/core/component.hpp        |  33 ++++
 lib/core/senseshift/core/helpers.hpp          |  59 ++++++
 lib/core/senseshift/core/logging.hpp          |  40 ++++
 lib/core/senseshift/core/macros.hpp           |   4 +
 lib/core/senseshift/interface.hpp             |  11 --
 lib/core/senseshift/logging.hpp               |  23 ---
 lib/freertos/senseshift/freertos/task.hpp     |   2 +-
 lib/haptics/senseshift/body/haptics/body.cpp  |  36 ++--
 lib/haptics/senseshift/body/haptics/body.hpp  |  39 ++--
 .../senseshift/body/haptics/interface.hpp     |  35 ++--
 lib/haptics/senseshift/body/haptics/plane.cpp |  78 ++++----
 lib/haptics/senseshift/body/haptics/plane.hpp | 128 ++++++------
 lib/io/senseshift/input/sensor.hpp            | 178 +++++++++--------
 lib/io/senseshift/input/sensor/joystick.hpp   |  32 +--
 lib/io/senseshift/output/actuator.hpp         |  22 ---
 lib/io/senseshift/output/binary_output.hpp    |  10 +
 lib/io/senseshift/output/output.hpp           |  15 ++
 lib/math/senseshift/math/point2.hpp           |  30 +--
 lib/opengloves/og_protocol.hpp                |   2 +-
 .../senseshift/opengloves/autoconfig.hpp      |   2 +-
 .../senseshift/opengloves/encoding/alpha.hpp  |   2 +-
 lib/opengloves/sensor/og_finger.hpp           |  10 +-
 lib/opengloves/sensor/og_gesture.hpp          |   8 +-
 lib/opengloves/sensor/og_sensor.hpp           |  36 ++--
 lib/opengloves_task/opengloves_task.hpp       |   2 +-
 lib/util/senseshift/calibration.hpp           | 116 +++++------
 lib/util/senseshift/container.hpp             |   2 +-
 lib/util/senseshift/range.hpp                 |  27 +--
 57 files changed, 1074 insertions(+), 946 deletions(-)
 delete mode 100644 lib/arduino/senseshift/arduino/components/serial_plotter.cpp
 delete mode 100644 lib/arduino/senseshift/arduino/components/serial_plotter.hpp
 create mode 100644 lib/arduino/senseshift/arduino/input/sensor/binary.hpp
 delete mode 100644 lib/arduino/senseshift/arduino/input/sensor/digital.hpp
 delete mode 100644 lib/arduino/senseshift/arduino/output/actuator/pca9685.hpp
 delete mode 100644 lib/arduino/senseshift/arduino/output/actuator/pwm.hpp
 create mode 100644 lib/arduino/senseshift/arduino/output/analog.hpp
 create mode 100644 lib/arduino/senseshift/arduino/output/pca9685.hpp
 create mode 100644 lib/arduino_esp32/senseshift/arduino/output/ledc.hpp
 create mode 100644 lib/core/senseshift/core/component.hpp
 create mode 100644 lib/core/senseshift/core/helpers.hpp
 create mode 100644 lib/core/senseshift/core/logging.hpp
 create mode 100644 lib/core/senseshift/core/macros.hpp
 delete mode 100644 lib/core/senseshift/interface.hpp
 delete mode 100644 lib/core/senseshift/logging.hpp
 delete mode 100644 lib/io/senseshift/output/actuator.hpp
 create mode 100644 lib/io/senseshift/output/binary_output.hpp
 create mode 100644 lib/io/senseshift/output/output.hpp

diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md
index dafd8ccf..1a6e5409 100644
--- a/docs/DEVELOPMENT.md
+++ b/docs/DEVELOPMENT.md
@@ -1,3 +1,23 @@
+# Code Style
+
+## Member naming
+
+Local variable: `snake_case`
+Global variable: `snake_case`
+
+Class: `UpperCamelCase`
+Class member: `snake_case_` (**with trailing underscore**)
+ClassFunction: `camelCase`
+Function argument: `snake_case`
+
+## Useful Scripts
+
+### Fix `clang-format`
+
+```shell
+find lib include firmware test examples -type f -regex '.*\.\(cpp\|hpp\|cu\|c\|h\)' ! -regex '^lib/frozen\(/.*\)' -exec clang-format-16 -style=file -i {} \;
+```
+
 # Debugging
 
 ## Debugging in Wokwi
@@ -9,7 +29,7 @@ Run and debug firmware in Wokwi Simulator
    pio debug -e lucidgloves-prototype3
    ```
 2. `Ctrl+Shift+P` => `> Wokwi: Start Simulator and Wait for Debugger`,
-3. Add launch option (this step is required until PlatformIO fixes this issue: [#3824](https://github.com/platformio/platformio-core/issues/3824)): 
+3. Add launch option (this step is required until PlatformIO fixes this issue: [#3824](https://github.com/platformio/platformio-core/issues/3824)):
    ```json
    ...
    
@@ -24,12 +44,4 @@ Run and debug firmware in Wokwi Simulator
         "miDebuggerServerAddress": "localhost:3333",
     },
    ...
-   ```
-
-## Useful Scripts
-
-### Fix `clang-format`
-
-```shell
-find lib include firmware test examples -type f -regex '.*\.\(cpp\|hpp\|cu\|c\|h\)' ! -regex '^lib/frozen\(/.*\)' -exec clang-format-16 -style=file -i {} \;
-```
+   ```
\ No newline at end of file
diff --git a/firmware/firmware.cpp b/firmware/firmware.cpp
index c30147c3..213a1fbd 100644
--- a/firmware/firmware.cpp
+++ b/firmware/firmware.cpp
@@ -19,7 +19,7 @@ extern void loopMode();
 
 #if defined(ARDUINO)
 
-SenseShift::SenseShift App;
+SenseShift::Application App;
 
 void setup()
 {
diff --git a/firmware/mode_configs/bhaptics/tactal.cpp b/firmware/mode_configs/bhaptics/tactal.cpp
index 54bd226f..08f35d38 100644
--- a/firmware/mode_configs/bhaptics/tactal.cpp
+++ b/firmware/mode_configs/bhaptics/tactal.cpp
@@ -7,7 +7,7 @@
 #include "senseshift.h"
 
 #include <senseshift/arduino/input/sensor/analog.hpp>
-#include <senseshift/arduino/output/actuator/pwm.hpp>
+#include <senseshift/arduino/output/ledc.hpp>
 #include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
@@ -22,24 +22,23 @@ using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
-extern SenseShift::SenseShift App;
-SenseShift::SenseShift* app = &App;
+extern Application App;
+Application* app = &App;
 
-static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTAL_SIZE;
-static const Position bhLayout[bhLayoutSize] = BH_LAYOUT_TACTAL;
+static const std::array<Position, BH_LAYOUT_TACTAL_SIZE> bhLayout = { BH_LAYOUT_TACTAL };
 
 void setupMode()
 {
     // Configure PWM pins to their positions on the face
-    const auto faceOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
+    const auto faceOutputs = PlaneMapper_Margin::mapMatrixCoordinates<FloatPlane::Actuator>({
       // clang-format off
-      { new ActuatorPWM(32), new ActuatorPWM(33), new ActuatorPWM(25), new ActuatorPWM(26), new ActuatorPWM(27), new ActuatorPWM(14) },
+      { new LedcOutput(32), new LedcOutput(33), new LedcOutput(25), new LedcOutput(26), new LedcOutput(27), new LedcOutput(14) },
       // clang-format on
     });
 
-    app->getHapticBody()->addTarget(Target::FaceFront, new VibroPlane_Closest(faceOutputs));
+    app->getVibroBody()->addTarget(Target::FaceFront, new FloatPlane_Closest(faceOutputs));
 
-    app->getHapticBody()->setup();
+    app->getVibroBody()->setup();
 
     auto* bhBleConnection = new BLE::Connection(
       {
@@ -48,7 +47,7 @@ void setupMode()
         .serialNumber = BH_SERIAL_NUMBER,
       },
       [](std::string& value) -> void {
-          Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect::Vibro, Target::FaceFront);
+          Decoder::applyPlain(app->getVibroBody(), value, bhLayout, Effect::Vibro, Target::FaceFront);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactglove.cpp b/firmware/mode_configs/bhaptics/tactglove.cpp
index 3091a5d6..7463c1b9 100644
--- a/firmware/mode_configs/bhaptics/tactglove.cpp
+++ b/firmware/mode_configs/bhaptics/tactglove.cpp
@@ -7,7 +7,7 @@
 #include "senseshift.h"
 
 #include <senseshift/arduino/input/sensor/analog.hpp>
-#include <senseshift/arduino/output/actuator/pwm.hpp>
+#include <senseshift/arduino/output/ledc.hpp>
 #include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
@@ -23,13 +23,12 @@ using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
-extern SenseShift::SenseShift App;
-SenseShift::SenseShift* app = &App;
+extern Application App;
+Application* app = &App;
 
 static constexpr Body::Hands::HandSide handSide = Body::Hands::HandSide::SENSESHIFT_HAND_SIDE;
-static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTGLOVE_SIZE;
 // clang-format off
-static const OutputLayout (&bhLayout)[bhLayoutSize] = handSide == Body::Hands::HandSide::Left ? BH::TactGloveLeftLayout : BH::TactGloveRightLayout;
+static const auto& bhLayout = handSide == Body::Hands::HandSide::Left ? BH::TactGloveLeftLayout : BH::TactGloveRightLayout;
 // clang-format on
 
 void setupMode()
@@ -37,17 +36,17 @@ void setupMode()
     // Configure PWM pins to their positions on the glove
     // Replace `new PWMOutputWriter(...)` with `nullptr` to disable a specific actuator
     addTactGloveActuators(
-      app->getHapticBody(),
+      app->getVibroBody(),
       handSide,
-      new ActuatorPWM(32), // Thumb
-      new ActuatorPWM(33), // Index
-      new ActuatorPWM(25), // Middle
-      new ActuatorPWM(26), // Ring
-      new ActuatorPWM(27), // Little
-      new ActuatorPWM(14)  // Wrist
+      new LedcOutput(32), // Thumb
+      new LedcOutput(33), // Index
+      new LedcOutput(25), // Middle
+      new LedcOutput(26), // Ring
+      new LedcOutput(27), // Little
+      new LedcOutput(14)  // Wrist
     );
 
-    app->getHapticBody()->setup();
+    app->getVibroBody()->setup();
 
     auto* bhBleConnection = new BLE::Connection(
       {
@@ -56,7 +55,7 @@ void setupMode()
         .serialNumber = BH_SERIAL_NUMBER,
       },
       [](std::string& value) -> void {
-          Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect::Vibro);
+          Decoder::applyPlain(app->getVibroBody(), value, bhLayout, Effect::Vibro);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactosy2.cpp b/firmware/mode_configs/bhaptics/tactosy2.cpp
index 20751691..74cbcdfd 100644
--- a/firmware/mode_configs/bhaptics/tactosy2.cpp
+++ b/firmware/mode_configs/bhaptics/tactosy2.cpp
@@ -7,7 +7,7 @@
 #include "senseshift.h"
 
 #include <senseshift/arduino/input/sensor/analog.hpp>
-#include <senseshift/arduino/output/actuator/pwm.hpp>
+#include <senseshift/arduino/output/ledc.hpp>
 #include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
@@ -22,25 +22,24 @@ using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
-extern SenseShift::SenseShift App;
-SenseShift::SenseShift* app = &App;
+extern Application App;
+Application* app = &App;
 
-static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTOSY2_SIZE;
-static const Position bhLayout[bhLayoutSize] = BH_LAYOUT_TACTOSY2;
+static const std::array<Position, BH_LAYOUT_TACTOSY2_SIZE> bhLayout = { BH_LAYOUT_TACTOSY2 };
 
 void setupMode()
 {
     // Configure PWM pins to their positions on the forearm
-    auto forearmOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
+    auto forearmOutputs = PlaneMapper_Margin::mapMatrixCoordinates<FloatPlane::Actuator>({
       // clang-format off
-      { new ActuatorPWM(32), new ActuatorPWM(33), new ActuatorPWM(25) },
-      { new ActuatorPWM(26), new ActuatorPWM(27), new ActuatorPWM(14) },
+      { new LedcOutput(32), new LedcOutput(33), new LedcOutput(25) },
+      { new LedcOutput(26), new LedcOutput(27), new LedcOutput(14) },
       // clang-format on
     });
 
-    app->getHapticBody()->addTarget(Target::Accessory, new VibroPlane_Closest(forearmOutputs));
+    app->getVibroBody()->addTarget(Target::Accessory, new FloatPlane_Closest(forearmOutputs));
 
-    app->getHapticBody()->setup();
+    app->getVibroBody()->setup();
 
     auto* bhBleConnection = new BLE::Connection(
       {
@@ -49,7 +48,7 @@ void setupMode()
         .serialNumber = BH_SERIAL_NUMBER,
       },
       [](std::string& value) -> void {
-          Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect::Vibro, Target::Accessory);
+          Decoder::applyPlain(app->getVibroBody(), value, bhLayout, Effect::Vibro, Target::Accessory);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactosyf.cpp b/firmware/mode_configs/bhaptics/tactosyf.cpp
index b7667f0f..c8352ee8 100644
--- a/firmware/mode_configs/bhaptics/tactosyf.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyf.cpp
@@ -7,7 +7,7 @@
 #include "senseshift.h"
 
 #include <senseshift/arduino/input/sensor/analog.hpp>
-#include <senseshift/arduino/output/actuator/pwm.hpp>
+#include <senseshift/arduino/output/ledc.hpp>
 #include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
@@ -22,26 +22,25 @@ using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
-extern SenseShift::SenseShift App;
-SenseShift::SenseShift* app = &App;
+extern Application App;
+Application* app = &App;
 
-static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTOSYF_SIZE;
-static const Position bhLayout[bhLayoutSize] = BH_LAYOUT_TACTOSYF;
+static const std::array<Position, BH_LAYOUT_TACTOSYF_SIZE> bhLayout = { BH_LAYOUT_TACTOSYF };
 
 void setupMode()
 {
     // Configure PWM pins to their positions on the feet
-    auto footOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
+    auto footOutputs = PlaneMapper_Margin::mapMatrixCoordinates<FloatPlane::Actuator>({
       // clang-format off
-      { new ActuatorPWM(32) },
-      { new ActuatorPWM(33) },
-      { new ActuatorPWM(25) },
+      { new LedcOutput(32) },
+      { new LedcOutput(33) },
+      { new LedcOutput(25) },
       // clang-format on
     });
 
-    app->getHapticBody()->addTarget(Target::Accessory, new VibroPlane_Closest(footOutputs));
+    app->getVibroBody()->addTarget(Target::Accessory, new FloatPlane_Closest(footOutputs));
 
-    app->getHapticBody()->setup();
+    app->getVibroBody()->setup();
 
     auto* bhBleConnection = new BLE::Connection(
       {
@@ -50,7 +49,7 @@ void setupMode()
         .serialNumber = BH_SERIAL_NUMBER,
       },
       [](std::string& value) -> void {
-          Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect::Vibro, Target::Accessory);
+          Decoder::applyPlain(app->getVibroBody(), value, bhLayout, Effect::Vibro, Target::Accessory);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactosyh.cpp b/firmware/mode_configs/bhaptics/tactosyh.cpp
index 50b46ec9..a4b8574f 100644
--- a/firmware/mode_configs/bhaptics/tactosyh.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyh.cpp
@@ -7,7 +7,7 @@
 #include "senseshift.h"
 
 #include <senseshift/arduino/input/sensor/analog.hpp>
-#include <senseshift/arduino/output/actuator/pwm.hpp>
+#include <senseshift/arduino/output/ledc.hpp>
 #include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
@@ -22,26 +22,25 @@ using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
-extern SenseShift::SenseShift App;
-SenseShift::SenseShift* app = &App;
+extern Application App;
+Application* app = &App;
 
-static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTOSYH_SIZE;
-static const Position bhLayout[bhLayoutSize] = BH_LAYOUT_TACTOSYH;
+static const std::array<Position, BH_LAYOUT_TACTOSYH_SIZE> bhLayout = { BH_LAYOUT_TACTOSYH };
 
 void setupMode()
 {
     // Configure PWM pins to their positions on the hands
-    auto handOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
+    auto handOutputs = PlaneMapper_Margin::mapMatrixCoordinates<FloatPlane::Actuator>({
       // clang-format off
-      { new ActuatorPWM(32) },
-      { new ActuatorPWM(33) },
-      { new ActuatorPWM(25) }
+      { new LedcOutput(32) },
+      { new LedcOutput(33) },
+      { new LedcOutput(25) }
       // clang-format on
     });
 
-    app->getHapticBody()->addTarget(Target::Accessory, new VibroPlane_Closest(handOutputs));
+    app->getVibroBody()->addTarget(Target::Accessory, new FloatPlane_Closest(handOutputs));
 
-    app->getHapticBody()->setup();
+    app->getVibroBody()->setup();
 
     auto* bhBleConnection = new BLE::Connection(
       {
@@ -50,7 +49,7 @@ void setupMode()
         .serialNumber = BH_SERIAL_NUMBER,
       },
       [](std::string& value) -> void {
-          Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect::Vibro, Target::Accessory);
+          Decoder::applyPlain(app->getVibroBody(), value, bhLayout, Effect::Vibro, Target::Accessory);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
index 62fc1e45..f5c566c3 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
@@ -5,10 +5,7 @@
 #include <Wire.h>
 
 #include "senseshift.h"
-
-#include <senseshift/arduino/input/sensor/analog.hpp>
-#include <senseshift/arduino/output/actuator/pwm.hpp>
-#include <senseshift/battery/sensor.hpp>
+#include <senseshift/arduino/output/ledc.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
@@ -22,36 +19,34 @@ using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
-extern SenseShift::SenseShift App;
-SenseShift::SenseShift* app = &App;
+extern Application App;
+Application* app = &App;
 
-static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTSUITX16_SIZE;
-static const OutputLayout bhLayout[BH_LAYOUT_TACTSUITX16_SIZE] = BH_LAYOUT_TACTSUITX16;
+static const std::array<OutputLayout, BH_LAYOUT_TACTSUITX16_SIZE> bhLayout = { BH_LAYOUT_TACTSUITX16 };
 
 // Ouput indices, responsible for x40 => x16 grouping
-static constexpr size_t layoutGroupsSize = BH_LAYOUT_TACTSUITX16_GROUPS_SIZE;
-static const uint8_t layoutGroups[layoutGroupsSize] = BH_LAYOUT_TACTSUITX16_GROUPS;
+static const std::array<std::uint8_t,BH_LAYOUT_TACTSUITX16_GROUPS_SIZE> layoutGroups = BH_LAYOUT_TACTSUITX16_GROUPS;
 
 void setupMode()
 {
     // Configure PWM pins to their positions on the vest
-    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
+    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<FloatPlane::Actuator>({
       // clang-format off
-      { new ActuatorPWM(32), new ActuatorPWM(33), new ActuatorPWM(25), new ActuatorPWM(26) },
-      { new ActuatorPWM(27), new ActuatorPWM(14), new ActuatorPWM(12), new ActuatorPWM(13) },
+      { new LedcOutput(32), new LedcOutput(33), new LedcOutput(25), new LedcOutput(26) },
+      { new LedcOutput(27), new LedcOutput(14), new LedcOutput(12), new LedcOutput(13) },
       // clang-format on
     });
-    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
+    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<FloatPlane::Actuator>({
       // clang-format off
-      { new ActuatorPWM(19), new ActuatorPWM(18), new ActuatorPWM(5), new ActuatorPWM(17) },
-      { new ActuatorPWM(16), new ActuatorPWM(4), new ActuatorPWM(2), new ActuatorPWM(15)  },
+      { new LedcOutput(19), new LedcOutput(18), new LedcOutput(5), new LedcOutput(17) },
+      { new LedcOutput(16), new LedcOutput(4), new LedcOutput(2), new LedcOutput(15)  },
       // clang-format on
     });
 
-    app->getHapticBody()->addTarget(Target::ChestFront, new VibroPlane_Closest(frontOutputs));
-    app->getHapticBody()->addTarget(Target::ChestBack, new VibroPlane_Closest(backOutputs));
+    app->getVibroBody()->addTarget(Target::ChestFront, new FloatPlane_Closest(frontOutputs));
+    app->getVibroBody()->addTarget(Target::ChestBack, new FloatPlane_Closest(backOutputs));
 
-    app->getHapticBody()->setup();
+    app->getVibroBody()->setup();
 
     auto* bhBleConnection = new BLE::Connection(
       {
@@ -60,7 +55,7 @@ void setupMode()
         .serialNumber = BH_SERIAL_NUMBER,
       },
       [](std::string& value) -> void {
-          Decoder::applyVestGrouped(app->getHapticBody(), value, bhLayout, layoutGroups);
+          Decoder::applyVestGrouped(app->getVibroBody(), value, bhLayout, layoutGroups);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
index 8d3383e8..128dcd22 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
@@ -5,10 +5,7 @@
 #include <Wire.h>
 
 #include "senseshift.h"
-
-#include <senseshift/arduino/input/sensor/analog.hpp>
-#include <senseshift/arduino/output/actuator/pca9685.hpp>
-#include <senseshift/battery/sensor.hpp>
+#include <senseshift/arduino/output/pca9685.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
@@ -22,41 +19,39 @@ using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
-extern SenseShift::SenseShift App;
-SenseShift::SenseShift* app = &App;
+extern Application App;
+Application* app = &App;
 
-static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTSUITX16_SIZE;
-static const OutputLayout bhLayout[BH_LAYOUT_TACTSUITX16_SIZE] = BH_LAYOUT_TACTSUITX16;
+static const std::array<OutputLayout, BH_LAYOUT_TACTSUITX16_SIZE> bhLayout = { BH_LAYOUT_TACTSUITX16 };
 
 // Ouput indices, responsible for x40 => x16 grouping
-static constexpr size_t layoutGroupsSize = BH_LAYOUT_TACTSUITX16_GROUPS_SIZE;
-static const uint8_t layoutGroups[layoutGroupsSize] = BH_LAYOUT_TACTSUITX16_GROUPS;
+static const std::array<std::uint8_t,BH_LAYOUT_TACTSUITX16_GROUPS_SIZE> layoutGroups = BH_LAYOUT_TACTSUITX16_GROUPS;
 
 void setupMode()
 {
     // Configure the PCA9685
-    auto pwm = new Adafruit_PWMServoDriver(0x40);
+    auto* pwm = new Adafruit_PWMServoDriver(0x40);
     pwm->begin();
     pwm->setPWMFreq(PWM_FREQUENCY);
 
     // Assign the pins on the configured PCA9685 to positions on the vest
-    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
+    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<FloatPlane::Actuator>({
       // clang-format off
-      { new ActuatorPCA9685(pwm, 0), new ActuatorPCA9685(pwm, 1), new ActuatorPCA9685(pwm, 2), new ActuatorPCA9685(pwm, 3) },
-      { new ActuatorPCA9685(pwm, 4), new ActuatorPCA9685(pwm, 5), new ActuatorPCA9685(pwm, 6), new ActuatorPCA9685(pwm, 7) },
+      { new PCA9685Output(pwm, 0), new PCA9685Output(pwm, 1), new PCA9685Output(pwm, 2), new PCA9685Output(pwm, 3) },
+      { new PCA9685Output(pwm, 4), new PCA9685Output(pwm, 5), new PCA9685Output(pwm, 6), new PCA9685Output(pwm, 7) },
       // clang-format on
     });
-    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
+    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<FloatPlane::Actuator>({
       // clang-format off
-      { new ActuatorPCA9685(pwm, 8),  new ActuatorPCA9685(pwm, 9),  new ActuatorPCA9685(pwm, 10), new ActuatorPCA9685(pwm, 11) },
-      { new ActuatorPCA9685(pwm, 12), new ActuatorPCA9685(pwm, 13), new ActuatorPCA9685(pwm, 14), new ActuatorPCA9685(pwm, 15) },
+      { new PCA9685Output(pwm, 8),  new PCA9685Output(pwm, 9),  new PCA9685Output(pwm, 10), new PCA9685Output(pwm, 11) },
+      { new PCA9685Output(pwm, 12), new PCA9685Output(pwm, 13), new PCA9685Output(pwm, 14), new PCA9685Output(pwm, 15) },
       // clang-format on
     });
 
-    app->getHapticBody()->addTarget(Target::ChestFront, new VibroPlane_Closest(frontOutputs));
-    app->getHapticBody()->addTarget(Target::ChestBack, new VibroPlane_Closest(backOutputs));
+    app->getVibroBody()->addTarget(Target::ChestFront, new FloatPlane_Closest(frontOutputs));
+    app->getVibroBody()->addTarget(Target::ChestBack, new FloatPlane_Closest(backOutputs));
 
-    app->getHapticBody()->setup();
+    app->getVibroBody()->setup();
 
     auto* bhBleConnection = new BLE::Connection(
       {
@@ -65,7 +60,7 @@ void setupMode()
         .serialNumber = BH_SERIAL_NUMBER,
       },
       [](std::string& value) -> void {
-          Decoder::applyVestGrouped(app->getHapticBody(), value, bhLayout, layoutGroups);
+          Decoder::applyVestGrouped(app->getVibroBody(), value, bhLayout, layoutGroups);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
index 13fce314..23983157 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
@@ -5,11 +5,8 @@
 #include <Wire.h>
 
 #include "senseshift.h"
-
-#include <senseshift/arduino/input/sensor/analog.hpp>
-#include <senseshift/arduino/output/actuator/pca9685.hpp>
-#include <senseshift/arduino/output/actuator/pwm.hpp>
-#include <senseshift/battery/sensor.hpp>
+#include <senseshift/arduino/output/ledc.hpp>
+#include <senseshift/arduino/output/pca9685.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
@@ -23,11 +20,10 @@ using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
-extern SenseShift::SenseShift App;
-SenseShift::SenseShift* app = &App;
+extern Application App;
+Application* app = &App;
 
-static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTSUITX40_SIZE;
-static const OutputLayout bhLayout[bhLayoutSize] = BH_LAYOUT_TACTSUITX40;
+static const std::array<OutputLayout, BH_LAYOUT_TACTSUITX40_SIZE> bhLayout = { BH_LAYOUT_TACTSUITX40 };
 
 void setupMode()
 {
@@ -42,29 +38,29 @@ void setupMode()
 
     // Assign the pins on the configured PCA9685s and PWM pins to locations on the
     // vest
-    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
+    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<FloatPlane::Actuator>({
       // clang-format off
-      { new ActuatorPCA9685(pwm0, 0),  new ActuatorPCA9685(pwm0, 1),  new ActuatorPCA9685(pwm0, 2),  new ActuatorPCA9685(pwm0, 3)  },
-      { new ActuatorPCA9685(pwm0, 4),  new ActuatorPCA9685(pwm0, 5),  new ActuatorPCA9685(pwm0, 6),  new ActuatorPCA9685(pwm0, 7)  },
-      { new ActuatorPCA9685(pwm0, 8),  new ActuatorPCA9685(pwm0, 9),  new ActuatorPCA9685(pwm0, 10), new ActuatorPCA9685(pwm0, 11) },
-      { new ActuatorPCA9685(pwm0, 12), new ActuatorPCA9685(pwm0, 13), new ActuatorPCA9685(pwm0, 14), new ActuatorPCA9685(pwm0, 15) },
-      { new ActuatorPWM(32),           new ActuatorPWM(33),           new ActuatorPWM(25),           new ActuatorPWM(26)           },
+      { new PCA9685Output(pwm0, 0),  new PCA9685Output(pwm0, 1),  new PCA9685Output(pwm0, 2),  new PCA9685Output(pwm0, 3)  },
+      { new PCA9685Output(pwm0, 4),  new PCA9685Output(pwm0, 5),  new PCA9685Output(pwm0, 6),  new PCA9685Output(pwm0, 7)  },
+      { new PCA9685Output(pwm0, 8),  new PCA9685Output(pwm0, 9),  new PCA9685Output(pwm0, 10), new PCA9685Output(pwm0, 11) },
+      { new PCA9685Output(pwm0, 12), new PCA9685Output(pwm0, 13), new PCA9685Output(pwm0, 14), new PCA9685Output(pwm0, 15) },
+      { new LedcOutput(32),          new LedcOutput(33),          new LedcOutput(25),          new LedcOutput(26)          },
       // clang-format on
     });
-    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
+    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<FloatPlane::Actuator>({
       // clang-format off
-      { new ActuatorPCA9685(pwm1, 0),  new ActuatorPCA9685(pwm1, 1),  new ActuatorPCA9685(pwm1, 2),  new ActuatorPCA9685(pwm1, 3)  },
-      { new ActuatorPCA9685(pwm1, 4),  new ActuatorPCA9685(pwm1, 5),  new ActuatorPCA9685(pwm1, 6),  new ActuatorPCA9685(pwm1, 7)  },
-      { new ActuatorPCA9685(pwm1, 8),  new ActuatorPCA9685(pwm1, 9),  new ActuatorPCA9685(pwm1, 10), new ActuatorPCA9685(pwm1, 11) },
-      { new ActuatorPCA9685(pwm1, 12), new ActuatorPCA9685(pwm1, 13), new ActuatorPCA9685(pwm1, 14), new ActuatorPCA9685(pwm1, 15) },
-      { new ActuatorPWM(27),           new ActuatorPWM(14),           new ActuatorPWM(12),           new ActuatorPWM(13)           },
+      { new PCA9685Output(pwm1, 0),  new PCA9685Output(pwm1, 1),  new PCA9685Output(pwm1, 2),  new PCA9685Output(pwm1, 3)  },
+      { new PCA9685Output(pwm1, 4),  new PCA9685Output(pwm1, 5),  new PCA9685Output(pwm1, 6),  new PCA9685Output(pwm1, 7)  },
+      { new PCA9685Output(pwm1, 8),  new PCA9685Output(pwm1, 9),  new PCA9685Output(pwm1, 10), new PCA9685Output(pwm1, 11) },
+      { new PCA9685Output(pwm1, 12), new PCA9685Output(pwm1, 13), new PCA9685Output(pwm1, 14), new PCA9685Output(pwm1, 15) },
+      { new LedcOutput(27),          new LedcOutput(14),          new LedcOutput(12),          new LedcOutput(13)          },
       // clang-format on
     });
 
-    app->getHapticBody()->addTarget(Target::ChestFront, new VibroPlane_Closest(frontOutputs));
-    app->getHapticBody()->addTarget(Target::ChestBack, new VibroPlane_Closest(backOutputs));
+    app->getVibroBody()->addTarget(Target::ChestFront, new FloatPlane_Closest(frontOutputs));
+    app->getVibroBody()->addTarget(Target::ChestBack, new FloatPlane_Closest(backOutputs));
 
-    app->getHapticBody()->setup();
+    app->getVibroBody()->setup();
 
     auto* bhBleConnection = new BLE::Connection(
       {
@@ -73,7 +69,7 @@ void setupMode()
         .serialNumber = BH_SERIAL_NUMBER,
       },
       [](std::string& value) -> void {
-          Decoder::applyVest(app->getHapticBody(), value, bhLayout);
+          Decoder::applyVest(app->getVibroBody(), value, bhLayout);
       },
       app
     );
diff --git a/firmware/mode_configs/bhaptics/tactvisor.cpp b/firmware/mode_configs/bhaptics/tactvisor.cpp
index f7e7ab44..1c3f02cb 100644
--- a/firmware/mode_configs/bhaptics/tactvisor.cpp
+++ b/firmware/mode_configs/bhaptics/tactvisor.cpp
@@ -7,7 +7,7 @@
 #include "senseshift.h"
 
 #include <senseshift/arduino/input/sensor/analog.hpp>
-#include <senseshift/arduino/output/actuator/pwm.hpp>
+#include <senseshift/arduino/output/ledc.hpp>
 #include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
@@ -22,24 +22,23 @@ using namespace SenseShift::Battery;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
-extern SenseShift::SenseShift App;
-SenseShift::SenseShift* app = &App;
+extern Application App;
+Application* app = &App;
 
-static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTVISOR_SIZE;
-static const Position bhLayout[bhLayoutSize] = BH_LAYOUT_TACTVISOR;
+static const std::array<Position, BH_LAYOUT_TACTVISOR_SIZE> bhLayout = { BH_LAYOUT_TACTVISOR };
 
 void setupMode()
 {
     // Configure PWM pins to their positions on the face
-    auto faceOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
+    auto faceOutputs = PlaneMapper_Margin::mapMatrixCoordinates<FloatPlane::Actuator>({
       // clang-format off
-      { new ActuatorPWM(32), new ActuatorPWM(33), new ActuatorPWM(25), new ActuatorPWM(26) },
+      { new LedcOutput(32), new LedcOutput(33), new LedcOutput(25), new LedcOutput(26) },
       // clang-format on
     });
 
-    app->getHapticBody()->addTarget(Target::FaceFront, new VibroPlane_Closest(faceOutputs));
+    app->getVibroBody()->addTarget(Target::FaceFront, new FloatPlane_Closest(faceOutputs));
 
-    app->getHapticBody()->setup();
+    app->getVibroBody()->setup();
 
     auto* bhBleConnection = new BLE::Connection(
       {
@@ -48,7 +47,7 @@ void setupMode()
         .serialNumber = BH_SERIAL_NUMBER,
       },
       [](std::string& value) -> void {
-          Decoder::applyPlain(app->getHapticBody(), value, bhLayout, Effect::Vibro, Target::FaceFront);
+          Decoder::applyPlain(app->getVibroBody(), value, bhLayout, Effect::Vibro, Target::FaceFront);
       },
       app
     );
diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp
index 3ebfca56..bded27d3 100644
--- a/firmware/mode_configs/opengloves/opengloves.cpp
+++ b/firmware/mode_configs/opengloves/opengloves.cpp
@@ -1,7 +1,7 @@
 #include <og_constants.hpp>
 #include <opengloves_task.hpp>
 #include <senseshift/arduino/input/sensor/analog.hpp>
-#include <senseshift/arduino/input/sensor/digital.hpp>
+#include <senseshift/arduino/input/sensor/binary.hpp>
 #include <senseshift/arduino/output/actuator/servo.hpp>
 #include <senseshift/calibration.hpp>
 #include <senseshift/input/sensor.hpp>
diff --git a/firmware/senseshift.cpp b/firmware/senseshift.cpp
index 3ba2b1f7..d6445325 100644
--- a/firmware/senseshift.cpp
+++ b/firmware/senseshift.cpp
@@ -6,27 +6,29 @@
 #include <Arduino.h>
 #endif
 
-#include <senseshift/logging.hpp>
+#include <senseshift/core/logging.hpp>
 
 namespace SenseShift {
-    SenseShift::SenseShift()
+    static const char *const TAG = "application";
+
+    Application::Application()
     {
-        this->pHapticBody = new Body::Haptics::HapticBody();
+        this->vibro_body_ = new Body::Haptics::FloatBody ();
     }
 
-    void SenseShift::postEvent(const IEvent* event)
+    void Application::postEvent(const IEvent* event)
     {
-        log_i("Event dispatched at %u: %s (%p)", millis(), event->eventName.c_str(), event);
+        LOG_I(TAG, "Event dispatched at %u: %s (%p)", millis(), event->eventName.c_str(), event);
 
-        for (auto* listener : this->eventListeners) {
+        for (const auto* listener : this->event_listeners_) {
             listener->handleEvent(event);
         }
 
         delete event;
     }
 
-    void SenseShift::addEventListener(const IEventListener* listener)
+    void Application::addEventListener(const IEventListener* listener)
     {
-        this->eventListeners.push_back(listener);
+        this->event_listeners_.push_back(listener);
     }
 } // namespace SenseShift
diff --git a/include/senseshift.h b/include/senseshift.h
index 95c5603e..ec98103d 100644
--- a/include/senseshift.h
+++ b/include/senseshift.h
@@ -9,18 +9,18 @@
 #include <vector>
 
 namespace SenseShift {
-    class SenseShift final : public IEventDispatcher {
-      private:
-        std::vector<const IEventListener*> eventListeners{};
-        Body::Haptics::HapticBody* pHapticBody;
-        ::SenseShift::Battery::IBatterySensor* battery;
-
+    class Application final : public IEventDispatcher {
       public:
-        SenseShift();
+        Application();
 
-        Body::Haptics::HapticBody* getHapticBody() { return this->pHapticBody; };
+        [[nodiscard]] auto getVibroBody() const -> Body::Haptics::FloatBody* { return this->vibro_body_; };
 
         void postEvent(const IEvent* event) override;
         void addEventListener(const IEventListener* listener) override;
+
+      private:
+        std::vector<const IEventListener*> event_listeners_{};
+        Body::Haptics::FloatBody* vibro_body_;
+        Battery::IBatterySensor* battery_ = nullptr;
     };
 } // namespace SenseShift
diff --git a/lib/arduino/library.json b/lib/arduino/library.json
index 8601c003..8434426d 100644
--- a/lib/arduino/library.json
+++ b/lib/arduino/library.json
@@ -7,7 +7,6 @@
         "adafruit/Adafruit Unified Sensor": "^1.1.4",
         "adafruit/Adafruit PWM Servo Driver Library": "^2.4.0",
         "adafruit/Adafruit INA219": "^1.2.1",
-        "sparkfun/SparkFun MAX1704x Fuel Gauge Arduino Library": "^1.0.4",
-        "madhephaestus/ESP32Servo": "^0.13.0"
+        "sparkfun/SparkFun MAX1704x Fuel Gauge Arduino Library": "^1.0.4"
     }
 }
diff --git a/lib/arduino/senseshift/arduino/components/serial_plotter.cpp b/lib/arduino/senseshift/arduino/components/serial_plotter.cpp
deleted file mode 100644
index 8cd48978..00000000
--- a/lib/arduino/senseshift/arduino/components/serial_plotter.cpp
+++ /dev/null
@@ -1,28 +0,0 @@
-#include "senseshift/arduino/components/serial_plotter.hpp"
-
-namespace SenseShift::Arduino {
-    struct PlaneVisitor {
-        const SenseShift::Body::Haptics::Target target;
-        HardwareSerial* serial;
-
-        void operator()(const SenseShift::Body::Haptics::VibroPlane* plane) const
-        {
-            for (const auto& [position, state] : *(plane->getActuatorStates())) {
-                this->serial->printf("Output[%u][%ux%u]:%u, ", this->target, position.x, position.y, state.intensity);
-            }
-        }
-    };
-
-    template<typename _Tp>
-    void SerialPlotter_OutputStates<_Tp>::run()
-    {
-        while (true) {
-            for (const auto& [target, plane] : *output->getTargets()) {
-                std::visit(PlaneVisitor{ target, this->serial }, plane);
-            }
-            this->serial->println();
-
-            delay(this->sampleRate);
-        }
-    }
-} // namespace SenseShift::Arduino
diff --git a/lib/arduino/senseshift/arduino/components/serial_plotter.hpp b/lib/arduino/senseshift/arduino/components/serial_plotter.hpp
deleted file mode 100644
index 2cc04d92..00000000
--- a/lib/arduino/senseshift/arduino/components/serial_plotter.hpp
+++ /dev/null
@@ -1,65 +0,0 @@
-#pragma once
-
-#include <senseshift/body/haptics/body.hpp>
-#include <senseshift/freertos/task.hpp>
-
-#include <HardwareSerial.h>
-
-#ifndef SERIAL_PLOTTER_BAUD
-#define SERIAL_PLOTTER_BAUD 115200
-#endif // SERIAL_PLOTTER_BAUD
-
-namespace SenseShift::Arduino {
-    /**
-     * Component, that prints the current state of the output to the serial port in Arduino's Serial Plotter format
-     *
-     * @tparam _Tp the type of the serial port
-     */
-    template<class _Tp>
-    class SerialPlotter_OutputStates : public ::SenseShift::FreeRTOS::Task<SerialPlotter_OutputStates<_Tp>> {
-        static_assert(
-          std::is_base_of<Print, _Tp>::value,
-          "SerialPlotter_OutputStates only can be used with types, that inherit from Print"
-        );
-        friend class ::SenseShift::FreeRTOS::Task<SerialPlotter_OutputStates<_Tp>>;
-
-      private:
-        _Tp* serial;
-        ::SenseShift::Body::Haptics::HapticBody* output;
-        uint32_t sampleRate;
-
-        void setup(void){};
-        void run(void);
-
-      public:
-        SerialPlotter_OutputStates(
-          _Tp& serial,
-          ::SenseShift::Body::Haptics::HapticBody* output,
-          uint32_t sampleRate,
-          ::SenseShift::FreeRTOS::TaskConfig taskConfig = { "Serial Plotter", 2048, 1, tskNO_AFFINITY }
-        ) :
-          ::SenseShift::FreeRTOS::Task<SerialPlotter_OutputStates<_Tp>>(taskConfig),
-          serial(&serial),
-          output(output),
-          sampleRate(sampleRate){};
-        SerialPlotter_OutputStates(_Tp& serial, ::SenseShift::Body::Haptics::HapticBody* output) :
-          SerialPlotter_OutputStates(serial, output, 100){};
-
-        void begin() override
-        {
-            this->setup();
-            ::SenseShift::FreeRTOS::Task<SerialPlotter_OutputStates<_Tp>>::begin();
-        };
-    };
-
-    /**
-     * Specialized setup for HardwareSerial
-     */
-    template<>
-    inline void SerialPlotter_OutputStates<HardwareSerial>::setup()
-    {
-        this->serial->begin(SERIAL_PLOTTER_BAUD);
-    }
-
-    template class SerialPlotter_OutputStates<HardwareSerial>;
-} // namespace SenseShift::Arduino
diff --git a/lib/arduino/senseshift/arduino/input/sensor/analog.hpp b/lib/arduino/senseshift/arduino/input/sensor/analog.hpp
index 4dabd41b..122b1906 100644
--- a/lib/arduino/senseshift/arduino/input/sensor/analog.hpp
+++ b/lib/arduino/senseshift/arduino/input/sensor/analog.hpp
@@ -5,28 +5,29 @@
 #include <Arduino.h>
 
 namespace SenseShift::Arduino::Input {
-    template<bool invert = false>
-    class AnalogSensor : public ::SenseShift::Input::ISimpleSensor<uint16_t> {
-      private:
-        uint8_t pin;
+    using IAnalogSensor = ::SenseShift::Input::ISimpleSensor<uint16_t>;
+
+    template<bool Invert = false>
+    class AnalogSensor : public IAnalogSensor {
+        uint8_t pin_;
 
       public:
-        AnalogSensor(uint8_t pin) : pin(pin) {}
+        AnalogSensor(const uint8_t pin) : pin_(pin) {}
 
-        void init() override { pinMode(this->pin, INPUT); };
+        void init() override { pinMode(this->pin_, INPUT); };
 
-        uint16_t getValue() override;
+        [[nodiscard]] auto getValue() -> uint16_t override;
     };
 
     template<>
-    uint16_t AnalogSensor<false>::getValue()
+    [[nodiscard]] inline auto AnalogSensor<false>::getValue() -> uint16_t
     {
-        return analogRead(this->pin);
+        return analogRead(this->pin_);
     }
 
     template<>
-    uint16_t AnalogSensor<true>::getValue()
+    [[nodiscard]] inline auto AnalogSensor<true>::getValue() -> uint16_t
     {
-        return ANALOG_MAX - analogRead(this->pin);
+        return ANALOG_MAX - analogRead(this->pin_);
     }
 } // namespace SenseShift::Arduino::Input
diff --git a/lib/arduino/senseshift/arduino/input/sensor/binary.hpp b/lib/arduino/senseshift/arduino/input/sensor/binary.hpp
new file mode 100644
index 00000000..44783b98
--- /dev/null
+++ b/lib/arduino/senseshift/arduino/input/sensor/binary.hpp
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <senseshift/input/sensor.hpp>
+
+#include <Arduino.h>
+
+namespace SenseShift::Arduino::Input {
+    template<bool Invert = false>
+    class BinarySensor : public IBinarySensor {
+        uint8_t pin_;
+
+      public:
+        BinarySensor(const uint8_t pin) : pin_(pin) {}
+
+        void init() override { pinMode(this->pin_, INPUT_PULLUP); };
+
+        [[nodiscard]] auto getValue() -> bool override;
+    };
+
+    template<>
+    [[nodiscard]] inline auto BinarySensor<false>::getValue() -> bool {
+        return digitalRead(this->pin_) == LOW;
+    }
+
+    template<>
+    [[nodiscard]] inline auto BinarySensor<true>::getValue() -> bool {
+        return digitalRead(this->pin_) == HIGH;
+    }
+} // namespace SenseShift::Arduino::Input
diff --git a/lib/arduino/senseshift/arduino/input/sensor/digital.hpp b/lib/arduino/senseshift/arduino/input/sensor/digital.hpp
deleted file mode 100644
index 3cb68b3f..00000000
--- a/lib/arduino/senseshift/arduino/input/sensor/digital.hpp
+++ /dev/null
@@ -1,34 +0,0 @@
-#pragma once
-
-#include <senseshift/input/sensor.hpp>
-
-#include <Arduino.h>
-
-namespace SenseShift::Arduino::Input {
-    using IDigitalSensor = ::SenseShift::Input::ISimpleSensor<bool>;
-
-    template<bool invert = false>
-    class DigitalSensor : public IDigitalSensor {
-      private:
-        uint8_t pin;
-
-      public:
-        DigitalSensor(uint8_t pin) : pin(pin) {}
-
-        void init() override { pinMode(this->pin, INPUT_PULLUP); };
-
-        bool getValue() override;
-    };
-
-    template<>
-    bool DigitalSensor<false>::getValue()
-    {
-        return digitalRead(this->pin) == LOW;
-    }
-
-    template<>
-    bool DigitalSensor<true>::getValue()
-    {
-        return digitalRead(this->pin) == HIGH;
-    }
-} // namespace SenseShift::Arduino::Input
diff --git a/lib/arduino/senseshift/arduino/output/actuator/pca9685.hpp b/lib/arduino/senseshift/arduino/output/actuator/pca9685.hpp
deleted file mode 100644
index 4d28f92d..00000000
--- a/lib/arduino/senseshift/arduino/output/actuator/pca9685.hpp
+++ /dev/null
@@ -1,26 +0,0 @@
-#pragma once
-
-#include <senseshift/output/actuator.hpp>
-#include <senseshift/utility.hpp>
-
-#include <Adafruit_PWMServoDriver.h>
-#include <Arduino.h>
-#include <Wire.h>
-
-namespace SenseShift::Arduino::Output {
-    class ActuatorPCA9685 : public ::SenseShift::Output::IActuator<std::uint16_t> {
-      public:
-        static inline constexpr const std::uint16_t MAX_INTENSITY = 4095;
-
-        ActuatorPCA9685(Adafruit_PWMServoDriver* driver, const std::uint8_t num) : driver(driver), num(num){};
-
-        void writeOutput(std::uint16_t intensity) override
-        {
-            this->driver->setPin(this->num, ::SenseShift::simpleMap<std::uint16_t>(intensity, MAX_INTENSITY, 4095));
-        }
-
-      private:
-        Adafruit_PWMServoDriver* driver;
-        std::uint8_t num;
-    };
-} // namespace SenseShift::Arduino::Output
diff --git a/lib/arduino/senseshift/arduino/output/actuator/pwm.hpp b/lib/arduino/senseshift/arduino/output/actuator/pwm.hpp
deleted file mode 100644
index 5f2a595e..00000000
--- a/lib/arduino/senseshift/arduino/output/actuator/pwm.hpp
+++ /dev/null
@@ -1,47 +0,0 @@
-#pragma once
-
-#include <senseshift/output/actuator.hpp>
-#include <senseshift/utility.hpp>
-
-#include <Arduino.h>
-
-namespace SenseShift::Arduino::Output {
-    class ActuatorPWM : public ::SenseShift::Output::IActuator<std::uint16_t> {
-      public:
-        static inline constexpr const std::uint16_t MAX_INTENSITY = 4095;
-
-        ActuatorPWM(const std::uint8_t pin, const double freq = 60, const std::uint8_t resolution = 12) :
-          pin(pin), freq(freq), resolution(resolution){};
-
-        void setup() override
-        {
-            this->chan = CHANNELS++;
-
-#if defined(ESP32)
-            ledcSetup(this->chan, this->freq, this->resolution);
-            ledcAttachPin(this->pin, this->chan);
-#else
-            pinMode(this->pin, OUTPUT);
-#endif
-        }
-
-        void writeOutput(std::uint16_t intensity) override
-        {
-#if defined(ESP32)
-            ledcWrite(
-              chan,
-              ::SenseShift::simpleMap<std::uint16_t>(intensity, MAX_INTENSITY, (1 << this->resolution) - 1)
-            );
-#else
-            // Arduino only supports 8-bit PWM
-            analogWrite(this->pin, ::SenseShift::simpleMap<std::uint16_t>(intensity, MAX_INTENSITY, (1 << 8) - 1));
-#endif
-        }
-
-      private:
-        static inline std::uint8_t CHANNELS = 0;
-        std::uint8_t pin, chan;
-        double freq;
-        std::uint8_t resolution;
-    };
-}; // namespace SenseShift::Arduino::Output
diff --git a/lib/arduino/senseshift/arduino/output/analog.hpp b/lib/arduino/senseshift/arduino/output/analog.hpp
new file mode 100644
index 00000000..bcefae41
--- /dev/null
+++ b/lib/arduino/senseshift/arduino/output/analog.hpp
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <cstdint>
+
+#include <Arduino.h>
+
+#include <senseshift/output/output.hpp>
+
+namespace SenseShift::Arduino::Output {
+    /// Arduino analog output
+    class AnalogOutput : public ::SenseShift::Output::FloatOutput {
+      public:
+        static inline constexpr std::uint16_t MAX_INTENSITY = 255;
+
+        explicit AnalogOutput(const std::uint8_t pin) : pin_(pin) {
+        }
+
+        void init() override {
+            pinMode(this->pin_, OUTPUT);
+        }
+
+        void writeState(const float value) override {
+            analogWrite(this->pin_, value * MAX_INTENSITY);
+        }
+
+      private:
+        uint8_t pin_;
+    };
+} // namespace SenseShift::Arduino::Output
diff --git a/lib/arduino/senseshift/arduino/output/pca9685.hpp b/lib/arduino/senseshift/arduino/output/pca9685.hpp
new file mode 100644
index 00000000..66798efd
--- /dev/null
+++ b/lib/arduino/senseshift/arduino/output/pca9685.hpp
@@ -0,0 +1,35 @@
+#pragma once
+
+#include <cstdint>
+
+#include <senseshift/output/output.hpp>
+#include <senseshift/utility.hpp>
+
+#include <Adafruit_PWMServoDriver.h>
+#include <Arduino.h>
+#include <Wire.h>
+
+namespace SenseShift::Arduino::Output {
+    class PCA9685Output : public ::SenseShift::Output::FloatOutput {
+      public:
+        static inline constexpr std::uint16_t MAX_INTENSITY = 4095;
+
+        PCA9685Output(Adafruit_PWMServoDriver* driver, const std::uint8_t num) : driver_(driver), channel_(num){};
+
+        void init() override {
+            this->driver_->begin();
+        }
+
+        void writeState(const ValueType intensity) override
+        {
+            this->driver_->setPin(
+                this->channel_,
+                intensity * MAX_INTENSITY
+            );
+        }
+
+      private:
+        Adafruit_PWMServoDriver* driver_;
+        std::uint8_t channel_;
+    };
+} // namespace SenseShift::Arduino::Output
diff --git a/lib/arduino_esp32/senseshift/arduino/output/ledc.hpp b/lib/arduino_esp32/senseshift/arduino/output/ledc.hpp
new file mode 100644
index 00000000..a74eba96
--- /dev/null
+++ b/lib/arduino_esp32/senseshift/arduino/output/ledc.hpp
@@ -0,0 +1,57 @@
+#pragma once
+
+#include <cstdint>
+
+#include <esp32-hal-ledc.h>
+
+#include <senseshift/output/output.hpp>
+#include <senseshift/core/logging.hpp>
+
+namespace SenseShift::Arduino::Output {
+    static const char *const TAG = "output.ledc";
+
+    /// Arduino analog output
+    class LedcOutput : public ::SenseShift::Output::FloatOutput {
+    public:
+        explicit LedcOutput(
+          const std::uint8_t pin,
+          const std::uint8_t analog_resolution = 12,
+          const std::uint32_t analog_frequency = 60
+        ) : pin_(pin), analog_resolution_(analog_resolution), analog_frequency_(analog_frequency) {}
+
+        void init() override {
+            pinMode(this->pin_, OUTPUT);
+            this->channel_ = findChannel(this->pin_);
+
+            LOG_D(TAG, "GPIO %d - Setting up Channel %d", this->pin_, this->channel_);
+            if(ledcSetup(this->channel_, this->analog_frequency_, this->analog_resolution_) == 0){
+                LOG_E(TAG, "setup failed (freq = %u, resolution = %u). Try setting different resolution or frequency", this->analog_frequency_, this->analog_resolution_);
+                return;
+            }
+            ledcAttachPin(this->pin_, this->channel_);
+        }
+
+        [[nodiscard]] auto getMaxValue() const -> std::uint32_t {
+            return (1 << analog_resolution_) - 1;
+        }
+
+        void writeState(const float value) override {
+            const auto duty = static_cast<std::uint32_t>(value * this->getMaxValue());
+            LOG_V(TAG, "GPIO %d - Writing %d to Channel %d", this->pin_, duty, this->channel_);
+            ledcWrite(this->channel_, duty);
+        };
+
+    protected:
+        static auto findChannel(std::uint8_t /*pin*/) -> std::int8_t {
+            return CHANNELS++;
+        }
+
+    private:
+        static inline std::uint8_t CHANNELS = 0;
+
+        std::uint8_t pin_;
+        std::int8_t channel_ = -1;
+        std::uint8_t analog_resolution_;
+        std::uint32_t analog_frequency_;
+    };
+} // namespace SenseShift::Arduino::Output
diff --git a/lib/battery/senseshift/battery.hpp b/lib/battery/senseshift/battery.hpp
index a89455fb..4de3c867 100644
--- a/lib/battery/senseshift/battery.hpp
+++ b/lib/battery/senseshift/battery.hpp
@@ -1,10 +1,64 @@
 #pragma once
 
-#include <senseshift/events.hpp>
-
 #include <cstdint>
 
+#include <frozen/map.h>
+
+#include <senseshift/events.hpp>
+
 namespace SenseShift::Battery {
+    // Source: https://blog.ampow.com/lipo-voltage-chart/
+    inline static const auto voltageToPercents_1s = frozen::make_map<float, float>({
+      // clang-format off
+        { 4.2, 1.0 },
+        { 4.15, 0.95 },
+        { 4.11, 0.9 },
+        { 4.08, 0.85 },
+        { 4.02, 0.8 },
+        { 3.98, 0.75 },
+        { 3.95, 0.7 },
+        { 3.91, 0.65 },
+        { 3.87, 0.6 },
+        { 3.85, 0.55 },
+        { 3.84, 0.5 },
+        { 3.82, 0.45 },
+        { 3.8, 0.4 },
+        { 3.79, 0.35 },
+        { 3.77, 0.3 },
+        { 3.75, 0.25 },
+        { 3.73, 0.2 },
+        { 3.71, 0.15 },
+        { 3.69, 0.1 },
+        { 3.61, 0.05 },
+        { 3.27, 0.0 },
+      // clang-format on
+    });
+    inline static const auto voltageToPercents_2s = frozen::make_map<float, float>({
+      // clang-format off
+        { 8.4, 1.0 },
+        { 8.3, 0.95 },
+        { 8.22, 0.9 },
+        { 8.16, 0.85 },
+        { 8.05, 0.8 },
+        { 7.97, 0.75 },
+        { 7.91, 0.7 },
+        { 7.83, 0.65 },
+        { 7.75, 0.6 },
+        { 7.71, 0.55 },
+        { 7.67, 0.5 },
+        { 7.63, 0.45 },
+        { 7.59, 0.4 },
+        { 7.57, 0.35 },
+        { 7.53, 0.3 },
+        { 7.49, 0.25 },
+        { 7.45, 0.2 },
+        { 7.41, 0.15 },
+        { 7.37, 0.1 },
+        { 7.22, 0.05 },
+        { 6.55, 0.0 },
+      // clang-format on
+    });
+
     struct BatteryState {
         uint8_t level;
     };
diff --git a/lib/battery/senseshift/battery/sensor.hpp b/lib/battery/senseshift/battery/sensor.hpp
index 01b1ae96..ff05f5af 100644
--- a/lib/battery/senseshift/battery/sensor.hpp
+++ b/lib/battery/senseshift/battery/sensor.hpp
@@ -20,7 +20,7 @@ namespace SenseShift::Battery {
       public:
         NaiveBatterySensor(::SenseShift::Input::ISimpleSensor<uint16_t>* sensor) : sensor(sensor){};
 
-        BatteryState getValue() override
+        [[nodiscard]] auto getValue() -> BatteryState override
         {
             return { .level =
                        static_cast<uint8_t>(::SenseShift::simpleMap<uint16_t>(this->sensor->getValue(), 4095, 255)) };
@@ -28,7 +28,7 @@ namespace SenseShift::Battery {
         void init() override { this->sensor->init(); }
 
       private:
-        ::SenseShift::Input::ISimpleSensor<uint16_t>* sensor;
+        ISimpleSensor<uint16_t> * sensor;
     };
 
     class BatterySensor : public ::SenseShift::Input::MemoizedSensor<::SenseShift::Battery::BatteryState> {
@@ -40,7 +40,7 @@ namespace SenseShift::Battery {
         void tick() override
         {
             this->::SenseShift::Input::MemoizedSensor<::SenseShift::Battery::BatteryState>::tick();
-            this->eventDispatcher->postEvent(new ::SenseShift::Battery::BatteryLevelEvent(this->value));
+            this->eventDispatcher->postEvent(new ::SenseShift::Battery::BatteryLevelEvent(this->getValue()));
         }
 
       private:
diff --git a/lib/bhaptics/senseshift/bh/devices.hpp b/lib/bhaptics/senseshift/bh/devices.hpp
index f4c168de..7b051c85 100644
--- a/lib/bhaptics/senseshift/bh/devices.hpp
+++ b/lib/bhaptics/senseshift/bh/devices.hpp
@@ -1,6 +1,11 @@
 #pragma once
 
+#include <array>
+#include <tuple>
+
 #include <hand_interface.hpp>
+
+#include <senseshift/body/haptics/plane.hpp>
 #include <senseshift/body/haptics/body.hpp>
 
 #pragma region BH_DEVICE_TACTSUITX40
@@ -18,8 +23,8 @@
 // X * Y for front and back
 #define BH_LAYOUT_TACTSUITX40_SIZE 40
 // clang-format off
-#define BH_LAYOUT_TACTSUITX40 {                                                \
-    /* Front, left part */                                                     \
+#define BH_LAYOUT_TACTSUITX40 {                                              \
+    /* Front, left part */                                                   \
     /*  0 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 0) }, \
     /*  1 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 0) }, \
     /*  2 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 1) }, \
@@ -30,8 +35,8 @@
     /*  7 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 3) }, \
     /*  8 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 4) }, \
     /*  9 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 4) }, \
-                                                                               \
-    /* Back */                                                                 \
+                                                                             \
+    /* Back */                                                               \
     /* 11 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 0) },  \
     /* 11 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 0) },  \
     /* 12 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 1) },  \
@@ -42,7 +47,7 @@
     /* 17 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 3) },  \
     /* 18 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(0, 4) },  \
     /* 19 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(1, 4) },  \
-                                                                               \
+                                                                             \
     /* 20 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 0) },  \
     /* 21 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 0) },  \
     /* 22 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 1) },  \
@@ -53,8 +58,8 @@
     /* 27 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 3) },  \
     /* 28 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 4) },  \
     /* 29 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 4) },  \
-                                                                               \
-    /* Front, again... Now right part */                                       \
+                                                                             \
+    /* Front, again... Now right part */                                     \
     /* 30 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 0) }, \
     /* 31 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(3, 0) }, \
     /* 32 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX40_MAKE_POINT(2, 1) }, \
@@ -85,51 +90,51 @@
 // X16 suit uses the same packets structure as x40 suit and performs motor grouping in firmware
 #define BH_LAYOUT_TACTSUITX16_SIZE 40
 // clang-format off
-#define BH_LAYOUT_TACTSUITX16 {                                                         \
-    /* Front, left part */                                                              \
+#define BH_LAYOUT_TACTSUITX16 {                                                       \
+    /* Front, left part */                                                            \
     /*  0 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0) }, /*  0 */ \
     /*  1 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0) }, /*  1 */ \
     /*  2 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0) }, /*  4 */ \
     /*  3 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0) }, /*  5 */ \
-                                                                                        \
+                                                                                      \
     /*  4 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) }, /*  8 */ \
     /*  5 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) }, /*  9 */ \
     /*  6 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) }, /* 12 */ \
     /*  7 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) }, /* 13 */ \
     /*  8 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) }, /* 16 */ \
     /*  9 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) }, /* 17 */ \
-                                                                                        \
-    /* Back */                                                                          \
+                                                                                      \
+    /* Back */                                                                        \
     /* 10 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0) }, /*  0 */  \
     /* 11 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0) }, /*  1 */  \
     /* 12 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 0) }, /*  4 */  \
     /* 13 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 0) }, /*  5 */  \
-                                                                                        \
+                                                                                      \
     /* 14 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) }, /*  8 */  \
     /* 15 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) }, /*  9 */  \
     /* 16 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) }, /* 12 */  \
     /* 17 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) }, /* 13 */  \
     /* 18 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(0, 1) }, /* 16 */  \
     /* 19 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(1, 1) }, /* 17 */  \
-                                                                                        \
+                                                                                      \
     /* 20 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0) }, /*  2 */  \
     /* 21 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0) }, /*  3 */  \
     /* 22 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0) }, /*  4 */  \
     /* 23 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0) }, /*  7 */  \
-                                                                                        \
+                                                                                      \
     /* 24 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) }, /* 10 */  \
     /* 25 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) }, /* 11 */  \
     /* 26 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) }, /* 14 */  \
     /* 27 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) }, /* 15 */  \
     /* 28 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) }, /* 18 */  \
     /* 29 */ { Target::ChestBack, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) }, /* 19 */  \
-                                                                                        \
-    /* Front, again... Now right part */                                                \
+                                                                                      \
+    /* Front, again... Now right part */                                              \
     /* 30 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0) }, /*  2 */ \
     /* 31 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0) }, /*  3 */ \
     /* 32 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 0) }, /*  4 */ \
     /* 33 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 0) }, /*  7 */ \
-                                                                                        \
+                                                                                      \
     /* 34 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) }, /* 10 */ \
     /* 35 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(3, 1) }, /* 11 */ \
     /* 36 */ { Target::ChestFront, BH_LAYOUT_TACTSUITX16_MAKE_POINT(2, 1) }, /* 14 */ \
@@ -290,62 +295,64 @@ namespace SenseShift::BH {
 
     using HandSide = ::SenseShift::Body::Hands::HandSide;
 
-    using OutputLayout = std::tuple<Target, Position>;
+    using OutputLayout = std::tuple<::SenseShift::Body::Haptics::Target, SenseShift::Body::Haptics::Position>;
 
     // TactGlove Wrist motor position
     static constexpr const Position WRIST_MOTOR_POSITION(127, 191);
-    static constexpr const OutputLayout TactGloveLeftLayout[] = BH_LAYOUT_TACTGLOVE_LEFT;
-    static constexpr const OutputLayout TactGloveRightLayout[] = BH_LAYOUT_TACTGLOVE_RIGHT;
+    static constexpr const std::array<OutputLayout, BH_LAYOUT_TACTGLOVE_SIZE> TactGloveLeftLayout = { BH_LAYOUT_TACTGLOVE_LEFT };
+    static constexpr const std::array<OutputLayout, BH_LAYOUT_TACTGLOVE_SIZE> TactGloveRightLayout = { BH_LAYOUT_TACTGLOVE_RIGHT };
 
     inline void addTactGloveActuators(
-      HapticBody* hapticBody,
+      FloatBody* hapticBody,
       const HandSide side,
-      VibroPlane::Actuator* const thumbActuator,
-      VibroPlane::Actuator* const indexActuator,
-      VibroPlane::Actuator* const middleActuator,
-      VibroPlane::Actuator* const ringActuator,
-      VibroPlane::Actuator* const littleActuator,
-      VibroPlane::Actuator* const wristActuator
-    )
-    {
-        const OutputLayout(&layout)[6] = (side == HandSide::Left) ? TactGloveLeftLayout : TactGloveRightLayout;
-
-        if (thumbActuator != nullptr) {
+      FloatBody::Plane::Actuator* const thumb,
+      FloatBody::Plane::Actuator* const index,
+      FloatBody::Plane::Actuator* const middle,
+      FloatBody::Plane::Actuator* const ring,
+      FloatBody::Plane::Actuator* const little,
+      FloatBody::Plane::Actuator* const wrist
+    ) {
+        const auto& layout = (side == HandSide::Left) ? TactGloveLeftLayout : TactGloveRightLayout;
+
+        if (thumb != nullptr) {
             hapticBody->addTarget(
               std::get<0>(layout[0]),
-              new VibroPlane({ { std::get<1>(layout[0]), thumbActuator } })
+              new FloatPlane({ {std::get<1>(layout[0]), thumb } })
             );
         }
 
-        if (indexActuator != nullptr) {
+        if (index != nullptr) {
             hapticBody->addTarget(
               std::get<0>(layout[1]),
-              new VibroPlane({ { std::get<1>(layout[1]), indexActuator } })
+              new FloatPlane({ {std::get<1>(layout[1]), index } })
             );
         }
 
-        if (middleActuator != nullptr) {
+        if (middle != nullptr) {
             hapticBody->addTarget(
               std::get<0>(layout[2]),
-              new VibroPlane({ { std::get<1>(layout[2]), middleActuator } })
+              new FloatPlane({ {std::get<1>(layout[2]), middle } })
             );
         }
 
-        if (ringActuator != nullptr) {
-            hapticBody->addTarget(std::get<0>(layout[3]), new VibroPlane({ { std::get<1>(layout[3]), ringActuator } }));
+        if (ring != nullptr) {
+            hapticBody->addTarget(
+              std::get<0>(layout[3]),
+              new FloatPlane({ {std::get<1>(layout[3]), ring } })
+            );
         }
 
-        if (littleActuator != nullptr) {
+        if (little != nullptr) {
             hapticBody->addTarget(
               std::get<0>(layout[4]),
-              new VibroPlane({ { std::get<1>(layout[4]), littleActuator } })
+              new FloatPlane({ {std::get<1>(layout[4]), little } })
             );
         }
 
-        if (wristActuator != nullptr) {
+        if (wrist != nullptr) {
             hapticBody->addTarget(
               std::get<0>(layout[5]),
-              new VibroPlane({ { std::get<1>(layout[5]), wristActuator } })
+              new FloatPlane({ {std::get<1>(layout[5]), wrist } })
             );
         }
     }
diff --git a/lib/bhaptics/senseshift/bh/encoding.hpp b/lib/bhaptics/senseshift/bh/encoding.hpp
index 273b98c7..3b05a54f 100644
--- a/lib/bhaptics/senseshift/bh/encoding.hpp
+++ b/lib/bhaptics/senseshift/bh/encoding.hpp
@@ -1,20 +1,24 @@
 #pragma once
 
+#include <algorithm>
+#include <array>
+#include <cstdint>
 #include <cstring>
+#include <string>
+#include <tuple>
+
+#include <senseshift/body/haptics/interface.hpp>
 #include <senseshift/body/haptics/body.hpp>
 
 namespace SenseShift::BH {
     class Decoder {
       public:
         using VibroEffectData = ::SenseShift::Body::Haptics::VibroEffectData;
-        using EffectData = ::SenseShift::Body::Haptics::EffectData;
 
         using Effect = ::SenseShift::Body::Haptics::Effect;
         using Target = ::SenseShift::Body::Haptics::Target;
         using Position = ::SenseShift::Body::Haptics::Position;
 
-        using HapticBody = ::SenseShift::Body::Haptics::HapticBody;
-
         using OutputLayout = std::tuple<Target, Position>;
 
         static constexpr size_t VEST_LAYOUT_SIZE = 40;
@@ -22,29 +26,33 @@ namespace SenseShift::BH {
 
         template<size_t N>
         static void applyPlain(
-          HapticBody* output, const uint8_t (&value)[N], const OutputLayout (&layout)[N], const Effect effect
-        )
-        {
+          FloatBody* output,
+          const std::array<std::uint8_t, N>& value,
+          const std::array<OutputLayout, N>& layout,
+          const Effect effect
+        ) {
             for (size_t i = 0; i < N; i++) {
                 const auto [target, position] = layout[i];
-                const uint8_t byte = value[i];
+                const std::uint8_t byte = value[i];
 
-                output->effect({
-                  .effect = effect,
-                  .target = target,
-                  .position = position,
-                  .data = effectDataFromByte(effect, byte),
-                });
+                output->effect(
+                  target,
+                  position,
+                  static_cast<FloatBody::Plane::Value>(effectDataFromByte(byte))
+                );
             }
         }
 
         template<size_t N>
-        static void
-          applyPlain(HapticBody* output, std::string& value, const OutputLayout (&layout)[N], const Effect effect)
-        {
-            std::uint8_t buf[N];
+        static void applyPlain(
+          FloatBody* output,
+          std::string& value,
+          const std::array<OutputLayout, N>& layout,
+          const Effect effect
+        ) {
+            std::array<std::uint8_t, N> buf{};
             std::size_t copyLength = std::min(value.size(), sizeof(buf));
-            std::memcpy(buf, value.c_str(), copyLength);
+            std::memcpy(buf.data(), value.c_str(), copyLength);
 
             applyPlain(output, buf, layout, effect);
         }
@@ -54,34 +62,35 @@ namespace SenseShift::BH {
          */
         template<size_t N>
         static void applyPlain(
-          HapticBody* output,
-          const uint8_t (&value)[N],
-          const Position (&layout)[N],
+          FloatBody* output,
+          const std::array<std::uint8_t, N>& value,
+          const std::array<Position, N>& layout,
           const Effect effect,
           const Target target
-        )
-        {
+        ) {
             for (size_t i = 0; i < N; i++) {
                 const auto position = layout[i];
                 const uint8_t byte = value[i];
 
-                output->effect({
-                  .effect = effect,
-                  .target = target,
-                  .position = position,
-                  .data = effectDataFromByte(effect, byte),
-                });
+                output->effect(
+                  target,
+                  position,
+                  static_cast<FloatBody::Plane::Value>(effectDataFromByte(byte))
+                );
             }
         }
 
         template<size_t N>
         static void applyPlain(
-          HapticBody* output, std::string& value, const Position (&layout)[N], const Effect effect, const Target target
-        )
-        {
-            std::uint8_t buf[N];
+          FloatBody* output,
+          std::string& value,
+          const std::array<Position, N>& layout,
+          const Effect effect,
+          const Target target
+        ) {
+            std::array<std::uint8_t, N> buf{};
             std::size_t copyLength = std::min(value.size(), sizeof(buf));
-            std::memcpy(buf, value.c_str(), copyLength);
+            std::memcpy(buf.data(), value.c_str(), copyLength);
 
             applyPlain(output, buf, layout, effect, target);
         }
@@ -90,32 +99,35 @@ namespace SenseShift::BH {
          * Apply vest-encoded data to the output.
          */
         static void applyVest(
-          HapticBody* output, const uint8_t (&value)[VEST_PAYLOAD_SIZE], const OutputLayout (&layout)[VEST_LAYOUT_SIZE]
-        )
-        {
+          FloatBody* output,
+          const std::array<uint8_t, VEST_PAYLOAD_SIZE>& value,
+          const std::array<OutputLayout, VEST_LAYOUT_SIZE>& layout
+        ) {
             for (size_t i = 0; i < VEST_PAYLOAD_SIZE; i++) {
-                uint8_t byte = value[i];
-                uint actIndex = i * 2;
-                output->effect({
-                  .effect = Effect::Vibro,
-                  .target = std::get<0>(layout[actIndex]),
-                  .position = std::get<1>(layout[actIndex]),
-                  .data = effectDataFromByte(Effect::Vibro, ((byte >> 4) & 0xf), 15),
-                });
-                output->effect({
-                  .effect = Effect::Vibro,
-                  .target = std::get<0>(layout[actIndex + 1]),
-                  .position = std::get<1>(layout[actIndex + 1]),
-                  .data = effectDataFromByte(Effect::Vibro, (byte & 0xf), 15),
-                });
+                const std::uint8_t byte = value[i];
+                const size_t actIndex = i * 2;
+
+                output->effect(
+                  std::get<0>(layout[actIndex]),
+                  std::get<1>(layout[actIndex]),
+                  static_cast<FloatBody::Plane::Value>(effectDataFromByte(((byte >> 4) & 0xf), 15))
+                );
+                output->effect(
+                  std::get<0>(layout[actIndex + 1]),
+                  std::get<1>(layout[actIndex + 1]),
+                  static_cast<FloatBody::Plane::Value>(effectDataFromByte((byte & 0xf), 15).getIntensity())
+                );
             }
         }
 
-        static void applyVest(HapticBody* output, std::string& value, const OutputLayout (&layout)[VEST_LAYOUT_SIZE])
-        {
-            std::uint8_t buf[VEST_PAYLOAD_SIZE];
-            std::size_t copyLength = std::min(value.size(), sizeof(buf));
-            std::memcpy(buf, value.c_str(), copyLength);
+        static void applyVest(
+          FloatBody* output,
+          std::string& value,
+          const std::array<OutputLayout, VEST_LAYOUT_SIZE>& layout
+        ) {
+            std::array<std::uint8_t, VEST_PAYLOAD_SIZE> buf{};
+            const size_t copyLength = std::min(value.size(), sizeof(buf));
+            std::memcpy(buf.data(), value.c_str(), copyLength);
 
             applyVest(output, buf, layout);
         }
@@ -125,18 +137,17 @@ namespace SenseShift::BH {
          */
         template<size_t N>
         static void applyVestGrouped(
-          HapticBody* output,
-          const uint8_t (&value)[VEST_PAYLOAD_SIZE],
-          const OutputLayout (&layout)[VEST_LAYOUT_SIZE],
-          const uint8_t (&layoutGroups)[N]
-        )
-        {
-            uint8_t result[VEST_LAYOUT_SIZE];
+          FloatBody* output,
+          const std::array<std::uint8_t, VEST_PAYLOAD_SIZE>& value,
+          const std::array<OutputLayout, VEST_LAYOUT_SIZE>& layout,
+          const std::array<std::uint8_t, N>& layoutGroups
+        ) {
+            std::array<std::uint8_t, VEST_LAYOUT_SIZE> result{};
 
             // Unpack values
             for (auto i = 0; i < VEST_PAYLOAD_SIZE; i++) {
-                uint8_t byte = value[i];
-                uint actIndex = i * 2;
+                const std::uint8_t byte = value[i];
+                const size_t actIndex = i * 2;
 
                 result[actIndex] = (byte >> 4) & 0xf;
                 result[actIndex + 1] = (byte & 0xf);
@@ -164,51 +175,39 @@ namespace SenseShift::BH {
 
             for (uint8_t i = 0; i < VEST_LAYOUT_SIZE; i++) {
                 // take only meaningful values
-                if (!::SenseShift::contains(layoutGroups, VEST_LAYOUT_SIZE, i)) {
+                if (std::find(layoutGroups.begin(), layoutGroups.end(), i) == layoutGroups.end()) {
                     continue;
                 }
 
                 const auto target = std::get<0>(layout[i]);
                 const auto position = std::get<1>(layout[i]);
 
-                output->effect({
-                  .effect = Effect::Vibro,
-                  .target = target,
-                  .position = position,
-                  .data = effectDataFromByte(Effect::Vibro, result[i], 15),
-                });
+                output->effect(
+                  target,
+                  position,
+                  static_cast<FloatBody::Plane::Value>(effectDataFromByte(result[i], 15))
+                );
             }
         }
 
         template<size_t N>
         static void applyVestGrouped(
-          HapticBody* output,
+          FloatBody* output,
           std::string& value,
-          const OutputLayout (&layout)[VEST_LAYOUT_SIZE],
-          const uint8_t (&layoutGroups)[N]
-        )
-        {
-            std::uint8_t buf[VEST_PAYLOAD_SIZE];
-            std::size_t copyLength = std::min(value.size(), sizeof(buf));
-            std::memcpy(buf, value.c_str(), copyLength);
+          const std::array<OutputLayout, VEST_LAYOUT_SIZE>& layout,
+          const std::array<std::uint8_t, N>& layoutGroups
+        ) {
+            std::array<std::uint8_t, VEST_PAYLOAD_SIZE> buf{};
+            const size_t copyLength = std::min(value.size(), sizeof(buf));
+            std::memcpy(buf.data(), value.c_str(), copyLength);
 
             applyVestGrouped(output, buf, layout, layoutGroups);
         }
 
       private:
-        static const EffectData
-          effectDataFromByte(const Effect effect, const uint8_t byte, const uint8_t maxValue = 100)
+        static auto effectDataFromByte(const uint8_t byte, const uint8_t maxValue = 100) -> VibroEffectData
         {
-            switch (effect) {
-                case Effect::Vibro:
-                    return VibroEffectData(::SenseShift::simpleMap<VibroEffectData::Intensity>(
-                      byte,
-                      maxValue,
-                      VibroEffectData::INTENSITY_MAX
-                    ));
-                default:
-                    throw std::runtime_error("Unknown effect");
-            }
+            return VibroEffectData(byte / maxValue);
         }
     };
 } // namespace SenseShift::BH
diff --git a/lib/core/senseshift/core/component.hpp b/lib/core/senseshift/core/component.hpp
new file mode 100644
index 00000000..8fae06dd
--- /dev/null
+++ b/lib/core/senseshift/core/component.hpp
@@ -0,0 +1,33 @@
+#pragma once
+
+namespace SenseShift {
+    class IInitializable {
+    public:
+        virtual void init() = 0;
+    };
+
+    class ITickable {
+    public:
+        virtual void tick() = 0;
+    };
+
+    class Component : public IInitializable, public ITickable {
+    public:
+        /// @brief Initialize the component.
+        ///
+        /// Where the component's initialization should happen.
+        /// Analogous to Arduino's setup(). This method is guaranteed to only be called once.
+        ///
+        /// Defaults to doing nothing.
+        void init() override {}
+
+        /// @brief Tick the component.
+        ///
+        /// This method will be called repeatedly.
+        /// Analogous to Arduino's loop(). init() is guaranteed to be called before this.
+        ///
+        /// Defaults to doing nothing.
+        void tick() override {}
+    };
+} // namespace SenseShift
+
diff --git a/lib/core/senseshift/core/helpers.hpp b/lib/core/senseshift/core/helpers.hpp
new file mode 100644
index 00000000..8a68a2fb
--- /dev/null
+++ b/lib/core/senseshift/core/helpers.hpp
@@ -0,0 +1,59 @@
+#pragma once
+
+#include <type_traits>
+
+namespace SenseShift {
+    /// @name Mathematics
+    /// @{
+
+    /// Linearly interpolate between \p start and \p end by \p completion (between 0 and 1).
+    constexpr auto lerp(const float completion, const float start, const float end) -> float
+    {
+        return start + (end - start) * completion;
+    }
+
+    /// Remap \p value from the range (\p min, \p max) to (\p min_out, \p max_out).
+    ///
+    /// \tparam Tp The output type.
+    /// \tparam Up The input type.
+    ///
+    /// \param value The value to remap.
+    /// \param min The minimum value of the input range.
+    /// \param max The maximum value of the input range.
+    /// \param min_out The minimum value of the output range.
+    /// \param max_out The maximum value of the output range.
+    ///
+    /// \return The remapped value.
+    template<typename Tp, typename Up>
+    constexpr auto remap(Up value, Up min, Up max, Tp min_out, Tp max_out) -> Tp
+    {
+        static_assert(std::is_arithmetic_v<Up>, "Type must be arithmetic");
+        static_assert(std::is_arithmetic_v<Tp>, "Type must be arithmetic");
+
+        return (value - min) * (max_out - min_out) / (max - min) + min_out;
+    }
+
+
+    /// Remap \p value from the range (0, \p max) to (0, \p max_out).
+    ///
+    /// \tparam Tp The output type.
+    /// \tparam Up The input type.
+    ///
+    /// \param value The value to remap.
+    /// \param max The maximum value of the input range.
+    /// \param max_out The maximum value of the output range.
+    ///
+    /// \return The remapped value.
+    ///
+    /// \note This is a simplified version of remap() where the minimum values are 0.
+    template<typename Tp, typename Up>
+    constexpr auto remap_simple(Up value, Up max, Tp max_out) noexcept -> Tp
+    {
+        static_assert(std::is_arithmetic_v<Up>, "Type must be arithmetic");
+        static_assert(std::is_arithmetic_v<Tp>, "Type must be arithmetic");
+
+        return value * max_out / max;
+    }
+
+    /// @}
+}  // namespace SenseShift
\ No newline at end of file
diff --git a/lib/core/senseshift/core/logging.hpp b/lib/core/senseshift/core/logging.hpp
new file mode 100644
index 00000000..647965ef
--- /dev/null
+++ b/lib/core/senseshift/core/logging.hpp
@@ -0,0 +1,40 @@
+#pragma once
+
+#if defined(ESP32)
+
+#include <esp32-hal-log.h>
+#define LOG_E(tag, format, ...) ESP_LOGE(tag, format, ##__VA_ARGS__)
+#define LOG_W(tag, format, ...) ESP_LOGW(tag, format, ##__VA_ARGS__)
+#define LOG_I(tag, format, ...) ESP_LOGI(tag, format, ##__VA_ARGS__)
+#define LOG_D(tag, format, ...) ESP_LOGD(tag, format, ##__VA_ARGS__)
+#define LOG_V(tag, format, ...) ESP_LOGV(tag, format, ##__VA_ARGS__)
+
+#elif defined(UNITY_INCLUDE_PRINT_FORMATTED)
+
+#define log_e(...) TEST_PRINTF(__VA_ARGS__)
+#define log_w(...) TEST_PRINTF(__VA_ARGS__)
+#define log_i(...) TEST_PRINTF(__VA_ARGS__)
+#define log_d(...) TEST_PRINTF(__VA_ARGS__)
+#define log_v(...) TEST_PRINTF(__VA_ARGS__)
+
+#define LOG_E(tag, format, ...) log_e("[%s] " format, tag, ##__VA_ARGS__)
+#define LOG_W(tag, format, ...) log_w("[%s] " format, tag, ##__VA_ARGS__)
+#define LOG_I(tag, format, ...) log_i("[%s] " format, tag, ##__VA_ARGS__)
+#define LOG_D(tag, format, ...) log_d("[%s] " format, tag, ##__VA_ARGS__)
+#define LOG_V(tag, format, ...) log_v("[%s] " format, tag, ##__VA_ARGS__)
+
+#else
+
+#define log_e(...)
+#define log_w(...)
+#define log_i(...)
+#define log_d(...)
+#define log_v(...)
+
+#define LOG_E(tag, format, ...)
+#define LOG_W(tag, format, ...)
+#define LOG_I(tag, format, ...)
+#define LOG_D(tag, format, ...)
+#define LOG_V(tag, format, ...)
+
+#endif
diff --git a/lib/core/senseshift/core/macros.hpp b/lib/core/senseshift/core/macros.hpp
new file mode 100644
index 00000000..1087e76f
--- /dev/null
+++ b/lib/core/senseshift/core/macros.hpp
@@ -0,0 +1,4 @@
+#pragma once
+
+// Helper macro to define a version code, whose value can be compared against other version codes.
+#define VERSION_CODE(major, minor, patch) ((major) << 16 | (minor) << 8 | (patch))
\ No newline at end of file
diff --git a/lib/core/senseshift/interface.hpp b/lib/core/senseshift/interface.hpp
deleted file mode 100644
index af428cc6..00000000
--- a/lib/core/senseshift/interface.hpp
+++ /dev/null
@@ -1,11 +0,0 @@
-#pragma once
-
-namespace SenseShift {
-    struct IInitializable {
-        virtual void init() = 0;
-    };
-
-    struct ITickable {
-        virtual void tick() = 0;
-    };
-} // namespace SenseShift
diff --git a/lib/core/senseshift/logging.hpp b/lib/core/senseshift/logging.hpp
deleted file mode 100644
index 9cf54f37..00000000
--- a/lib/core/senseshift/logging.hpp
+++ /dev/null
@@ -1,23 +0,0 @@
-#pragma once
-
-#if defined(ESP32)
-
-#include <esp32-hal-log.h>
-
-#elif defined(UNITY_INCLUDE_PRINT_FORMATTED)
-
-#define log_e(...) TEST_PRINTF(__VA_ARGS__)
-#define log_w(...) TEST_PRINTF(__VA_ARGS__)
-#define log_i(...) TEST_PRINTF(__VA_ARGS__)
-#define log_d(...) TEST_PRINTF(__VA_ARGS__)
-#define log_t(...) TEST_PRINTF(__VA_ARGS__)
-
-#else
-
-#define log_e(...)
-#define log_w(...)
-#define log_i(...)
-#define log_d(...)
-#define log_t(...)
-
-#endif
diff --git a/lib/freertos/senseshift/freertos/task.hpp b/lib/freertos/senseshift/freertos/task.hpp
index d41355ec..d06415ea 100644
--- a/lib/freertos/senseshift/freertos/task.hpp
+++ b/lib/freertos/senseshift/freertos/task.hpp
@@ -1,6 +1,6 @@
 #pragma once
 
-#include <senseshift/logging.hpp>
+#include <senseshift/core/logging.hpp>
 
 extern "C" void delay(uint32_t ms);
 
diff --git a/lib/haptics/senseshift/body/haptics/body.cpp b/lib/haptics/senseshift/body/haptics/body.cpp
index edd3ee6e..e60eb210 100644
--- a/lib/haptics/senseshift/body/haptics/body.cpp
+++ b/lib/haptics/senseshift/body/haptics/body.cpp
@@ -1,33 +1,23 @@
 #include "senseshift/body/haptics/body.hpp"
+#include "senseshift/body/haptics/interface.hpp"
 
-#include <senseshift/logging.hpp>
+#include <senseshift/output/output.hpp>
+#include <senseshift/core/logging.hpp>
 
 namespace SenseShift::Body::Haptics {
-    void HapticBody::setup()
-    {
-        for (auto& [target, plane] : this->vibroTargets) {
-            plane->setup();
-        }
-    }
+    static const char *const TAG = "haptic.body";
 
-    void HapticBody::effect(const EffectRequest& effect)
+    template<typename Tp, typename Ta>
+    void OutputBody<Tp, Ta>::effect(const Target& target, const Position& pos, const typename Plane::Value& val)
     {
-        if (effect.effect == Effect::Vibro && std::holds_alternative<VibroEffectData>(effect.data)) {
-            auto it = this->vibroTargets.find(effect.target);
-            if (it == this->vibroTargets.end()) {
-                log_w("No target found for effect: %d", effect.target);
-                return;
-            }
-
-            it->second->effect(effect.position, std::get<VibroEffectData>(effect.data));
-        } else {
-            log_w("Non-supported effect type: %d", effect.effect);
+        auto find = this->targets_.find(target);
+        if (find == this->targets_.end()) {
+            LOG_W(TAG, "No target found for effect: %d", target);
+            return;
         }
-    }
 
-    void HapticBody::addTarget(const Target target, VibroPlane* plane)
-    {
-        this->vibroTargets[target] = plane;
-        this->allTargets.insert({ target, plane });
+        find->second->effect(pos, val);
     }
+
+    template class OutputBody<Position::Value, Output::FloatOutput::ValueType>;
 } // namespace SenseShift::Body::Haptics
diff --git a/lib/haptics/senseshift/body/haptics/body.hpp b/lib/haptics/senseshift/body/haptics/body.hpp
index b0c9068e..386866a4 100644
--- a/lib/haptics/senseshift/body/haptics/body.hpp
+++ b/lib/haptics/senseshift/body/haptics/body.hpp
@@ -3,29 +3,42 @@
 #include "senseshift/body/haptics/interface.hpp"
 #include "senseshift/body/haptics/plane.hpp"
 
-#include <senseshift/utility.hpp>
-
 #include <map>
 
+#include <senseshift/output/output.hpp>
+
 namespace SenseShift::Body::Haptics {
-    class HapticBody {
+    /// Output body, contains all the output planes.
+    ///
+    /// \tparam Tc The type of the coordinate.
+    /// \tparam To The type of the output value.
+    template<typename Tc, typename To>
+    class OutputBody {
       public:
-        using AuctiativePlane = std::variant<VibroPlane*>;
-        using PlaneTargetMap = std::multimap<Target, AuctiativePlane>;
-        using VibroTargetMap = std::map<Target, VibroPlane*>;
+        /// The type of the output plane for the given target.
+        using Plane = OutputPlane<Tc, To>;
+        /// The type of the target to output plane map (e.g. Chest -> OutputPlane).
+        using TargetPlaneMap = std::map<Target, Plane*>;
 
-        HapticBody(){};
+        OutputBody() = default;
 
-        void setup();
+        void setup() {
+            for (auto& [target, plane] : this->targets_) {
+                plane->setup();
+            }
+        }
 
-        void effect(const EffectRequest&);
+        void addTarget(Target target, Plane* plane) {
+            this->targets_[target] = plane;
+        }
 
-        void addTarget(const Target, VibroPlane* plane);
+        void effect(const Target& target, const Position& pos, const typename Plane::Value& val);
 
-        [[nodiscard]] const PlaneTargetMap* getTargets() const { return &allTargets; }
+        [[nodiscard]] auto getTargets() const -> const TargetPlaneMap* { return &targets_; }
 
       private:
-        PlaneTargetMap allTargets{};
-        VibroTargetMap vibroTargets{};
+        TargetPlaneMap targets_{};
     };
+
+    using FloatBody = OutputBody<Position::Value, Output::FloatOutput::ValueType>;
 } // namespace SenseShift::Body::Haptics
diff --git a/lib/haptics/senseshift/body/haptics/interface.hpp b/lib/haptics/senseshift/body/haptics/interface.hpp
index c4084531..89f14cff 100644
--- a/lib/haptics/senseshift/body/haptics/interface.hpp
+++ b/lib/haptics/senseshift/body/haptics/interface.hpp
@@ -6,9 +6,9 @@
 #include <senseshift/math/point2.hpp>
 
 namespace SenseShift::Body::Haptics {
-    using EffectIntex = std::uint8_t;
-    static constexpr EffectIntex EFFECT_INVALID = 0xFF;
-    enum class Effect : EffectIntex {
+    using EffectIndex = std::uint8_t;
+    static constexpr EffectIndex EFFECT_INVALID = 0xFF;
+    enum class Effect : EffectIndex {
         Invalid = EFFECT_INVALID,
         Vibro = 0x00,
         // TODO: thermal, etc.
@@ -47,31 +47,36 @@ namespace SenseShift::Body::Haptics {
     };
 
     using Coordinate = std::uint8_t;
-    using Position = ::SenseShift::Math::Point2<Coordinate>;
+    using Position = Math::Point2<Coordinate>;
 
-    // Vibration intensity.
-    struct VibroEffectData {
-        using Intensity = std::uint16_t;
-        static constexpr Intensity INTENSITY_MIN = 0;
-        static constexpr Intensity INTENSITY_MAX = 4095;
+    // Vibration intensity_.
+    class VibroEffectData {
+      public:
+        using Intensity = float;
 
-        Intensity intensity = 0;
+        static constexpr Intensity INTENSITY_MIN = 0.0F;
+        static constexpr Intensity INTENSITY_MAX = 1.0F;
 
-        inline constexpr VibroEffectData() = default;
-        inline constexpr VibroEffectData(const Intensity intensity) : intensity(intensity) {}
-        inline constexpr VibroEffectData(const VibroEffectData& other) = default;
+        constexpr VibroEffectData() = default;
+        constexpr explicit VibroEffectData(const Intensity intensity) : intensity_(intensity) {}
+        constexpr VibroEffectData(const VibroEffectData& other) = default;
 
-        inline constexpr operator std::uint16_t() const { return intensity; }
+        constexpr inline explicit operator float() const { return this->intensity_; }
+
+        [[nodiscard]] constexpr inline auto getIntensity() const -> Intensity { return intensity_; };
+
+      private:
+        Intensity intensity_ = 0;
     };
 
     // TODO: thermal, etc.
-
     using EffectData = std::variant<VibroEffectData>;
 
     struct EffectRequest {
         Effect effect = Effect::Invalid;
         Target target = Target::Invalid;
         Position position = Position(0, 0);
+
         EffectData data;
     };
 } // namespace SenseShift::Body::Haptics
diff --git a/lib/haptics/senseshift/body/haptics/plane.cpp b/lib/haptics/senseshift/body/haptics/plane.cpp
index ab11c81b..bfbd4f33 100644
--- a/lib/haptics/senseshift/body/haptics/plane.cpp
+++ b/lib/haptics/senseshift/body/haptics/plane.cpp
@@ -1,78 +1,82 @@
 #include "senseshift/body/haptics/plane.hpp"
+#include "senseshift/body/haptics/interface.hpp"
 
 #include <algorithm>
-#include <cmath>
-#include <senseshift/logging.hpp>
+#include <map>
+
+#include <senseshift/output/output.hpp>
+#include <senseshift/core/logging.hpp>
 
 namespace SenseShift::Body::Haptics {
-    template<typename _Tp, typename _Ta>
-    void ActuativePlane<_Tp, _Ta>::setActuators(const ActuatorMap& actuators)
+    static const char *const TAG = "haptic.plane";
+
+    template<typename Tc, typename To>
+    void OutputPlane<Tc, To>::setActuators(const ActuatorMap& actuators)
     {
-        this->actuators.clear();
+        this->actuators_.clear();
         for (const auto& [point, actuator] : actuators) {
-            this->actuators[point] = actuator;
+            this->actuators_[point] = actuator;
         }
 
-        this->points.clear();
+        this->points_.clear();
         for (const auto& [point, _] : actuators) {
-            this->points.insert(point);
+            this->points_.insert(point);
         }
 
-        this->states.clear();
+        this->states_.clear();
         for (const auto& [point, _] : actuators) {
-            this->states[point] = 0;
+            this->states_[point] = static_cast<Value>(0);
         }
     }
 
-    template<typename _Tp, typename _Ta>
-    void ActuativePlane<_Tp, _Ta>::setup()
+    template<typename Tc, typename To>
+    void OutputPlane<Tc, To>::setup()
     {
-        for (const auto& [point, actuator] : this->actuators) {
-            actuator->setup();
+        for (const auto& [point, actuator] : this->actuators_) {
+            actuator->init();
         }
     }
 
-    template<typename _Tp, typename _Ta>
-    void ActuativePlane<_Tp, _Ta>::effect(const Position& pos, const Value& val)
+    template<typename Tc, typename To>
+    void OutputPlane<Tc, To>::effect(const Position& pos, const Value& val)
     {
-        auto it = this->actuators.find(pos);
-        if (it == this->actuators.end()) {
-            log_w("No actuator for point (%u, %u)", pos.x, pos.y);
+        auto find = this->actuators_.find(pos);
+        if (find == this->actuators_.end()) {
+            LOG_W(TAG, "No actuator for point (%u, %u)", pos.x, pos.y);
             return;
         }
 
-        it->second->writeOutput(val);
-        this->states[pos] = val;
+        find->second->writeState(val);
+        this->states_[pos] = val;
     }
 
-    template<typename _Tp, typename _Ta>
-    void ActuativePlane_Closest<_Tp, _Ta>::effect(const Position& pos, const Value& val)
+    template<typename Tc, typename To>
+    void OutputPlane_Closest<Tc, To>::effect(const Position& pos, const Value& val)
     {
         auto& closest = this->findClosestPoint(*this->getAvailablePoints(), pos);
-        ActuativePlane<_Tp, _Ta>::effect(closest, val);
+        OutputPlane<Tc, To>::effect(closest, val);
     }
 
-    template<typename _Tp, typename _Ta>
-    const Position&
-      ActuativePlane_Closest<_Tp, _Ta>::findClosestPoint(const PositionSet& pts, const Position& target) const
+    template<typename Tc, typename To>
+    [[nodiscard]] auto OutputPlane_Closest<Tc, To>::findClosestPoint(const PositionSet& pts, const Position& target) -> const Position&
     {
         // check if exact point exists
-        auto it = pts.find(target);
-        if (it != pts.end()) {
-            return *it;
+        const auto find = pts.find(target);
+        if (find != pts.end()) {
+            return *find;
         }
 
-        // find closest point by square distance
-        std::multimap<float, Position> mp = {};
-        for (const auto& _p : pts) {
-            mp.insert({ (target - _p), _p });
+        // find the closest point by square distance
+        std::multimap<float, Position> distance_map = {};
+        for (const auto& point : pts) {
+            distance_map.insert({(target - point), point });
         }
 
-        auto nearest = std::min_element(mp.begin(), mp.end());
+        const auto nearest = std::min_element(distance_map.begin(), distance_map.end());
 
         return nearest->second;
     }
 
-    template class ActuativePlane<VibroEffectData, ::SenseShift::Output::IActuator<VibroEffectData::Intensity>>;
-    template class ActuativePlane_Closest<VibroEffectData, ::SenseShift::Output::IActuator<VibroEffectData::Intensity>>;
+    template class OutputPlane<Position::Value, Output::FloatOutput::ValueType>;
+    template class OutputPlane_Closest<Position::Value, Output::FloatOutput::ValueType>;
 } // namespace SenseShift::Body::Haptics
diff --git a/lib/haptics/senseshift/body/haptics/plane.hpp b/lib/haptics/senseshift/body/haptics/plane.hpp
index b8ac0727..eeea313b 100644
--- a/lib/haptics/senseshift/body/haptics/plane.hpp
+++ b/lib/haptics/senseshift/body/haptics/plane.hpp
@@ -2,96 +2,101 @@
 
 #include "senseshift/body/haptics/interface.hpp"
 
-#include <senseshift/output/actuator.hpp>
-#include <senseshift/utility.hpp>
-
-#include <list>
+#include <cstddef>
 #include <map>
 #include <set>
 #include <vector>
 
-namespace SenseShift::Body::Haptics {
-    using PositionSet = std::set<Position>;
-
-    /**
-     * Output "plane" (e.g. Chest, Palm, Finger, etc.)
-     *
-     * @tparam _Tp The type of the output value.
-     */
-    template<typename _Tp, typename _Ta>
-    class ActuativePlane {
-        static_assert(std::is_same<_Tp, VibroEffectData>());
+#include <senseshift/output/output.hpp>
+#include <senseshift/utility.hpp>
+#include <senseshift/math/point2.hpp>
 
+namespace SenseShift::Body::Haptics {
+    /// Output "plane" (e.g. Chest, Palm, Finger, etc.).
+    ///
+    /// \tparam Tc The type of the coordinate.
+    /// \tparam To The type of the output value.
+    template<typename Tc, typename To>
+    class OutputPlane {
       public:
-        using Value = _Tp;
-        using Actuator = _Ta;
+        /// The type of the coordinate (e.g. std::uint8_t) for the plane.
+        using Coordinate = Tc;
+        /// The type of the position (e.g. Point2<std::uint8_t>) for the plane.
+        using Position = Math::Point2<Coordinate>;
+        using PositionSet = std::set<Position>;
+
+        /// The type of the output value (e.g. float) for the plane.
+        using Value = To;
+        /// The type of the actuator for the plane.
+        using Actuator = Output::Output<Value>;
 
         using ActuatorMap = std::map<Position, Actuator*>;
         using PositionStateMap = std::map<Position, Value>;
 
-        ActuativePlane() = default;
+        OutputPlane() = default;
 
-        ActuativePlane(const ActuatorMap& actuators) { this->setActuators(actuators); }
+        explicit OutputPlane(const ActuatorMap& actuators) { this->setActuators(actuators); }
 
         void setup();
         virtual void effect(const Position&, const Value&);
 
-        [[nodiscard]] const PositionSet* getAvailablePoints() const { return &points; }
-        [[nodiscard]] const PositionStateMap* getActuatorStates() const { return &states; }
+        [[nodiscard]] auto getAvailablePoints() const -> const PositionSet* { return &points_; }
+        [[nodiscard]] auto getActuatorStates() const -> const PositionStateMap* { return &states_; }
 
-      private:
-        PositionSet points;
-        ActuatorMap actuators{};
-        PositionStateMap states{};
+      protected:
+        void setActuators(const ActuatorMap& actuators);
 
-        void setActuators(const ActuatorMap&);
+      private:
+        PositionSet points_;
+        ActuatorMap actuators_{};
+        PositionStateMap states_{};
     };
 
-    using VibroPlane = ActuativePlane<VibroEffectData, ::SenseShift::Output::IActuator<VibroEffectData::Intensity>>;
-
-    /**
-     * Output plane, finds the closest actuator for the given point.
-     * @deprecated We should guarantee on the driver level, that the actuator is always exists
-     */
-    template<typename _Tp, typename _Ta>
-    class ActuativePlane_Closest : public ActuativePlane<_Tp, _Ta> {
-      public:
-        using Value = _Tp;
-
-        ActuativePlane_Closest(const typename ActuativePlane<_Tp, _Ta>::ActuatorMap& actuators) :
-          ActuativePlane<_Tp, _Ta>(actuators)
+    /// Output plane, finds the closest actuator for the given point.
+    /// \deprecated We should guarantee on the driver level, that the actuator is always exists.
+    ///
+    /// \tparam Tc The type of the coordinate.
+    /// \tparam To The type of the output value.
+    template<typename Tc, typename To>
+    class OutputPlane_Closest : public OutputPlane<Tc, To> {
+    public:
+        using Value = To;
+        using PositionSet = typename OutputPlane<Tc, To>::PositionSet;
+
+        explicit OutputPlane_Closest(const typename OutputPlane<Tc, To>::ActuatorMap& actuators) : OutputPlane<Tc, To>(actuators)
         {
         }
 
         void effect(const Position&, const Value&) override;
 
-      private:
-        [[nodiscard]] const Position& findClosestPoint(const PositionSet&, const Position&) const;
+    private:
+        [[nodiscard]] static auto findClosestPoint(const PositionSet&, const Position&) -> const Position&;
     };
 
-    using VibroPlane_Closest =
-      ActuativePlane_Closest<VibroEffectData, ::SenseShift::Output::IActuator<VibroEffectData::Intensity>>;
+    using FloatPlane = OutputPlane<Position::Value, Output::FloatOutput::ValueType>;
+    using FloatPlane_Closest = OutputPlane_Closest<Position::Value, Output::FloatOutput::ValueType>;
 
     // TODO: configurable margin
     class PlaneMapper_Margin {
       public:
-        template<typename _Tp>
-        [[nodiscard]] static constexpr inline std::map<Position, _Tp*>
-          mapMatrixCoordinates(std::vector<std::vector<_Tp*>> map2d)
+
+        /// Maps a 2D matrix into a 1D (coord, object) map.
+        template<typename Tp>
+        [[nodiscard]] static constexpr auto mapMatrixCoordinates(std::vector<std::vector<Tp*>> map2d) -> std::map<Position, Tp*>
         {
-            std::map<Position, _Tp*> points{};
+            std::map<Position, Tp*> points{};
 
-            size_t y_size = map2d.size();
-            size_t y_max = y_size - 1;
+            const size_t y_size = map2d.size();
+            const size_t y_max = y_size - 1;
 
             for (size_t y = 0; y < y_size; ++y) {
                 auto row = map2d.at(y);
-                size_t x_size = row.size();
-                size_t x_max = x_size - 1;
+                const size_t x_size = row.size();
+                const size_t x_max = x_size - 1;
 
                 for (size_t x = 0; x < x_size; ++x) {
                     auto* wr = row.at(x);
-                    Position coord = PlaneMapper_Margin::mapPoint<Position::Value>(x, y, x_max, y_max);
+                    Position coord = mapPoint<Position::Value>(x, y, x_max, y_max);
 
                     points[coord] = wr;
                 }
@@ -100,19 +105,16 @@ namespace SenseShift::Body::Haptics {
             return points;
         }
 
-        /**
-         * Re-maps a point index to output coordinate.
-         * @tparam _Tp The type of the point index.
-         */
-        template<typename _Tp>
-        [[nodiscard]] static constexpr inline ::SenseShift::Math::Point2<_Tp>
-          mapPoint(_Tp x, _Tp y, _Tp x_max, _Tp y_max)
+        /// Re-maps a point index to output coordinate.
+        /// \tparam Tp The type of the point index.
+        template<typename Tp>
+        [[nodiscard]] static constexpr auto mapPoint(Tp x, Tp y, Tp x_max, Tp y_max) -> Math::Point2<Tp>
         {
-            using Point = ::SenseShift::Math::Point2<_Tp>;
+            using LocalPointType = Math::Point2<Tp>;
 
-            return Point(
-              ::SenseShift::accurateMap<_Tp>(x + 1, 0, x_max + 2, Point::MIN, Point::MAX),
-              ::SenseShift::accurateMap<_Tp>(y + 1, 0, y_max + 2, Point::MIN, Point::MAX)
+            return LocalPointType(
+              ::SenseShift::remap<Tp, Tp>(x + 1, 0, x_max + 2, LocalPointType::MIN, LocalPointType::MAX),
+              ::SenseShift::remap<Tp, Tp>(y + 1, 0, y_max + 2, LocalPointType::MIN, LocalPointType::MAX)
             );
         }
     };
diff --git a/lib/io/senseshift/input/sensor.hpp b/lib/io/senseshift/input/sensor.hpp
index 2520cd76..22b8a116 100644
--- a/lib/io/senseshift/input/sensor.hpp
+++ b/lib/io/senseshift/input/sensor.hpp
@@ -3,8 +3,7 @@
 #include <type_traits>
 
 #include <senseshift/calibration.hpp>
-#include <senseshift/interface.hpp>
-#include <senseshift/logging.hpp>
+#include <senseshift/core/component.hpp>
 
 #if defined(__AVR__)
 #define ANALOG_MAX 1023
@@ -18,145 +17,154 @@
 #endif
 
 namespace SenseShift::Input {
-    /**
-     * Abstract hardware sensor (e.g. potentiometer, flex sensor, etc.)
-     * @tparam _Tp Type of the sensor value
-     */
-    template<typename _Tp>
-    struct ISimpleSensor : public virtual IInitializable {
-        using ValueType = _Tp;
-
-        /**
-         * Get the current sensor value
-         */
-        virtual ValueType getValue() = 0;
+    /// Abstract hardware sensor (e.g. potentiometer, flex sensor, etc.)
+    /// \tparam Tp Type of the sensor value
+    template<typename Tp>
+    class ISimpleSensor : virtual public IInitializable {
+    public:
+        using ValueType = Tp;
+
+        /// Get the current sensor value
+        [[nodiscard]] virtual auto getValue() -> ValueType = 0;
     };
 
-    template<typename _Tp>
-    struct ISensor : public virtual ISimpleSensor<_Tp>, ITickable {};
+    using IBinarySensor = ISimpleSensor<bool>;
 
-    /**
-     * Memoized sensor decorator
-     * @tparam _Tp Type of the sensor value
-     */
-    template<typename _Tp>
-    class MemoizedSensor : public ISensor<_Tp> {
-      protected:
-        ISimpleSensor<_Tp>* sensor;
-        _Tp value;
+    template<typename Tp>
+    class ISensor : virtual ISimpleSensor<Tp>, ITickable {};
 
+    /// Memoized sensor decorator. Stores the last read value and returns it on subsequent calls
+    /// \tparam Tp Type of the sensor value
+    template<typename Tp>
+    class MemoizedSensor : public ISensor<Tp> {
       public:
-        /**
-         * @param sensor Sensor to be decorated
-         */
-        MemoizedSensor(ISimpleSensor<_Tp>* sensor) : sensor(sensor){};
+        using ValueType = Tp;
+
+        /// \param sensor Sensor to be decorated
+        explicit MemoizedSensor(ISimpleSensor<ValueType>* sensor) : sensor_(sensor){}
 
         /**
          * Setup the sensor hardware
          */
-        void init() override { this->sensor->init(); };
+        void init() override { this->sensor_->init(); }
 
         /**
          * Read actual value from the hardware and memoize it
          */
-        void tick() override { this->value = this->sensor->getValue(); };
+        void tick() override { this->value_ = this->sensor_->getValue(); }
 
         /**
          * Get the current memoized value
          */
-        _Tp getValue() override { return this->value; };
+        [[nodiscard]] auto getValue() -> ValueType override { return this->value_; }
+
+    private:
+        ISimpleSensor<ValueType>* sensor_;
+        ValueType value_;
     };
 
-    template<typename _Tp>
-    class ICalibratedSimpleSensor : public ISimpleSensor<_Tp>, public Calibration::ICalibrated {};
+    template<typename Tp>
+    class ICalibratedSimpleSensor : public ISimpleSensor<Tp>, public Calibration::ICalibrated {};
 
-    /**
-     * Calibrated sensor decorator
-     *
-     * @tparam _Tp Type of the sensor value
-     */
-    template<typename _Tp>
-    class CalibratedSimpleSensor : public ICalibratedSimpleSensor<_Tp> {
+    /// Calibrated sensor decorator
+    /// \tparam Tp Type of the sensor value
+    template<typename Tp>
+    class CalibratedSimpleSensor : public ICalibratedSimpleSensor<Tp> {
       public:
-        /**
-         * @param sensor Sensor to be decorated
-         * @param calibrator ICalibrator algorithm to be used
-         */
-        CalibratedSimpleSensor(ISimpleSensor<_Tp>* sensor, Calibration::ICalibrator<_Tp>* calibrator) :
-          sensor(sensor), calibrator(calibrator){};
+        using ValueType = Tp;
 
-        void init() override { this->sensor->init(); };
-        _Tp getValue() override { return this->getCalibratedValue(); };
+        /// \param sensor Sensor to be decorated
+        /// \param calibrator ICalibrator algorithm to be used
+        CalibratedSimpleSensor(ISimpleSensor<ValueType>* sensor, Calibration::ICalibrator<ValueType>* calibrator) :
+          sensor_(sensor), calibrator_(calibrator){};
 
-        void resetCalibration() override { this->calibrator->reset(); };
-        void enableCalibration() override { calibrating = true; }
-        void disableCalibration() override { calibrating = false; }
+        void init() override { this->sensor_->init(); };
+        [[nodiscard]] auto getValue() -> ValueType override { return this->getCalibratedValue(); };
 
-      protected:
-        ISimpleSensor<_Tp>* sensor;
-        Calibration::ICalibrator<_Tp>* calibrator;
-        bool calibrating = false;
+        void resetCalibration() override { this->calibrator_->reset(); };
+        void enableCalibration() override { is_calibrating_ = true; }
+        void disableCalibration() override { is_calibrating_ = false; }
 
-        _Tp getCalibratedValue()
+      protected:
+        [[nodiscard]] auto getCalibratedValue() -> ValueType
         {
-            auto value = this->sensor->getValue();
+            auto value = this->sensor_->getValue();
 
-            if (this->calibrating) {
-                this->calibrator->update(value);
+            if (this->is_calibrating_) {
+                this->calibrator_->update(value);
             }
 
-            return this->calibrator->calibrate(value);
+            return this->calibrator_->calibrate(value);
         }
+
+    private:
+        ISimpleSensor<ValueType>* sensor_;
+        Calibration::ICalibrator<ValueType>* calibrator_;
+        bool is_calibrating_ = false;
     };
 
-    template<typename _Tp>
-    class AverageSensor : public ISimpleSensor<_Tp> {
-        static_assert(std::is_arithmetic<_Tp>::value, "AverageSensor only supports arithmetic types");
+    /// A sensor that returns the average value of N samples.
+    /// \tparam Tp Type of the sensor value
+    template<typename Tp>
+    class AverageSensor : public ISimpleSensor<Tp> {
+        static_assert(std::is_arithmetic_v<Tp>, "AverageSensor only supports arithmetic types");
 
       public:
-        AverageSensor(ISimpleSensor<_Tp>* sensor, size_t samples) : sensor(sensor), samples(samples) {}
+        using ValueType = Tp;
+
+        /// \param sensor Sensor to be decorated
+        /// \param samples Number of samples to be used
+        AverageSensor(ISimpleSensor<ValueType>* sensor, const size_t samples) : sensor_(sensor), samples_(samples) {}
 
-        void init() override { this->sensor->init(); };
+        void init() override { this->sensor_->init(); };
 
-        _Tp getValue() override
+        [[nodiscard]] auto getValue() const -> ValueType override
         {
             // TODO: another type for sum?
             double sum = 0;
-            for (size_t i = 0; i < this->samples; i++) {
-                sum += this->sensor->getValue();
+            for (size_t i = 0; i < this->samples_; i++) {
+                sum += this->sensor_->getValue();
             }
 
-            return sum / this->samples;
+            return sum / this->samples_;
         }
 
       private:
-        ISimpleSensor<_Tp>* sensor;
-        size_t samples;
+        ISimpleSensor<ValueType>* sensor_;
+        size_t samples_;
     };
 
-    template<typename _Tp, size_t _Samples>
-    class StaticMedianSensor : public ISimpleSensor<_Tp> {
-        static_assert(std::is_arithmetic<_Tp>::value, "StaticMedianSensor only supports arithmetic types");
-        static_assert(_Samples % 2 == 1, "StaticMedianSensor only supports odd sample sizes");
+    /// A sensor that returns the median value of N samples.
+    /// \tparam Tp Type of the sensor value
+    /// \tparam NumSamples Number of samples to be used
+    template<typename Tp, size_t NumSamples>
+    class StaticMedianSensor : public ISimpleSensor<Tp> {
+        static_assert(std::is_arithmetic_v<Tp>, "StaticMedianSensor only supports arithmetic types");
+        static_assert(NumSamples % 2 == 1, "StaticMedianSensor only supports odd sample sizes");
 
       public:
-        StaticMedianSensor(ISimpleSensor<_Tp>* sensor) : sensor(sensor) {}
+        using ValueType = Tp;
 
-        void init() override { this->sensor->init(); };
+        explicit StaticMedianSensor(ISimpleSensor<ValueType>* sensor) : sensor_(sensor) {}
 
-        _Tp getValue() override
+        void init() override { this->sensor_->init(); };
+
+        [[nodiscard]] auto getValue() -> ValueType override
         {
-            for (size_t i = 0; i < _Samples; i++) {
-                this->values[i] = this->sensor->getValue();
+            for (size_t i = 0; i < NumSamples; i++) {
+                this->values_[i] = this->sensor_->getValue();
             }
 
-            std::sort(this->values, this->values + _Samples);
+            std::sort(this->values_.begin(), this->values_.end());
 
-            return this->values[_Samples / 2];
+            return this->values_[NumSamples / 2];
         }
 
       private:
-        _Tp values[_Samples];
-        ISimpleSensor<_Tp>* sensor;
+        /// Buffer to store the last N samples.
+        /// We are using a static array to avoid dynamic allocation
+        std::array<ValueType, NumSamples> values_;
+
+        ISimpleSensor<Tp>* sensor_;
     };
 } // namespace SenseShift::Input
diff --git a/lib/io/senseshift/input/sensor/joystick.hpp b/lib/io/senseshift/input/sensor/joystick.hpp
index a03af6f7..0dd95dfe 100644
--- a/lib/io/senseshift/input/sensor/joystick.hpp
+++ b/lib/io/senseshift/input/sensor/joystick.hpp
@@ -6,29 +6,31 @@ namespace SenseShift::Input {
     /**
      * Joystick axis sensor decorator
      */
-    template<typename _Tp>
-    class JoystickAxisSensor : public ISimpleSensor<_Tp> {
+    template<typename Tp>
+    class JoystickAxisSensor : public ISimpleSensor<Tp> {
+    public:
+        using ValueType = Tp;
+
       private:
-        ISimpleSensor<_Tp>* sensor;
-        float dead_zone;
+        ISimpleSensor<ValueType>* sensor_;
+        float dead_zone_;
 
-        int filterDeadZone(int in)
-        {
-            // This function clamps the input to the center of the range if
-            // the value is within the threshold. This is to eliminate at-rest
-            // noise of the joystick.
-            int center = ANALOG_MAX / 2;
-            return abs(center - in) < dead_zone * ANALOG_MAX ? center : in;
+        /// This function clamps the input to the center of the range if
+        /// the value is within the threshold. This is to eliminate at-rest
+        /// noise of the joystick.
+        [[nodiscard]] auto filterDeadZone(const int value_in) const -> ValueType {
+            constexpr ValueType center = ANALOG_MAX / 2;
+            return abs(center - value_in) < dead_zone_ * ANALOG_MAX ? center : value_in;
         }
 
       public:
-        JoystickAxisSensor(ISimpleSensor<_Tp>* sensor, float dead_zone) : sensor(sensor), dead_zone(dead_zone){};
+        JoystickAxisSensor(ISimpleSensor<Tp>* sensor, const float dead_zone) : sensor_(sensor), dead_zone_(dead_zone){};
 
-        void init() override { this->sensor->init(); };
+        void init() override { this->sensor_->init(); };
 
-        uint16_t getValue(void) override
+        [[nodiscard]] auto getValue() -> uint16_t override
         {
-            auto value = this->sensor->getValue();
+            auto value = this->sensor_->getValue();
             value = this->filterDeadZone(value);
             return value;
         }
diff --git a/lib/io/senseshift/output/actuator.hpp b/lib/io/senseshift/output/actuator.hpp
deleted file mode 100644
index 3938c65f..00000000
--- a/lib/io/senseshift/output/actuator.hpp
+++ /dev/null
@@ -1,22 +0,0 @@
-#pragma once
-
-#include <cstdint>
-
-#include <senseshift/logging.hpp>
-
-namespace SenseShift::Output {
-    // Singular output point (e.g. vibration motor)
-    template<typename T>
-    class IActuator {
-      public:
-        virtual void setup(){};
-        virtual void writeOutput(T) = 0;
-    };
-
-    template<>
-    class IActuator<std::uint16_t> {
-      public:
-        virtual void setup(){};
-        virtual void writeOutput(std::uint16_t) = 0;
-    };
-} // namespace SenseShift::Output
diff --git a/lib/io/senseshift/output/binary_output.hpp b/lib/io/senseshift/output/binary_output.hpp
new file mode 100644
index 00000000..c9200a08
--- /dev/null
+++ b/lib/io/senseshift/output/binary_output.hpp
@@ -0,0 +1,10 @@
+#pragma once
+
+#include "senseshift/output/output.hpp"
+
+namespace SenseShift::Output {
+    class BinaryOutput : public ::SenseShift::Output::Output<bool> {
+      public:
+        virtual void writeOutput(bool) = 0;
+    };
+} // namespace SenseShift::Output
\ No newline at end of file
diff --git a/lib/io/senseshift/output/output.hpp b/lib/io/senseshift/output/output.hpp
new file mode 100644
index 00000000..c02bfaf7
--- /dev/null
+++ b/lib/io/senseshift/output/output.hpp
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <senseshift/core/component.hpp>
+
+namespace SenseShift::Output {
+    template<typename Tp>
+    class Output : public IInitializable {
+    public:
+        using ValueType = Tp;
+
+        virtual void writeState(ValueType value) = 0;
+    };
+
+    using FloatOutput = Output<float>;
+} // namespace SenseShift::Output
\ No newline at end of file
diff --git a/lib/math/senseshift/math/point2.hpp b/lib/math/senseshift/math/point2.hpp
index b91b096b..faa2f475 100644
--- a/lib/math/senseshift/math/point2.hpp
+++ b/lib/math/senseshift/math/point2.hpp
@@ -1,33 +1,33 @@
 #pragma once
 
 #include <cmath>
+#include <limits>
 #include <tuple>
+#include <type_traits>
 
 namespace SenseShift::Math {
-    template<typename _Tp>
+    template<typename Tp>
     struct Point2 {
-        static_assert(
-          std::is_arithmetic<_Tp>::value, "::SenseShift::Math::Point2 only can be used with arithmetic types"
-        );
+        static_assert(std::is_arithmetic_v<Tp>, "Point2 only can be used with arithmetic types");
 
-        using Value = _Tp;
+        using Value = Tp;
 
-        static constexpr _Tp MIN = std::numeric_limits<_Tp>::min();
-        static constexpr _Tp MAX = std::numeric_limits<_Tp>::max();
+        static constexpr Tp MIN = std::numeric_limits<Tp>::min();
+        static constexpr Tp MAX = std::numeric_limits<Tp>::max();
 
-        _Tp x, y;
+        Tp x, y;
 
-        constexpr Point2() : x((_Tp) 0), y((_Tp) 0){};
-        constexpr Point2(_Tp x, _Tp y) : x(x), y(y){};
-        constexpr Point2(const Point2<_Tp>& v) : x((_Tp) v.x), y((_Tp) v.y){};
+        constexpr Point2() : x(static_cast<Tp>(0)), y(static_cast<Tp>(0)){};
+        constexpr Point2(Tp x, Tp y) : x(x), y(y){};
+        constexpr Point2(const Point2<Tp>& v) : x((Tp) v.x), y((Tp) v.y){};
 
-        constexpr inline bool operator==(const Point2<_Tp>& rhs) const { return x == rhs.x && y == rhs.y; }
+        constexpr inline auto operator==(const Point2<Tp>& rhs) const -> bool { return x == rhs.x && y == rhs.y; }
 
-        constexpr inline bool operator!=(const Point2<_Tp>& rhs) const { return !(*this == rhs); }
+        constexpr inline auto operator!=(const Point2<Tp>& rhs) const -> bool { return !(*this == rhs); }
 
-        constexpr bool operator<(const Point2<_Tp>& rhs) const { return std::tie(x, y) < std::tie(rhs.x, rhs.y); }
+        constexpr auto operator<(const Point2<Tp>& rhs) const -> bool { return std::tie(x, y) < std::tie(rhs.x, rhs.y); }
 
-        constexpr float operator-(const Point2<_Tp>& rhs) const
+        constexpr auto operator-(const Point2<Tp>& rhs) const -> float
         {
             return std::sqrt(std::pow(x - rhs.x, 2) + std::pow(y - rhs.y, 2));
         }
diff --git a/lib/opengloves/og_protocol.hpp b/lib/opengloves/og_protocol.hpp
index a8f19e2e..f1849a3b 100644
--- a/lib/opengloves/og_protocol.hpp
+++ b/lib/opengloves/og_protocol.hpp
@@ -38,7 +38,7 @@ namespace OpenGloves {
         IStringEncoded(Type type) : IEncodedInput(type){};
 
         virtual size_t getEncodedLength() const = 0;
-        virtual size_t encodeString(char* buffer) const = 0;
+        virtual size_t encodeString(char* buffer) = 0;
     };
 
     class IStringEncodedMemoizedSensor : public IStringEncoded {
diff --git a/lib/opengloves/senseshift/opengloves/autoconfig.hpp b/lib/opengloves/senseshift/opengloves/autoconfig.hpp
index 191a8c06..35419d72 100644
--- a/lib/opengloves/senseshift/opengloves/autoconfig.hpp
+++ b/lib/opengloves/senseshift/opengloves/autoconfig.hpp
@@ -113,7 +113,7 @@
 #define BUTTON_PINCH_ENABLED (!GESTURE_PINCH_ENABLED && defined(PIN_BUTTON_PINCH) && (PIN_BUTTON_PINCH != -1))
 
 #define BUTTON_CLASS(_type, _pin, _invert) \
-    StringEncodedMemoizedSensor<bool>(new ::SenseShift::Arduino::Input::DigitalSensor<_invert>(_pin), _type)
+    StringEncodedMemoizedSensor<bool>(new ::SenseShift::Arduino::Input::BinarySensor<_invert>(_pin), _type)
 
 #pragma endregion
 
diff --git a/lib/opengloves/senseshift/opengloves/encoding/alpha.hpp b/lib/opengloves/senseshift/opengloves/encoding/alpha.hpp
index 49d223cf..a8f81f7a 100644
--- a/lib/opengloves/senseshift/opengloves/encoding/alpha.hpp
+++ b/lib/opengloves/senseshift/opengloves/encoding/alpha.hpp
@@ -12,7 +12,7 @@
 #include <frozen/unordered_map.h>
 
 #include <og_protocol.hpp>
-#include <senseshift/logging.hpp>
+#include <senseshift/core/logging.hpp>
 #include <senseshift/opengloves/interface.hpp>
 
 #define SENSESHIFT_OPENGLOVES_ALPHA_ENCODING_BUFFER_SIZE 256
diff --git a/lib/opengloves/sensor/og_finger.hpp b/lib/opengloves/sensor/og_finger.hpp
index 542d981e..95760b7a 100644
--- a/lib/opengloves/sensor/og_finger.hpp
+++ b/lib/opengloves/sensor/og_finger.hpp
@@ -53,7 +53,7 @@ namespace OpenGloves {
             }
         }
 
-        FingerValue getValue() override
+        [[nodiscard]] auto getValue() -> FingerValue override
         {
             FingerValue value{
                 .curl = std::vector<uint16_t>(),
@@ -68,7 +68,7 @@ namespace OpenGloves {
             return value;
         }
 
-        uint16_t getCurl() override { return this->getValue().getTotalCurl(); }
+        [[nodiscard]] auto getCurl() -> uint16_t override { return this->getValue().getTotalCurl(); }
 
       protected:
         FingerSensors sensors;
@@ -136,11 +136,11 @@ namespace OpenGloves {
         FingerSensor(SenseShift::Input::CalibratedSimpleSensor<uint16_t>* curl1, IEncodedInput::Type type) :
           StringEncodedMemoizedSensor<FingerValue>(new CalibratedFingerSensor(curl1, std::nullopt), type){};
 
-        void resetCalibration() override { static_cast<CalibratedFingerSensor*>(this->sensor)->resetCalibration(); }
+        void resetCalibration() override { static_cast<CalibratedFingerSensor*>(this->getSensor())->resetCalibration(); }
 
-        void enableCalibration() override { static_cast<CalibratedFingerSensor*>(this->sensor)->enableCalibration(); }
+        void enableCalibration() override { static_cast<CalibratedFingerSensor*>(this->getSensor())->enableCalibration(); }
 
-        void disableCalibration() override { static_cast<CalibratedFingerSensor*>(this->sensor)->disableCalibration(); }
+        void disableCalibration() override { static_cast<CalibratedFingerSensor*>(this->getSensor())->disableCalibration(); }
 
         uint16_t getCurl() override { return this->getValue().getTotalCurl(); }
     };
diff --git a/lib/opengloves/sensor/og_gesture.hpp b/lib/opengloves/sensor/og_gesture.hpp
index 9bb0e375..c64365c1 100644
--- a/lib/opengloves/sensor/og_gesture.hpp
+++ b/lib/opengloves/sensor/og_gesture.hpp
@@ -19,7 +19,7 @@ namespace OpenGloves {
 
         void init() override{};
 
-        bool getValue() override
+        [[nodiscard]] auto getValue() -> bool override
         {
             return this->index.getCurl() > this->threshold && this->middle.getCurl() > this->threshold
                    && this->ring.getCurl() > this->threshold && this->pinky.getCurl() > this->threshold;
@@ -36,7 +36,7 @@ namespace OpenGloves {
 
         void init() override{};
 
-        bool getValue() override { return this->index.getCurl() > this->threshold; }
+        [[nodiscard]] auto getValue() -> bool override { return this->index.getCurl() > this->threshold; }
     };
 
     class PinchGesture : public Gesture {
@@ -46,12 +46,12 @@ namespace OpenGloves {
         uint16_t threshold;
 
       public:
-        PinchGesture(ICurl& index, ICurl& thumb, uint16_t threshold) :
+        PinchGesture(ICurl& index, ICurl& thumb, const uint16_t threshold) :
           index(index), thumb(thumb), threshold(threshold){};
 
         void init() override{};
 
-        bool getValue() override
+        [[nodiscard]] auto getValue() -> bool override
         {
             return this->index.getCurl() > this->threshold && this->thumb.getCurl() > this->threshold;
         }
diff --git a/lib/opengloves/sensor/og_sensor.hpp b/lib/opengloves/sensor/og_sensor.hpp
index caa4a9c8..6b95f0c1 100644
--- a/lib/opengloves/sensor/og_sensor.hpp
+++ b/lib/opengloves/sensor/og_sensor.hpp
@@ -33,27 +33,26 @@ namespace OpenGloves {
         StringEncodedMemoizedSensor(SenseShift::Input::ISimpleSensor<_Tp>* sensor, IEncodedInput::Type type) :
           IStringEncodedMemoizedSensor(type), SenseShift::Input::MemoizedSensor<_Tp>(sensor){};
 
-        void init() override { this->sensor->init(); }
+        void init() override { this->getSensor()->init(); }
 
-        void updateValue() override { this->value = this->sensor->getValue(); }
+        void updateValue() override { this->value = this->sensor_->getValue(); }
 
-        size_t getEncodedLength() const override;
+        [[nodiscard]] size_t getEncodedLength() const override;
 
-        size_t encodeString(char* buffer) const override;
+        size_t encodeString(char* buffer) override;
     };
 
     template<>
-    size_t StringEncodedMemoizedSensor<uint16_t>::getEncodedLength() const
+    inline size_t StringEncodedMemoizedSensor<uint16_t>::getEncodedLength() const
     {
         return 6;
     }
 
+    /// Format as "Axxxx", where A is the type and xxxxx is the value without leading zeros.
     template<>
-    size_t StringEncodedMemoizedSensor<uint16_t>::encodeString(char* buffer) const
+    inline size_t StringEncodedMemoizedSensor<uint16_t>::encodeString(char* buffer)
     {
-        // Format as "Axxxx", where A is the type and xxxxx is the value without
-        // leading zeros.
-        return snprintf(buffer, this->getEncodedLength(), "%c%d", this->getType(), this->value);
+        return snprintf(buffer, this->getEncodedLength(), "%c%d", this->getType(), this->getValue());
     }
 
     template<>
@@ -63,11 +62,14 @@ namespace OpenGloves {
     }
 
     template<>
-    size_t StringEncodedMemoizedSensor<bool>::encodeString(char* buffer) const
+    size_t StringEncodedMemoizedSensor<bool>::encodeString(char* buffer)
     {
+        const auto value = this->getValue();
+
         if (value) {
             buffer[0] = this->getType();
         }
+
         return value ? this->getEncodedLength() : 0;
     }
 
@@ -79,20 +81,20 @@ namespace OpenGloves {
     }
 
     template<>
-    size_t StringEncodedMemoizedSensor<FingerValue>::encodeString(char* buffer) const
+    size_t StringEncodedMemoizedSensor<FingerValue>::encodeString(char* buffer)
     {
         size_t offset = 0;
-        offset += snprintf(buffer + offset, 6, "%c%d", this->type, this->value.getTotalCurl());
+        offset += snprintf(buffer + offset, 6, "%c%d", this->type, this->getValue().getTotalCurl());
 
-        if (this->value.curl.size() > 1) {
-            for (size_t i = 0; i < this->value.curl.size(); i++) {
+        if (this->getValue().curl.size() > 1) {
+            for (size_t i = 0; i < this->getValue().curl.size(); i++) {
                 char knuckle = 'A' + i;
-                offset += snprintf(buffer + offset, 10, "(%cA%c)%d", this->type, knuckle, this->value.curl[i]);
+                offset += snprintf(buffer + offset, 10, "(%cA%c)%d", this->type, knuckle, this->getValue().curl[i]);
             }
         }
 
-        if (this->value.splay.has_value()) {
-            offset += snprintf(buffer + offset, 9, "(%cB)%d", this->type, this->value.splay.value());
+        if (this->getValue().splay.has_value()) {
+            offset += snprintf(buffer + offset, 9, "(%cB)%d", this->type, this->getValue().splay.value());
         }
 
         return offset;
diff --git a/lib/opengloves_task/opengloves_task.hpp b/lib/opengloves_task/opengloves_task.hpp
index 2f7bc581..4fb4f43f 100644
--- a/lib/opengloves_task/opengloves_task.hpp
+++ b/lib/opengloves_task/opengloves_task.hpp
@@ -6,7 +6,7 @@
 
 #include <og_ffb.hpp>
 #include <senseshift/arduino/input/sensor/analog.hpp>
-#include <senseshift/arduino/input/sensor/digital.hpp>
+#include <senseshift/arduino/input/sensor/binary.hpp>
 #include <senseshift/calibration.hpp>
 #include <senseshift/freertos/task.hpp>
 #include <senseshift/input/sensor.hpp>
diff --git a/lib/util/senseshift/calibration.hpp b/lib/util/senseshift/calibration.hpp
index 53411728..e1602582 100644
--- a/lib/util/senseshift/calibration.hpp
+++ b/lib/util/senseshift/calibration.hpp
@@ -15,105 +15,106 @@ namespace SenseShift::Calibration {
     };
 
     class Calibrated : public virtual ICalibrated {
-      protected:
-        bool calibrate = false;
+        bool calibrate_ = false;
 
       public:
-        virtual void resetCalibration() = 0;
-        void enableCalibration() override { calibrate = true; }
-        void disableCalibration() override { calibrate = false; }
+        void resetCalibration() override = 0;
+        void enableCalibration() override { calibrate_ = true; }
+        void disableCalibration() override { calibrate_ = false; }
     };
 
-    template<typename _Tp>
+    template<typename Tp>
     struct ICalibrator {
-        static_assert(std::is_arithmetic<_Tp>::value, "ICalibrator only can be used with arithmetic types");
+        static_assert(std::is_arithmetic_v<Tp>, "ICalibrator only can be used with arithmetic types");
 
         virtual void reset() = 0;
-        virtual void update(_Tp input) = 0;
-        virtual _Tp calibrate(_Tp input) const = 0;
+        virtual void update(Tp input) = 0;
+        virtual auto calibrate(Tp input) const -> Tp = 0;
     };
 
-    template<typename _Tp, _Tp output_min, _Tp output_max>
-    class MinMaxCalibrator : public ICalibrator<_Tp> {
+    template<typename Tp, Tp output_min, Tp output_max>
+    class MinMaxCalibrator : public ICalibrator<Tp> {
       public:
-        MinMaxCalibrator() : value_min(output_max), value_max(output_min) {}
+        using ValueType = Tp;
+
+        MinMaxCalibrator() : value_min_(output_max), value_max_(output_min) {}
 
-        void reset()
-        {
-            value_min = output_max;
-            value_max = output_min;
+        void reset() override {
+            value_min_ = output_max;
+            value_max_ = output_min;
         }
 
-        void update(_Tp input)
-        {
+        void update(ValueType input) override {
             // Update the min and the max.
-            if (input < value_min)
-                value_min = input;
-            if (input > value_max)
-                value_max = input;
+            if (input < value_min_) {
+                value_min_ = input;
+            }
+            if (input > value_max_) {
+                value_max_ = input;
+            }
         }
 
-        _Tp calibrate(_Tp input) const
-        {
+        auto calibrate(ValueType input) const -> ValueType override {
             // This means we haven't had any calibration data yet.
             // Return a neutral value right in the middle of the output range.
-            if (value_min > value_max) {
-                return (output_min + output_max) / 2.0f;
+            if (value_min_ > value_max_) {
+                return (output_min + output_max) / 2.0F;
             }
 
-            if (input <= value_min) {
+            if (input <= value_min_) {
                 return output_min;
             }
 
-            if (input >= value_max) {
+            if (input >= value_max_) {
                 return output_max;
             }
 
             // Map the input range to the output range.
-            _Tp output = ::SenseShift::accurateMap<_Tp>(input, value_min, value_max, output_min, output_max);
+            ValueType output = ::SenseShift::remap<ValueType, ValueType>(input, value_min_, value_max_, output_min, output_max);
 
             // Lock the range to the output.
             return std::clamp(output, output_min, output_max);
         }
 
       private:
-        _Tp value_min;
-        _Tp value_max;
+        ValueType value_min_;
+        ValueType value_max_;
     };
 
-    template<typename _Tp, _Tp sensor_max, _Tp driver_max_deviation, _Tp output_min, _Tp output_max>
-    class CenterPointDeviationCalibrator : public ICalibrator<_Tp> {
+    template<typename Tp, Tp sensor_max, Tp driver_max_deviation, Tp output_min, Tp output_max>
+    class CenterPointDeviationCalibrator : public ICalibrator<Tp> {
       public:
+        using ValueType = Tp;
+
         CenterPointDeviationCalibrator() : range_min(sensor_max), range_max(0) {}
 
-        void reset()
-        {
+        void reset() override {
             range_min = sensor_max;
             range_max = 0;
         }
 
-        void update(_Tp input)
-        {
+        void update(Tp input) override {
             // Update the min and the max.
-            if (input < range_min)
-                range_min = ::SenseShift::accurateMap<_Tp>(input, output_min, output_max, 0, sensor_max);
-            if (input > range_max)
-                range_max = ::SenseShift::accurateMap<_Tp>(input, output_min, output_max, 0, sensor_max);
+            if (input < range_min) {
+                range_min = ::SenseShift::remap<ValueType>(input, output_min, output_max, 0, sensor_max);
+            }
+            if (input > range_max) {
+                range_max = ::SenseShift::remap<ValueType>(input, output_min, output_max, 0, sensor_max);
+            }
         }
 
-        _Tp calibrate(_Tp input) const
-        {
+        auto calibrate(ValueType input) const -> ValueType override {
             // Find the center point of the sensor so we know how much we have deviated from it.
-            _Tp center = (range_min + range_max) / 2.0f;
+            Tp center = (range_min + range_max) / 2.0F;
 
             // Map the input to the sensor range of motion.
-            int output = ::SenseShift::accurateMap<_Tp>(input, output_min, output_max, 0, sensor_max);
+            int output = ::SenseShift::accurateMap<Tp>(input, output_min, output_max, 0, sensor_max);
 
             // Find the deviation from the center and clamp it to the maximum that the driver supports.
             output = std::clamp<int>(output - center, -driver_max_deviation, driver_max_deviation);
 
             // Finally map the deviation from the center back to the output range.
-            return (_Tp)::SenseShift::accurateMap<int>(
+            return SenseShift::remap<ValueType, int>(
               output,
               -driver_max_deviation,
               driver_max_deviation,
@@ -123,29 +124,30 @@ namespace SenseShift::Calibration {
         }
 
       private:
-        _Tp range_min;
-        _Tp range_max;
+        Tp range_min;
+        Tp range_max;
     };
 
-    template<typename _Tp, _Tp sensor_max, _Tp driver_max_deviation, _Tp output_min, _Tp output_max>
-    class FixedCenterPointDeviationCalibrator : public ICalibrator<_Tp> {
+    template<typename Tp, Tp sensor_max, Tp driver_max_deviation, Tp output_min, Tp output_max>
+    class FixedCenterPointDeviationCalibrator : public ICalibrator<Tp> {
       public:
-        void reset() {}
-        void update(_Tp input) {}
+        using ValueType = Tp;
+
+        void reset() override {}
+        void update(ValueType input) override {}
 
-        _Tp calibrate(_Tp input) const
-        {
+        auto calibrate(ValueType input) const -> ValueType override {
             // Find the center point of the sensor so we know how much we have deviated from it.
-            _Tp center = sensor_max / 2.0f;
+            Tp center = sensor_max / 2.0F;
 
             // Map the input to the sensor range of motion.
-            int output = ::SenseShift::accurateMap<_Tp>(input, output_min, output_max, 0, sensor_max);
+            int output = ::SenseShift::remap<int, Tp>(input, output_min, output_max, 0, sensor_max);
 
             // Find the deviation from the center and clamp it to the maximum that the driver supports.
             output = std::clamp<int>(output - center, -driver_max_deviation, driver_max_deviation);
 
             // Finally map the deviation from the center back to the output range.
-            return (_Tp)::SenseShift::accurateMap<int>(
+            return SenseShift::remap<ValueType, int>(
               output,
               -driver_max_deviation,
               driver_max_deviation,
diff --git a/lib/util/senseshift/container.hpp b/lib/util/senseshift/container.hpp
index 7f0ab894..f8da0365 100644
--- a/lib/util/senseshift/container.hpp
+++ b/lib/util/senseshift/container.hpp
@@ -12,7 +12,7 @@ namespace SenseShift {
      * @tparam _Tp The type of the value.
      */
     template<class _Cp, typename _Tp>
-    inline bool contains(_Cp&& c, _Tp val)
+    inline auto contains(const _Cp&& c, const _Tp val) -> bool
     {
         return std::find(std::begin(c), std::end(c), val) != std::end(c);
     };
diff --git a/lib/util/senseshift/range.hpp b/lib/util/senseshift/range.hpp
index 6dac8598..ea690108 100644
--- a/lib/util/senseshift/range.hpp
+++ b/lib/util/senseshift/range.hpp
@@ -1,30 +1,21 @@
 #include <cstdint>
 #include <type_traits>
 
-#include <senseshift/logging.hpp>
+#include <senseshift/core/logging.hpp>
+#include <senseshift/core/helpers.hpp>
 
 namespace SenseShift {
-    template<typename _Tp>
-    constexpr _Tp accurateMap(_Tp x, _Tp in_min, _Tp in_max, _Tp out_min, _Tp out_max) noexcept
+    /// Remap \p value from the range (\p min, \p max) to (\p min_out, \p max_out).
+    template<typename Tp>
+    constexpr auto accurateMap(Tp value, Tp min, Tp max, Tp min_out, Tp max_out) noexcept -> Tp
     {
-        static_assert(std::is_arithmetic<_Tp>::value, "Type must be arithmetic");
-
-        const _Tp run = in_max - in_min;
-        if (run == 0) {
-            log_e("map(): Invalid input range, min == max");
-            return (out_min + out_max) / 2;
-        }
-        const _Tp rise = out_max - out_min;
-        const _Tp delta = x - in_min;
-        return (delta * rise) / run + out_min;
+        return remap<Tp, Tp>(value, min, max, min_out, max_out);
     }
 
     // Same as the above, but both mins are 0.
-    template<typename _Tp>
-    constexpr inline _Tp simpleMap(_Tp x, _Tp in_max, _Tp out_max) noexcept
+    template<typename Tp>
+    constexpr auto simpleMap(Tp value, Tp in_max, Tp out_max) noexcept -> Tp
     {
-        static_assert(std::is_arithmetic<_Tp>::value, "Type must be arithmetic");
-
-        return x * out_max / in_max;
+        return remap_simple<Tp, Tp>(value, in_max, out_max);
     }
 } // namespace SenseShift

From 176f5e512c39f823c391b6883366c9d6a80ce042 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Sun, 28 Jan 2024 00:16:15 +0400
Subject: [PATCH 50/82] refactor(Battery): use analog float sensor

---
 firmware/firmware.cpp                         | 16 ---------
 .../mode_configs/bhaptics/tactsuit_x16.cpp    |  3 ++
 .../bhaptics/tactsuit_x16_pca9685.cpp         |  3 ++
 .../mode_configs/bhaptics/tactsuit_x40.cpp    |  3 ++
 .../arduino/input/sensor/analog.hpp           | 22 ++++++------
 .../arduino/output/actuator/servo.hpp         | 34 -------------------
 .../senseshift/arduino/output/analog.hpp      |  5 +--
 .../senseshift/arduino/output/pca9685.hpp     | 10 +++---
 .../senseshift/arduino/output/ledc.hpp        |  2 +-
 lib/battery/senseshift/battery.hpp            |  1 +
 lib/battery/senseshift/battery/sensor.hpp     | 12 ++++---
 lib/bhaptics/senseshift/bh/encoding.hpp       |  3 +-
 .../senseshift/freertos/input/sensor.hpp      |  8 ++---
 lib/haptics/senseshift/body/haptics/body.cpp  |  2 +-
 lib/haptics/senseshift/body/haptics/body.hpp  |  4 +--
 lib/haptics/senseshift/body/haptics/plane.cpp |  4 +--
 lib/haptics/senseshift/body/haptics/plane.hpp |  6 ++--
 lib/io/senseshift/input/sensor.hpp            |  5 +--
 lib/io/senseshift/output/binary_output.hpp    | 10 ------
 lib/io/senseshift/output/output.hpp           |  5 +--
 20 files changed, 58 insertions(+), 100 deletions(-)
 delete mode 100644 lib/arduino/senseshift/arduino/output/actuator/servo.hpp
 delete mode 100644 lib/io/senseshift/output/binary_output.hpp

diff --git a/firmware/firmware.cpp b/firmware/firmware.cpp
index 213a1fbd..258921f2 100644
--- a/firmware/firmware.cpp
+++ b/firmware/firmware.cpp
@@ -4,14 +4,6 @@
 #include <Arduino.h>
 #endif // ARDUINO
 
-#if defined(SENSESHIFT_SERIAL_PLOTTER) && SENSESHIFT_SERIAL_PLOTTER == true
-#include <senseshift/arduino/components/serial_plotter.hpp>
-#endif // SENSESHIFT_SERIAL_PLOTTER
-
-#ifndef SENSESHIFT_SERIAL_PLOTTER_PORT
-#define SENSESHIFT_SERIAL_PLOTTER_PORT Serial
-#endif // SENSESHIFT_SERIAL_PLOTTER_PORT
-
 #ifndef PIO_UNIT_TESTING
 
 extern void setupMode();
@@ -24,14 +16,6 @@ SenseShift::Application App;
 void setup()
 {
     setupMode();
-
-#if defined(SENSESHIFT_SERIAL_PLOTTER) && SENSESHIFT_SERIAL_PLOTTER == true
-    auto* serialOutputState = new ::SenseShift::Arduino::SerialPlotter_OutputStates<HardwareSerial>(
-      SENSESHIFT_SERIAL_PLOTTER_PORT,
-      App.getHapticBody()
-    );
-    serialOutputState->begin();
-#endif // SENSESHIFT_SERIAL_PLOTTER
 }
 
 void loop()
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
index f5c566c3..52b95ccf 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
@@ -5,7 +5,10 @@
 #include <Wire.h>
 
 #include "senseshift.h"
+
+#include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/ledc.hpp>
+#include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
index 128dcd22..87869e43 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
@@ -5,7 +5,10 @@
 #include <Wire.h>
 
 #include "senseshift.h"
+
+#include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/pca9685.hpp>
+#include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
index 23983157..8a036950 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
@@ -5,8 +5,11 @@
 #include <Wire.h>
 
 #include "senseshift.h"
+
+#include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/ledc.hpp>
 #include <senseshift/arduino/output/pca9685.hpp>
+#include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
diff --git a/lib/arduino/senseshift/arduino/input/sensor/analog.hpp b/lib/arduino/senseshift/arduino/input/sensor/analog.hpp
index 122b1906..e95f14f4 100644
--- a/lib/arduino/senseshift/arduino/input/sensor/analog.hpp
+++ b/lib/arduino/senseshift/arduino/input/sensor/analog.hpp
@@ -1,33 +1,35 @@
 #pragma once
 
+#include <cstdint>
+
 #include <senseshift/input/sensor.hpp>
 
 #include <Arduino.h>
 
 namespace SenseShift::Arduino::Input {
-    using IAnalogSensor = ::SenseShift::Input::ISimpleSensor<uint16_t>;
-
     template<bool Invert = false>
-    class AnalogSensor : public IAnalogSensor {
-        uint8_t pin_;
+    class AnalogSensor : public ::SenseShift::Input::IFloatSensor {
+        std::uint8_t pin_;
 
       public:
-        AnalogSensor(const uint8_t pin) : pin_(pin) {}
+        AnalogSensor(const std::uint8_t pin) : pin_(pin) {}
 
         void init() override { pinMode(this->pin_, INPUT); };
 
-        [[nodiscard]] auto getValue() -> uint16_t override;
+        [[nodiscard]] auto getValue() -> float override;
     };
 
     template<>
-    [[nodiscard]] inline auto AnalogSensor<false>::getValue() -> uint16_t
+    [[nodiscard]] inline auto AnalogSensor<false>::getValue() -> float
     {
-        return analogRead(this->pin_);
+        const auto raw = analogRead(this->pin_);
+        return static_cast<float>(raw) / ANALOG_MAX;
     }
 
     template<>
-    [[nodiscard]] inline auto AnalogSensor<true>::getValue() -> uint16_t
+    [[nodiscard]] inline auto AnalogSensor<true>::getValue() -> float
     {
-        return ANALOG_MAX - analogRead(this->pin_);
+        const auto raw = ANALOG_MAX - analogRead(this->pin_);
+        return static_cast<float>(raw) / ANALOG_MAX;
     }
 } // namespace SenseShift::Arduino::Input
diff --git a/lib/arduino/senseshift/arduino/output/actuator/servo.hpp b/lib/arduino/senseshift/arduino/output/actuator/servo.hpp
deleted file mode 100644
index 29bfa5e1..00000000
--- a/lib/arduino/senseshift/arduino/output/actuator/servo.hpp
+++ /dev/null
@@ -1,34 +0,0 @@
-#pragma once
-
-#include <senseshift/output/actuator.hpp>
-#include <senseshift/utility.hpp>
-
-#include <ESP32Servo.h>
-
-namespace SenseShift::Arduino::Output {
-    class ActuatorServo : public ::SenseShift::Output::IActuator<std::uint16_t> {
-      public:
-        static inline constexpr const std::uint16_t MAX_INTENSITY = 4095;
-
-        ActuatorServo(const uint8_t pin, const std::uint16_t min = 500, const std::uint16_t max = 2400) :
-          pin(pin), min(min), max(max){};
-
-        void setup() override
-        {
-            servo.attach(this->pin, this->min, this->max);
-            this->writeOutput(0);
-        };
-
-        void writeOutput(std::uint16_t intensity) override
-        {
-            servo.writeMicroseconds(
-              ::SenseShift::accurateMap<std::uint16_t>(intensity, 0, MAX_INTENSITY, this->min, this->max)
-            );
-        };
-
-      private:
-        Servo servo = Servo();
-        uint8_t pin;
-        std::uint16_t min, max;
-    };
-} // namespace SenseShift::Arduino::Output
diff --git a/lib/arduino/senseshift/arduino/output/analog.hpp b/lib/arduino/senseshift/arduino/output/analog.hpp
index bcefae41..7dec32b6 100644
--- a/lib/arduino/senseshift/arduino/output/analog.hpp
+++ b/lib/arduino/senseshift/arduino/output/analog.hpp
@@ -8,7 +8,7 @@
 
 namespace SenseShift::Arduino::Output {
     /// Arduino analog output
-    class AnalogOutput : public ::SenseShift::Output::FloatOutput {
+    class AnalogOutput : public ::SenseShift::Output::IFloatOutput {
       public:
         static inline constexpr std::uint16_t MAX_INTENSITY = 255;
 
@@ -20,7 +20,8 @@ namespace SenseShift::Arduino::Output {
         }
 
         void writeState(const float value) override {
-            analogWrite(this->pin_, value * MAX_INTENSITY);
+            const auto duty = static_cast<int>(value * MAX_INTENSITY);
+            analogWrite(this->pin_, duty);
         }
 
       private:
diff --git a/lib/arduino/senseshift/arduino/output/pca9685.hpp b/lib/arduino/senseshift/arduino/output/pca9685.hpp
index 66798efd..62ea63cf 100644
--- a/lib/arduino/senseshift/arduino/output/pca9685.hpp
+++ b/lib/arduino/senseshift/arduino/output/pca9685.hpp
@@ -10,7 +10,7 @@
 #include <Wire.h>
 
 namespace SenseShift::Arduino::Output {
-    class PCA9685Output : public ::SenseShift::Output::FloatOutput {
+    class PCA9685Output : public ::SenseShift::Output::IFloatOutput {
       public:
         static inline constexpr std::uint16_t MAX_INTENSITY = 4095;
 
@@ -20,12 +20,10 @@ namespace SenseShift::Arduino::Output {
             this->driver_->begin();
         }
 
-        void writeState(const ValueType intensity) override
+        void writeState(const ValueType value) override
         {
-            this->driver_->setPin(
-                this->channel_,
-                intensity * MAX_INTENSITY
-            );
+            const auto duty = static_cast<std::uint16_t>(value * MAX_INTENSITY);
+            this->driver_->setPin(this->channel_, duty);
         }
 
       private:
diff --git a/lib/arduino_esp32/senseshift/arduino/output/ledc.hpp b/lib/arduino_esp32/senseshift/arduino/output/ledc.hpp
index a74eba96..2d1d810c 100644
--- a/lib/arduino_esp32/senseshift/arduino/output/ledc.hpp
+++ b/lib/arduino_esp32/senseshift/arduino/output/ledc.hpp
@@ -11,7 +11,7 @@ namespace SenseShift::Arduino::Output {
     static const char *const TAG = "output.ledc";
 
     /// Arduino analog output
-    class LedcOutput : public ::SenseShift::Output::FloatOutput {
+    class LedcOutput : public ::SenseShift::Output::IFloatOutput {
     public:
         explicit LedcOutput(
           const std::uint8_t pin,
diff --git a/lib/battery/senseshift/battery.hpp b/lib/battery/senseshift/battery.hpp
index 4de3c867..c6238a0d 100644
--- a/lib/battery/senseshift/battery.hpp
+++ b/lib/battery/senseshift/battery.hpp
@@ -60,6 +60,7 @@ namespace SenseShift::Battery {
     });
 
     struct BatteryState {
+        static constexpr std::uint8_t MAX_LEVEL = 255;
         uint8_t level;
     };
 
diff --git a/lib/battery/senseshift/battery/sensor.hpp b/lib/battery/senseshift/battery/sensor.hpp
index ff05f5af..aae97c85 100644
--- a/lib/battery/senseshift/battery/sensor.hpp
+++ b/lib/battery/senseshift/battery/sensor.hpp
@@ -1,5 +1,7 @@
 #pragma once
 
+#include <cstdint>
+
 #include "senseshift/battery.hpp"
 
 #include <senseshift/freertos/task.hpp>
@@ -18,17 +20,19 @@ namespace SenseShift::Battery {
 
     class NaiveBatterySensor : public IBatterySensor {
       public:
-        NaiveBatterySensor(::SenseShift::Input::ISimpleSensor<uint16_t>* sensor) : sensor(sensor){};
+        explicit NaiveBatterySensor(::SenseShift::Input::IFloatSensor* sensor) : sensor(sensor){};
 
         [[nodiscard]] auto getValue() -> BatteryState override
         {
-            return { .level =
-                       static_cast<uint8_t>(::SenseShift::simpleMap<uint16_t>(this->sensor->getValue(), 4095, 255)) };
+            const auto level = static_cast<std::uint8_t>(this->sensor->getValue() * BatteryState::MAX_LEVEL);
+
+            return { .level = level};
         };
+
         void init() override { this->sensor->init(); }
 
       private:
-        ISimpleSensor<uint16_t> * sensor;
+        ::SenseShift::Input::IFloatSensor* sensor;
     };
 
     class BatterySensor : public ::SenseShift::Input::MemoizedSensor<::SenseShift::Battery::BatteryState> {
diff --git a/lib/bhaptics/senseshift/bh/encoding.hpp b/lib/bhaptics/senseshift/bh/encoding.hpp
index 3b05a54f..1a8b8af4 100644
--- a/lib/bhaptics/senseshift/bh/encoding.hpp
+++ b/lib/bhaptics/senseshift/bh/encoding.hpp
@@ -207,7 +207,8 @@ namespace SenseShift::BH {
       private:
         static auto effectDataFromByte(const uint8_t byte, const uint8_t maxValue = 100) -> VibroEffectData
         {
-            return VibroEffectData(byte / maxValue);
+            const auto value = static_cast<float>(byte) / static_cast<float>(maxValue);
+            return VibroEffectData(value);
         }
     };
 } // namespace SenseShift::BH
diff --git a/lib/freertos/senseshift/freertos/input/sensor.hpp b/lib/freertos/senseshift/freertos/input/sensor.hpp
index 8209aa3f..aa2b12a5 100644
--- a/lib/freertos/senseshift/freertos/input/sensor.hpp
+++ b/lib/freertos/senseshift/freertos/input/sensor.hpp
@@ -32,12 +32,12 @@ namespace SenseShift::FreeRTOS::Input {
         }
     };
 
-    template<typename _Tp>
-    class TaskedSensor : public SensorUpdateTask, public ::SenseShift::Input::ISimpleSensor<_Tp> {
+    template<typename Tp>
+    class TaskedSensor : public SensorUpdateTask, public ::SenseShift::Input::ISimpleSensor<Tp> {
         friend class SensorUpdateTask;
 
       private:
-        using Sensor = ::SenseShift::Input::MemoizedSensor<_Tp>;
+        using Sensor = ::SenseShift::Input::MemoizedSensor<Tp>;
 
       public:
         TaskedSensor(Sensor* sensor, std::uint32_t updateDelay, TaskConfig taskConfig) :
@@ -51,7 +51,7 @@ namespace SenseShift::FreeRTOS::Input {
 
         void init() override { this->sensor->init(); };
 
-        _Tp getValue() override { return this->sensor->getValue(); };
+        Tp getValue() override { return this->sensor->getValue(); };
 
       private:
         Sensor* sensor;
diff --git a/lib/haptics/senseshift/body/haptics/body.cpp b/lib/haptics/senseshift/body/haptics/body.cpp
index e60eb210..5caa7beb 100644
--- a/lib/haptics/senseshift/body/haptics/body.cpp
+++ b/lib/haptics/senseshift/body/haptics/body.cpp
@@ -19,5 +19,5 @@ namespace SenseShift::Body::Haptics {
         find->second->effect(pos, val);
     }
 
-    template class OutputBody<Position::Value, Output::FloatOutput::ValueType>;
+    template class OutputBody<Position::Value, Output::IFloatOutput::ValueType>;
 } // namespace SenseShift::Body::Haptics
diff --git a/lib/haptics/senseshift/body/haptics/body.hpp b/lib/haptics/senseshift/body/haptics/body.hpp
index 386866a4..6cdd2cbd 100644
--- a/lib/haptics/senseshift/body/haptics/body.hpp
+++ b/lib/haptics/senseshift/body/haptics/body.hpp
@@ -8,7 +8,7 @@
 #include <senseshift/output/output.hpp>
 
 namespace SenseShift::Body::Haptics {
-    /// Output body, contains all the output planes.
+    /// IOutput body, contains all the output planes.
     ///
     /// \tparam Tc The type of the coordinate.
     /// \tparam To The type of the output value.
@@ -40,5 +40,5 @@ namespace SenseShift::Body::Haptics {
         TargetPlaneMap targets_{};
     };
 
-    using FloatBody = OutputBody<Position::Value, Output::FloatOutput::ValueType>;
+    using FloatBody = OutputBody<Position::Value, Output::IFloatOutput::ValueType>;
 } // namespace SenseShift::Body::Haptics
diff --git a/lib/haptics/senseshift/body/haptics/plane.cpp b/lib/haptics/senseshift/body/haptics/plane.cpp
index bfbd4f33..9fbe77ee 100644
--- a/lib/haptics/senseshift/body/haptics/plane.cpp
+++ b/lib/haptics/senseshift/body/haptics/plane.cpp
@@ -77,6 +77,6 @@ namespace SenseShift::Body::Haptics {
         return nearest->second;
     }
 
-    template class OutputPlane<Position::Value, Output::FloatOutput::ValueType>;
-    template class OutputPlane_Closest<Position::Value, Output::FloatOutput::ValueType>;
+    template class OutputPlane<Position::Value, Output::IFloatOutput::ValueType>;
+    template class OutputPlane_Closest<Position::Value, Output::IFloatOutput::ValueType>;
 } // namespace SenseShift::Body::Haptics
diff --git a/lib/haptics/senseshift/body/haptics/plane.hpp b/lib/haptics/senseshift/body/haptics/plane.hpp
index eeea313b..a12b3d75 100644
--- a/lib/haptics/senseshift/body/haptics/plane.hpp
+++ b/lib/haptics/senseshift/body/haptics/plane.hpp
@@ -28,7 +28,7 @@ namespace SenseShift::Body::Haptics {
         /// The type of the output value (e.g. float) for the plane.
         using Value = To;
         /// The type of the actuator for the plane.
-        using Actuator = Output::Output<Value>;
+        using Actuator = Output::IOutput<Value>;
 
         using ActuatorMap = std::map<Position, Actuator*>;
         using PositionStateMap = std::map<Position, Value>;
@@ -73,8 +73,8 @@ namespace SenseShift::Body::Haptics {
         [[nodiscard]] static auto findClosestPoint(const PositionSet&, const Position&) -> const Position&;
     };
 
-    using FloatPlane = OutputPlane<Position::Value, Output::FloatOutput::ValueType>;
-    using FloatPlane_Closest = OutputPlane_Closest<Position::Value, Output::FloatOutput::ValueType>;
+    using FloatPlane = OutputPlane<Position::Value, Output::IFloatOutput::ValueType>;
+    using FloatPlane_Closest = OutputPlane_Closest<Position::Value, Output::IFloatOutput::ValueType>;
 
     // TODO: configurable margin
     class PlaneMapper_Margin {
diff --git a/lib/io/senseshift/input/sensor.hpp b/lib/io/senseshift/input/sensor.hpp
index 22b8a116..a8302172 100644
--- a/lib/io/senseshift/input/sensor.hpp
+++ b/lib/io/senseshift/input/sensor.hpp
@@ -21,7 +21,7 @@ namespace SenseShift::Input {
     /// \tparam Tp Type of the sensor value
     template<typename Tp>
     class ISimpleSensor : virtual public IInitializable {
-    public:
+      public:
         using ValueType = Tp;
 
         /// Get the current sensor value
@@ -29,9 +29,10 @@ namespace SenseShift::Input {
     };
 
     using IBinarySensor = ISimpleSensor<bool>;
+    using IFloatSensor = ISimpleSensor<float>;
 
     template<typename Tp>
-    class ISensor : virtual ISimpleSensor<Tp>, ITickable {};
+    class ISensor : public virtual ISimpleSensor<Tp>, public ITickable {};
 
     /// Memoized sensor decorator. Stores the last read value and returns it on subsequent calls
     /// \tparam Tp Type of the sensor value
diff --git a/lib/io/senseshift/output/binary_output.hpp b/lib/io/senseshift/output/binary_output.hpp
deleted file mode 100644
index c9200a08..00000000
--- a/lib/io/senseshift/output/binary_output.hpp
+++ /dev/null
@@ -1,10 +0,0 @@
-#pragma once
-
-#include "senseshift/output/output.hpp"
-
-namespace SenseShift::Output {
-    class BinaryOutput : public ::SenseShift::Output::Output<bool> {
-      public:
-        virtual void writeOutput(bool) = 0;
-    };
-} // namespace SenseShift::Output
\ No newline at end of file
diff --git a/lib/io/senseshift/output/output.hpp b/lib/io/senseshift/output/output.hpp
index c02bfaf7..0392714f 100644
--- a/lib/io/senseshift/output/output.hpp
+++ b/lib/io/senseshift/output/output.hpp
@@ -4,12 +4,13 @@
 
 namespace SenseShift::Output {
     template<typename Tp>
-    class Output : public IInitializable {
+    class IOutput : public IInitializable {
     public:
         using ValueType = Tp;
 
         virtual void writeState(ValueType value) = 0;
     };
 
-    using FloatOutput = Output<float>;
+    using IBinaryOutput = IOutput<bool>;
+    using IFloatOutput = IOutput<float>;
 } // namespace SenseShift::Output
\ No newline at end of file

From 2d163b617c6959723754b6a797bc7ca8b2ca5e05 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Mon, 29 Jan 2024 18:47:55 +0400
Subject: [PATCH 51/82] refactor(Sensor): new sensor stack with filters

---
 firmware/mode_configs/bhaptics/tactal.cpp     |  25 +-
 firmware/mode_configs/bhaptics/tactglove.cpp  |  25 +-
 firmware/mode_configs/bhaptics/tactosy2.cpp   |  25 +-
 firmware/mode_configs/bhaptics/tactosyf.cpp   |  25 +-
 firmware/mode_configs/bhaptics/tactosyh.cpp   |  25 +-
 .../mode_configs/bhaptics/tactsuit_x16.cpp    |  25 +-
 .../bhaptics/tactsuit_x16_pca9685.cpp         |  25 +-
 .../mode_configs/bhaptics/tactsuit_x40.cpp    |  25 +-
 firmware/mode_configs/bhaptics/tactvisor.cpp  |  25 +-
 .../senseshift/arduino/battery/ina219.hpp     |  33 --
 .../senseshift/arduino/battery/max17048.hpp   |  45 ---
 .../arduino/input/sensor/analog.hpp           |  23 +-
 lib/battery/senseshift/battery.hpp            |  88 +++---
 lib/battery/senseshift/battery/sensor.hpp     |  58 ++--
 lib/core/senseshift/core/helpers.hpp          |  78 ++++-
 .../senseshift/freertos/input/sensor.hpp      |  56 ++--
 lib/freertos/senseshift/freertos/task.hpp     |  21 +-
 .../senseshift/input}/calibration.hpp         |  40 +--
 lib/io/senseshift/input/filter.hpp            | 284 +++++++++++++++++
 lib/io/senseshift/input/sensor.hpp            | 285 +++++++++++-------
 lib/io/senseshift/input/sensor/joystick.hpp   |  38 ---
 .../senseshift/opengloves/interface.hpp       |  26 +-
 lib/opengloves/sensor/og_sensor.hpp           |  82 +----
 test/test_util_calibration/main.cpp           |   2 +-
 24 files changed, 874 insertions(+), 510 deletions(-)
 delete mode 100644 lib/arduino/senseshift/arduino/battery/ina219.hpp
 delete mode 100644 lib/arduino/senseshift/arduino/battery/max17048.hpp
 rename lib/{util/senseshift => io/senseshift/input}/calibration.hpp (82%)
 create mode 100644 lib/io/senseshift/input/filter.hpp
 delete mode 100644 lib/io/senseshift/input/sensor/joystick.hpp

diff --git a/firmware/mode_configs/bhaptics/tactal.cpp b/firmware/mode_configs/bhaptics/tactal.cpp
index 08f35d38..e81c311d 100644
--- a/firmware/mode_configs/bhaptics/tactal.cpp
+++ b/firmware/mode_configs/bhaptics/tactal.cpp
@@ -15,6 +15,7 @@
 #include <senseshift/freertos/input/sensor.hpp>
 
 using namespace SenseShift;
+using namespace SenseShift::Input;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
@@ -54,12 +55,26 @@ void setupMode()
     bhBleConnection->begin();
 
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
-    auto* battery = new TaskedSensor<BatteryState>(
-      new BatterySensor(new NaiveBatterySensor(new AnalogSensor(36)), app),
-      SENSESHIFT_BATTERY_SAMPLE_RATE,
-      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
+    batteryVoltageSensor->addFilters({
+        new MultiplyFilter(3.3F), // Convert to raw pin voltage
+        new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
+    });
+    auto* batteryTask = new SensorUpdateTask<SimpleSensorDecorator<float>>(
+            batteryVoltageSensor,
+            SENSESHIFT_BATTERY_SAMPLE_RATE,
+            { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    );
+    batteryTask->begin();
+
+    auto* batterySensor = new LookupTableInterpolateBatterySensor<const frozen::map<float, float, 21>>(
+      batteryVoltageSensor,
+      &VoltageMap::LiPO_1S_42
     );
-    battery->begin();
+    batterySensor->addValueCallback([](BatteryState value) -> void {
+        app->postEvent(new BatteryLevelEvent(value));
+    });
+    batterySensor->init();
 #endif
 }
 
diff --git a/firmware/mode_configs/bhaptics/tactglove.cpp b/firmware/mode_configs/bhaptics/tactglove.cpp
index 7463c1b9..2cca8354 100644
--- a/firmware/mode_configs/bhaptics/tactglove.cpp
+++ b/firmware/mode_configs/bhaptics/tactglove.cpp
@@ -16,6 +16,7 @@
 #include <senseshift/utility.hpp>
 
 using namespace SenseShift;
+using namespace SenseShift::Input;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
@@ -62,12 +63,26 @@ void setupMode()
     bhBleConnection->begin();
 
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
-    auto* battery = new TaskedSensor<BatteryState>(
-      new BatterySensor(new NaiveBatterySensor(new AnalogSensor(36)), app),
-      SENSESHIFT_BATTERY_SAMPLE_RATE,
-      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
+    batteryVoltageSensor->addFilters({
+        new MultiplyFilter(3.3F), // Convert to raw pin voltage
+        new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
+    });
+    auto* batteryTask = new SensorUpdateTask<SimpleSensorDecorator<float>>(
+            batteryVoltageSensor,
+            SENSESHIFT_BATTERY_SAMPLE_RATE,
+            { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
-    battery->begin();
+    batteryTask->begin();
+
+    auto* batterySensor = new LookupTableInterpolateBatterySensor<const frozen::map<float, float, 21>>(
+      batteryVoltageSensor,
+      &VoltageMap::LiPO_1S_42
+    );
+    batterySensor->addValueCallback([](BatteryState value) -> void {
+        app->postEvent(new BatteryLevelEvent(value));
+    });
+    batterySensor->init();
 #endif
 }
 
diff --git a/firmware/mode_configs/bhaptics/tactosy2.cpp b/firmware/mode_configs/bhaptics/tactosy2.cpp
index 74cbcdfd..fb20ce80 100644
--- a/firmware/mode_configs/bhaptics/tactosy2.cpp
+++ b/firmware/mode_configs/bhaptics/tactosy2.cpp
@@ -15,6 +15,7 @@
 #include <senseshift/freertos/input/sensor.hpp>
 
 using namespace SenseShift;
+using namespace SenseShift::Input;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
@@ -55,12 +56,26 @@ void setupMode()
     bhBleConnection->begin();
 
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
-    auto* battery = new TaskedSensor<BatteryState>(
-      new BatterySensor(new NaiveBatterySensor(new AnalogSensor(36)), app),
-      SENSESHIFT_BATTERY_SAMPLE_RATE,
-      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
+    batteryVoltageSensor->addFilters({
+        new MultiplyFilter(3.3F), // Convert to raw pin voltage
+        new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
+    });
+    auto* batteryTask = new SensorUpdateTask<SimpleSensorDecorator<float>>(
+            batteryVoltageSensor,
+            SENSESHIFT_BATTERY_SAMPLE_RATE,
+            { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    );
+    batteryTask->begin();
+
+    auto* batterySensor = new LookupTableInterpolateBatterySensor<const frozen::map<float, float, 21>>(
+      batteryVoltageSensor,
+      &VoltageMap::LiPO_1S_42
     );
-    battery->begin();
+    batterySensor->addValueCallback([](BatteryState value) -> void {
+        app->postEvent(new BatteryLevelEvent(value));
+    });
+    batterySensor->init();
 #endif
 }
 
diff --git a/firmware/mode_configs/bhaptics/tactosyf.cpp b/firmware/mode_configs/bhaptics/tactosyf.cpp
index c8352ee8..cc623ce2 100644
--- a/firmware/mode_configs/bhaptics/tactosyf.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyf.cpp
@@ -15,6 +15,7 @@
 #include <senseshift/freertos/input/sensor.hpp>
 
 using namespace SenseShift;
+using namespace SenseShift::Input;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
@@ -56,12 +57,26 @@ void setupMode()
     bhBleConnection->begin();
 
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
-    auto* battery = new TaskedSensor<BatteryState>(
-      new BatterySensor(new NaiveBatterySensor(new AnalogSensor(36)), app),
-      SENSESHIFT_BATTERY_SAMPLE_RATE,
-      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
+    batteryVoltageSensor->addFilters({
+        new MultiplyFilter(3.3F), // Convert to raw pin voltage
+        new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
+    });
+    auto* batteryTask = new SensorUpdateTask<SimpleSensorDecorator<float>>(
+            batteryVoltageSensor,
+            SENSESHIFT_BATTERY_SAMPLE_RATE,
+            { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    );
+    batteryTask->begin();
+
+    auto* batterySensor = new LookupTableInterpolateBatterySensor<const frozen::map<float, float, 21>>(
+      batteryVoltageSensor,
+      &VoltageMap::LiPO_1S_42
     );
-    battery->begin();
+    batterySensor->addValueCallback([](BatteryState value) -> void {
+        app->postEvent(new BatteryLevelEvent(value));
+    });
+    batterySensor->init();
 #endif
 }
 
diff --git a/firmware/mode_configs/bhaptics/tactosyh.cpp b/firmware/mode_configs/bhaptics/tactosyh.cpp
index a4b8574f..05e6ba9f 100644
--- a/firmware/mode_configs/bhaptics/tactosyh.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyh.cpp
@@ -15,6 +15,7 @@
 #include <senseshift/freertos/input/sensor.hpp>
 
 using namespace SenseShift;
+using namespace SenseShift::Input;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
@@ -56,12 +57,26 @@ void setupMode()
     bhBleConnection->begin();
 
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
-    auto* battery = new TaskedSensor<BatteryState>(
-      new BatterySensor(new NaiveBatterySensor(new AnalogSensor(36)), app),
-      SENSESHIFT_BATTERY_SAMPLE_RATE,
-      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
+    batteryVoltageSensor->addFilters({
+        new MultiplyFilter(3.3F), // Convert to raw pin voltage
+        new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
+    });
+    auto* batteryTask = new SensorUpdateTask<SimpleSensorDecorator<float>>(
+            batteryVoltageSensor,
+            SENSESHIFT_BATTERY_SAMPLE_RATE,
+            { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    );
+    batteryTask->begin();
+
+    auto* batterySensor = new LookupTableInterpolateBatterySensor<const frozen::map<float, float, 21>>(
+      batteryVoltageSensor,
+      &VoltageMap::LiPO_1S_42
     );
-    battery->begin();
+    batterySensor->addValueCallback([](BatteryState value) -> void {
+        app->postEvent(new BatteryLevelEvent(value));
+    });
+    batterySensor->init();
 #endif
 }
 
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
index 52b95ccf..f828ec19 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
@@ -15,6 +15,7 @@
 #include <senseshift/freertos/input/sensor.hpp>
 
 using namespace SenseShift;
+using namespace SenseShift::Input;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
@@ -65,12 +66,26 @@ void setupMode()
     bhBleConnection->begin();
 
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
-    auto* battery = new TaskedSensor<BatteryState>(
-      new BatterySensor(new NaiveBatterySensor(new AnalogSensor(36)), app),
-      SENSESHIFT_BATTERY_SAMPLE_RATE,
-      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
+    batteryVoltageSensor->addFilters({
+        new MultiplyFilter(3.3F), // Convert to raw pin voltage
+        new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
+    });
+    auto* batteryTask = new SensorUpdateTask<SimpleSensorDecorator<float>>(
+            batteryVoltageSensor,
+            SENSESHIFT_BATTERY_SAMPLE_RATE,
+            { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    );
+    batteryTask->begin();
+
+    auto* batterySensor = new LookupTableInterpolateBatterySensor<const frozen::map<float, float, 21>>(
+      batteryVoltageSensor,
+      &VoltageMap::LiPO_1S_42
     );
-    battery->begin();
+    batterySensor->addValueCallback([](BatteryState value) -> void {
+        app->postEvent(new BatteryLevelEvent(value));
+    });
+    batterySensor->init();
 #endif
 }
 
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
index 87869e43..b1066077 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
@@ -15,6 +15,7 @@
 #include <senseshift/freertos/input/sensor.hpp>
 
 using namespace SenseShift;
+using namespace SenseShift::Input;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
@@ -70,12 +71,26 @@ void setupMode()
     bhBleConnection->begin();
 
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
-    auto* battery = new TaskedSensor<BatteryState>(
-      new BatterySensor(new NaiveBatterySensor(new AnalogSensor(36)), app),
-      SENSESHIFT_BATTERY_SAMPLE_RATE,
-      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
+    batteryVoltageSensor->addFilters({
+        new MultiplyFilter(3.3F), // Convert to raw pin voltage
+        new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
+    });
+    auto* batteryTask = new SensorUpdateTask<SimpleSensorDecorator<float>>(
+            batteryVoltageSensor,
+            SENSESHIFT_BATTERY_SAMPLE_RATE,
+            { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    );
+    batteryTask->begin();
+
+    auto* batterySensor = new LookupTableInterpolateBatterySensor<const frozen::map<float, float, 21>>(
+      batteryVoltageSensor,
+      &VoltageMap::LiPO_1S_42
     );
-    battery->begin();
+    batterySensor->addValueCallback([](BatteryState value) -> void {
+        app->postEvent(new BatteryLevelEvent(value));
+    });
+    batterySensor->init();
 #endif
 }
 
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
index 8a036950..48ea44ee 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
@@ -16,6 +16,7 @@
 #include <senseshift/freertos/input/sensor.hpp>
 
 using namespace SenseShift;
+using namespace SenseShift::Input;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
@@ -79,12 +80,26 @@ void setupMode()
     bhBleConnection->begin();
 
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
-    auto* battery = new TaskedSensor<BatteryState>(
-      new BatterySensor(new NaiveBatterySensor(new AnalogSensor(36)), app),
-      SENSESHIFT_BATTERY_SAMPLE_RATE,
-      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
+    batteryVoltageSensor->addFilters({
+        new MultiplyFilter(3.3F), // Convert to raw pin voltage
+        new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
+    });
+    auto* batteryTask = new SensorUpdateTask<SimpleSensorDecorator<float>>(
+            batteryVoltageSensor,
+            SENSESHIFT_BATTERY_SAMPLE_RATE,
+            { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    );
+    batteryTask->begin();
+
+    auto* batterySensor = new LookupTableInterpolateBatterySensor<const frozen::map<float, float, 21>>(
+      batteryVoltageSensor,
+      &VoltageMap::LiPO_1S_42
     );
-    battery->begin();
+    batterySensor->addValueCallback([](BatteryState value) -> void {
+        app->postEvent(new BatteryLevelEvent(value));
+    });
+    batterySensor->init();
 #endif
 }
 
diff --git a/firmware/mode_configs/bhaptics/tactvisor.cpp b/firmware/mode_configs/bhaptics/tactvisor.cpp
index 1c3f02cb..d5c7565c 100644
--- a/firmware/mode_configs/bhaptics/tactvisor.cpp
+++ b/firmware/mode_configs/bhaptics/tactvisor.cpp
@@ -15,6 +15,7 @@
 #include <senseshift/freertos/input/sensor.hpp>
 
 using namespace SenseShift;
+using namespace SenseShift::Input;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
@@ -54,12 +55,26 @@ void setupMode()
     bhBleConnection->begin();
 
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
-    auto* battery = new TaskedSensor<BatteryState>(
-      new BatterySensor(new NaiveBatterySensor(new AnalogSensor(36)), app),
-      SENSESHIFT_BATTERY_SAMPLE_RATE,
-      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
+    batteryVoltageSensor->addFilters({
+        new MultiplyFilter(3.3F), // Convert to raw pin voltage
+        new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
+    });
+    auto* batteryTask = new SensorUpdateTask<SimpleSensorDecorator<float>>(
+            batteryVoltageSensor,
+            SENSESHIFT_BATTERY_SAMPLE_RATE,
+            { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    );
+    batteryTask->begin();
+
+    auto* batterySensor = new LookupTableInterpolateBatterySensor<const frozen::map<float, float, 21>>(
+      batteryVoltageSensor,
+      &VoltageMap::LiPO_1S_42
     );
-    battery->begin();
+    batterySensor->addValueCallback([](BatteryState value) -> void {
+        app->postEvent(new BatteryLevelEvent(value));
+    });
+    batterySensor->init();
 #endif
 }
 
diff --git a/lib/arduino/senseshift/arduino/battery/ina219.hpp b/lib/arduino/senseshift/arduino/battery/ina219.hpp
deleted file mode 100644
index a343488d..00000000
--- a/lib/arduino/senseshift/arduino/battery/ina219.hpp
+++ /dev/null
@@ -1,33 +0,0 @@
-#pragma once
-
-#include <senseshift/battery/sensor.hpp>
-
-#include <Adafruit_INA219.h>
-
-namespace SenseShift::Arduino::Battery {
-    class INA219_Battery : public ::SenseShift::Battery::IBatterySensor {
-      private:
-        bool active = false;
-        Adafruit_INA219* sensor;
-
-      public:
-        INA219_Battery(Adafruit_INA219* sensor) : sensor(sensor){};
-
-        void init() override { this->active = this->sensor->begin(); }
-
-        ::SenseShift::Battery::BatteryState getValue() override
-        {
-            if (!this->active) {
-                return { 0 };
-            }
-
-            auto batteryVoltage = this->sensor->getBusVoltage_V();
-            // TODO: change this linear transformation to smth more useful
-            auto batteryPercentage = (batteryVoltage - 3.0) / 0.96;
-
-            return {
-                simpleMap<float>(batteryPercentage, 1.0f, 255.0f),
-            };
-        }
-    };
-} // namespace SenseShift::Arduino::Battery
diff --git a/lib/arduino/senseshift/arduino/battery/max17048.hpp b/lib/arduino/senseshift/arduino/battery/max17048.hpp
deleted file mode 100644
index dc886cc2..00000000
--- a/lib/arduino/senseshift/arduino/battery/max17048.hpp
+++ /dev/null
@@ -1,45 +0,0 @@
-#pragma once
-
-#include <senseshift/battery/sensor.hpp>
-
-#include <SparkFun_MAX1704x_Fuel_Gauge_Arduino_Library.h>
-
-namespace SenseShift::Arduino::Battery {
-    class MAX1704_Battery : public ::SenseShift::Battery::IBatterySensor {
-      public:
-        MAX1704_Battery(SFE_MAX1704X* gauge) : gauge(gauge){};
-
-        /**
-         * @see
-         * https://github.com/sparkfun/SparkFun_MAX1704x_Fuel_Gauge_Arduino_Library/blob/main/examples/Example1_Simple/Example1_Simple.ino
-         */
-        void init() override
-        {
-            // Set up the MAX17043 LiPo fuel gauge:
-            this->active = this->gauge->begin();
-
-            if (this->active) {
-                // Quick start restarts the MAX17043 in hopes of getting a more accurate
-                // guess for the SOC.
-                this->gauge->quickStart();
-
-                // We can set an interrupt to alert when the battery SoC gets too low.
-                // We can alert at anywhere between 1% - 32%:
-                // this->gauge->setThreshold(SENSESHIFT_BATTERY_THRESHOLD_PERCENTAGE);
-            }
-        }
-
-        ::SenseShift::Battery::BatteryState getValue() override
-        {
-            if (!this->active) {
-                return { 0 };
-            }
-
-            return { .level = simpleMap(this->gauge->getSOC(), 1.0f, 255.0f) };
-        }
-
-      private:
-        bool active = false;
-        SFE_MAX1704X* gauge;
-    };
-} // namespace SenseShift::Arduino::Battery
diff --git a/lib/arduino/senseshift/arduino/input/sensor/analog.hpp b/lib/arduino/senseshift/arduino/input/sensor/analog.hpp
index e95f14f4..dbef7c39 100644
--- a/lib/arduino/senseshift/arduino/input/sensor/analog.hpp
+++ b/lib/arduino/senseshift/arduino/input/sensor/analog.hpp
@@ -6,13 +6,24 @@
 
 #include <Arduino.h>
 
+#if defined(__AVR__)
+#define ANALOG_MAX 1023.0F
+#elif defined(ESP32)
+#define ANALOG_MAX 4095.0F
+#elif !defined(ANALOG_MAX)
+#warning "This board doesn't have an auto ANALOG_MAX assignment, please set it manually"
+#define ANALOG_MAX static_assert(false, "ANALOG_MAX is not defined")
+// Uncomment and set as needed (only touch if you know what you are doing)
+// #define ANALOG_MAX 4095.0F
+#endif
+
 namespace SenseShift::Arduino::Input {
     template<bool Invert = false>
-    class AnalogSensor : public ::SenseShift::Input::IFloatSensor {
+    class AnalogSimpleSensor : public ::SenseShift::Input::IFloatSimpleSensor {
         std::uint8_t pin_;
 
       public:
-        AnalogSensor(const std::uint8_t pin) : pin_(pin) {}
+        explicit AnalogSimpleSensor(const std::uint8_t pin) : pin_(pin) {}
 
         void init() override { pinMode(this->pin_, INPUT); };
 
@@ -20,16 +31,16 @@ namespace SenseShift::Arduino::Input {
     };
 
     template<>
-    [[nodiscard]] inline auto AnalogSensor<false>::getValue() -> float
+    [[nodiscard]] inline auto AnalogSimpleSensor<false>::getValue() -> float
     {
-        const auto raw = analogRead(this->pin_);
+        const std::uint16_t raw = analogRead(this->pin_);
         return static_cast<float>(raw) / ANALOG_MAX;
     }
 
     template<>
-    [[nodiscard]] inline auto AnalogSensor<true>::getValue() -> float
+    [[nodiscard]] inline auto AnalogSimpleSensor<true>::getValue() -> float
     {
-        const auto raw = ANALOG_MAX - analogRead(this->pin_);
+        const std::uint16_t raw = ANALOG_MAX - analogRead(this->pin_);
         return static_cast<float>(raw) / ANALOG_MAX;
     }
 } // namespace SenseShift::Arduino::Input
diff --git a/lib/battery/senseshift/battery.hpp b/lib/battery/senseshift/battery.hpp
index c6238a0d..ed784445 100644
--- a/lib/battery/senseshift/battery.hpp
+++ b/lib/battery/senseshift/battery.hpp
@@ -1,67 +1,49 @@
 #pragma once
 
 #include <cstdint>
+#include <limits>
+#include <algorithm>
 
 #include <frozen/map.h>
 
 #include <senseshift/events.hpp>
 
 namespace SenseShift::Battery {
-    // Source: https://blog.ampow.com/lipo-voltage-chart/
-    inline static const auto voltageToPercents_1s = frozen::make_map<float, float>({
-      // clang-format off
-        { 4.2, 1.0 },
-        { 4.15, 0.95 },
-        { 4.11, 0.9 },
-        { 4.08, 0.85 },
-        { 4.02, 0.8 },
-        { 3.98, 0.75 },
-        { 3.95, 0.7 },
-        { 3.91, 0.65 },
-        { 3.87, 0.6 },
-        { 3.85, 0.55 },
-        { 3.84, 0.5 },
-        { 3.82, 0.45 },
-        { 3.8, 0.4 },
-        { 3.79, 0.35 },
-        { 3.77, 0.3 },
-        { 3.75, 0.25 },
-        { 3.73, 0.2 },
-        { 3.71, 0.15 },
-        { 3.69, 0.1 },
-        { 3.61, 0.05 },
-        { 3.27, 0.0 },
-      // clang-format on
-    });
-    inline static const auto voltageToPercents_2s = frozen::make_map<float, float>({
-      // clang-format off
-        { 8.4, 1.0 },
-        { 8.3, 0.95 },
-        { 8.22, 0.9 },
-        { 8.16, 0.85 },
-        { 8.05, 0.8 },
-        { 7.97, 0.75 },
-        { 7.91, 0.7 },
-        { 7.83, 0.65 },
-        { 7.75, 0.6 },
-        { 7.71, 0.55 },
-        { 7.67, 0.5 },
-        { 7.63, 0.45 },
-        { 7.59, 0.4 },
-        { 7.57, 0.35 },
-        { 7.53, 0.3 },
-        { 7.49, 0.25 },
-        { 7.45, 0.2 },
-        { 7.41, 0.15 },
-        { 7.37, 0.1 },
-        { 7.22, 0.05 },
-        { 6.55, 0.0 },
-      // clang-format on
-    });
+
+    namespace VoltageMap {
+        /// Lookup table for LiPO 1S 4.2V batteries
+        /// \see <a href="https://blog.ampow.com/lipo-voltage-chart/">Source</a>
+        [[maybe_unused]] inline static const auto LiPO_1S_42= frozen::make_map<float, float>({
+            { 4.2, 1.0 },
+            { 4.15, 0.95 },
+            { 4.11, 0.9 },
+            { 4.08, 0.85 },
+            { 4.02, 0.8 },
+            { 3.98, 0.75 },
+            { 3.95, 0.7 },
+            { 3.91, 0.65 },
+            { 3.87, 0.6 },
+            { 3.85, 0.55 },
+            { 3.84, 0.5 },
+            { 3.82, 0.45 },
+            { 3.8, 0.4 },
+            { 3.79, 0.35 },
+            { 3.77, 0.3 },
+            { 3.75, 0.25 },
+            { 3.73, 0.2 },
+            { 3.71, 0.15 },
+            { 3.69, 0.1 },
+            { 3.61, 0.05 },
+            { 3.27, 0.0 },
+        });
+    } // namespace VoltageMap
 
     struct BatteryState {
-        static constexpr std::uint8_t MAX_LEVEL = 255;
-        uint8_t level;
+        using VoltageType = float;
+        using LevelType = std::uint8_t;
+
+        static constexpr LevelType MAX_LEVEL = std::numeric_limits<LevelType>::max();
+        LevelType level;
     };
 
     class BatteryLevelEvent : public IEvent {
diff --git a/lib/battery/senseshift/battery/sensor.hpp b/lib/battery/senseshift/battery/sensor.hpp
index aae97c85..fdfa525d 100644
--- a/lib/battery/senseshift/battery/sensor.hpp
+++ b/lib/battery/senseshift/battery/sensor.hpp
@@ -13,41 +13,47 @@
 #endif
 
 namespace SenseShift::Battery {
-    /**
-     * Abstract battery sensor
-     */
-    using IBatterySensor = ::SenseShift::Input::ISimpleSensor<BatteryState>;
+    /// Abstract battery sensor
+    using IBatterySensor = ::SenseShift::Input::Sensor<BatteryState>;
+
+    /// Interpolate voltage according to a lookup table.
+    template<typename Container>
+    class LookupTableInterpolateBatterySensor : public IBatterySensor {
+        static_assert(std::is_same_v<typename Container::key_type, typename BatteryState::VoltageType>);
+        static_assert(std::is_same_v<typename Container::mapped_type, float>);
 
-    class NaiveBatterySensor : public IBatterySensor {
       public:
-        explicit NaiveBatterySensor(::SenseShift::Input::IFloatSensor* sensor) : sensor(sensor){};
+        using VoltageType = typename BatteryState::VoltageType;
+        using VoltageSource = ::SenseShift::Input::Sensor<VoltageType>;
 
-        [[nodiscard]] auto getValue() -> BatteryState override
-        {
-            const auto level = static_cast<std::uint8_t>(this->sensor->getValue() * BatteryState::MAX_LEVEL);
+        LookupTableInterpolateBatterySensor(
+          VoltageSource* voltage_source,
+          Container* lookup_table
+        ) : IBatterySensor(), voltage_source_(voltage_source), lookup_table_(lookup_table) {}
 
-            return { .level = level};
-        };
+        void init() override {
+            this->voltage_source_->init();
+            this->voltage_source_->addValueCallback([this](VoltageType voltage) {
+                // Current level in % (0.0 - 1.0)
+                auto level = this->lookupInterpolateLevel(voltage);
 
-        void init() override { this->sensor->init(); }
+                LOG_D("battery.sensor", "voltage=%f, level=%f", voltage, level);
 
-      private:
-        ::SenseShift::Input::IFloatSensor* sensor;
-    };
+                const BatteryState value = {
+                  .level = static_cast<typename BatteryState::LevelType>(level * BatteryState::MAX_LEVEL),
+                };
 
-    class BatterySensor : public ::SenseShift::Input::MemoizedSensor<::SenseShift::Battery::BatteryState> {
-      public:
-        BatterySensor(::SenseShift::Battery::IBatterySensor* sensor, ::SenseShift::IEventDispatcher* eventDispatcher) :
-          ::SenseShift::Input::MemoizedSensor<::SenseShift::Battery::BatteryState>(sensor),
-          eventDispatcher(eventDispatcher){};
-
-        void tick() override
-        {
-            this->::SenseShift::Input::MemoizedSensor<::SenseShift::Battery::BatteryState>::tick();
-            this->eventDispatcher->postEvent(new ::SenseShift::Battery::BatteryLevelEvent(this->getValue()));
+                this->publishState(value);
+            });
+        }
+
+      protected:
+        [[nodiscard]] auto lookupInterpolateLevel(VoltageType voltage) -> float {
+            return ::SenseShift::lookup_interpolate<VoltageType, float, Container>(*this->lookup_table_, voltage);
         }
 
       private:
-        ::SenseShift::IEventDispatcher* eventDispatcher;
+        VoltageSource* voltage_source_;
+        Container* lookup_table_;
     };
 } // namespace SenseShift::Battery
diff --git a/lib/core/senseshift/core/helpers.hpp b/lib/core/senseshift/core/helpers.hpp
index 8a68a2fb..35cfe2a0 100644
--- a/lib/core/senseshift/core/helpers.hpp
+++ b/lib/core/senseshift/core/helpers.hpp
@@ -1,14 +1,16 @@
 #pragma once
 
+#include <functional>
 #include <type_traits>
+#include <vector>
 
 namespace SenseShift {
-    /// @name Mathematics
-    /// @{
-
     /// Linearly interpolate between \p start and \p end by \p completion (between 0 and 1).
-    constexpr auto lerp(const float completion, const float start, const float end) -> float
+    template<typename Tp>
+    constexpr auto lerp(const float completion, const Tp start, const Tp end) -> Tp
     {
+        static_assert(std::is_arithmetic_v<Tp>, "Type must be arithmetic");
+
         return start + (end - start) * completion;
     }
 
@@ -27,13 +29,12 @@ namespace SenseShift {
     template<typename Tp, typename Up>
     constexpr auto remap(Up value, Up min, Up max, Tp min_out, Tp max_out) -> Tp
     {
-        static_assert(std::is_arithmetic_v<Up>, "Type must be arithmetic");
         static_assert(std::is_arithmetic_v<Tp>, "Type must be arithmetic");
+        static_assert(std::is_arithmetic_v<Up>, "Type must be arithmetic");
 
         return (value - min) * (max_out - min_out) / (max - min) + min_out;
     }
 
-
     /// Remap \p value from the range (0, \p max) to (0, \p max_out).
     ///
     /// \tparam Tp The output type.
@@ -55,5 +56,68 @@ namespace SenseShift {
         return value * max_out / max;
     }
 
-    /// @}
+    /// Lookup a value in a table and interpolate between the two closest values.
+    ///
+    /// \tparam Tp
+    /// \tparam To
+    /// \tparam Container
+    ///
+    /// \param lookup_table Lookup table to use in the format of std::map<Tp, Tp> in descending order.
+    /// \param value
+    ///
+    /// \return
+    template <typename Tp, typename To, typename Container>
+    auto lookup_interpolate(Container const& lookup_table, Tp value) -> To
+    {
+        static_assert(std::is_same_v<typename Container::key_type, Tp>);
+        static_assert(std::is_same_v<typename Container::mapped_type, To>);
+        static_assert(std::is_arithmetic_v<Tp>, "lookup_interpolate only supports arithmetic types");
+        static_assert(std::is_arithmetic_v<To>, "lookup_interpolate only supports arithmetic types");
+
+        // If the value is outside the range of the lookup table, return the closest value
+        if (value <= lookup_table.begin()->first) {
+            return lookup_table.begin()->second;
+        }
+        if (value >= lookup_table.rbegin()->first) {
+            return lookup_table.rbegin()->second;
+        }
+
+        // Find the two closest values in the lookup table
+        auto const upper = lookup_table.lower_bound(value);
+        auto const lower = std::prev(upper);
+
+        // Interpolate between the two closest values
+        float const completion = (value - lower->first) / (upper->first - lower->first);
+
+        return lerp(completion, lower->second, upper->second);
+    }
+
+    template<typename... X>
+    class CallbackManager;
+
+     /// Helper class to allow having multiple subscribers to a callback.
+     ///
+     /// \tparam Ts The arguments for the callbacks, wrapped in void().
+    template<typename... Ts>
+    class CallbackManager<void(Ts...)> {
+      public:
+        using CallbackType = std::function<void(Ts...)>;
+
+        /// Add a callback to the list.
+        void add(std::function<void(Ts...)> &&callback) { this->callbacks_.push_back(std::move(callback)); }
+
+        /// Call all callbacks in this manager.
+        void call(Ts... args) {
+            for (auto &callback : this->callbacks_) {
+                callback(args...);
+            }
+        }
+        [[nodiscard]] auto size() const -> size_t { return this->callbacks_.size(); }
+
+        /// Call all callbacks in this manager.
+        void operator()(Ts... args) { call(args...); }
+
+      private:
+        std::vector<std::function<void(Ts...)>> callbacks_;
+    };
 }  // namespace SenseShift
\ No newline at end of file
diff --git a/lib/freertos/senseshift/freertos/input/sensor.hpp b/lib/freertos/senseshift/freertos/input/sensor.hpp
index aa2b12a5..667a2581 100644
--- a/lib/freertos/senseshift/freertos/input/sensor.hpp
+++ b/lib/freertos/senseshift/freertos/input/sensor.hpp
@@ -6,54 +6,36 @@
 #include <cstdint>
 
 namespace SenseShift::FreeRTOS::Input {
-    /**
-     * Sensor update task
-     */
-    class SensorUpdateTask : public Task<SensorUpdateTask> {
-        friend class Task<SensorUpdateTask>;
-
-      private:
-        using Sensor = ::SenseShift::ITickable;
+    /// Sensor update task
+    template<typename Sensor>
+    class SensorUpdateTask : public Task<SensorUpdateTask<Sensor>> {
+        static_assert(std::is_same_v<decltype(&Sensor::init), void (Sensor::*)()>);
+        static_assert(std::is_same_v<decltype(&Sensor::tick), void (Sensor::*)()>);
 
       public:
         SensorUpdateTask(Sensor* sensor, std::uint32_t updateDelay, TaskConfig taskConfig) :
-          Task<SensorUpdateTask>(taskConfig), updateDelay(updateDelay){};
+          Task<SensorUpdateTask>(taskConfig), sensor_(sensor), updateDelay_(updateDelay) {
+            log_i("creating SensorUpdateTask: %s", taskConfig.name);
+        };
 
-      protected:
-        Sensor* sensor;
-        std::uint32_t updateDelay;
+        void begin() override {
+            this->sensor_->init();
+            this->Task<SensorUpdateTask>::begin();
+        }
 
-        void run()
+      protected:
+        [[noreturn]] void run()
         {
             while (true) {
-                this->sensor->tick();
-                ::delay(this->updateDelay);
+                this->sensor_->tick();
+                delay(this->updateDelay_);
             }
         }
-    };
-
-    template<typename Tp>
-    class TaskedSensor : public SensorUpdateTask, public ::SenseShift::Input::ISimpleSensor<Tp> {
-        friend class SensorUpdateTask;
 
       private:
-        using Sensor = ::SenseShift::Input::MemoizedSensor<Tp>;
-
-      public:
-        TaskedSensor(Sensor* sensor, std::uint32_t updateDelay, TaskConfig taskConfig) :
-          SensorUpdateTask(sensor, updateDelay, taskConfig), sensor(sensor){};
-
-        void begin() override
-        {
-            this->init();
-            SensorUpdateTask::begin();
-        };
-
-        void init() override { this->sensor->init(); };
-
-        Tp getValue() override { return this->sensor->getValue(); };
+        friend class Task<SensorUpdateTask>;
 
-      private:
-        Sensor* sensor;
+        Sensor* sensor_;
+        std::uint32_t updateDelay_;
     };
 } // namespace SenseShift::FreeRTOS::Input
diff --git a/lib/freertos/senseshift/freertos/task.hpp b/lib/freertos/senseshift/freertos/task.hpp
index d06415ea..0cbb1a12 100644
--- a/lib/freertos/senseshift/freertos/task.hpp
+++ b/lib/freertos/senseshift/freertos/task.hpp
@@ -39,11 +39,9 @@ namespace SenseShift::FreeRTOS {
         friend class Task;
 
       public:
-        Task(const char* name, uint32_t stackDepth, UBaseType_t priority, const BaseType_t coreId = tskNO_AFFINITY)
-        {
-            this->taskConfig = { name, stackDepth, priority, coreId };
+        explicit Task(TaskConfig& config) : taskConfig(config) {
+            log_i("creating SensorUpdateTask: %s", taskConfig.name);
         };
-        Task(TaskConfig& config) : taskConfig(config){};
         virtual ~Task()
         {
             if (taskHandle) {
@@ -56,15 +54,16 @@ namespace SenseShift::FreeRTOS {
         virtual void begin()
         {
             BaseType_t result = xTaskCreateUniversal(
-              taskFunction,                // pvTaskCode
-              this->taskConfig.name,       // pcName
-              this->taskConfig.stackDepth, // usStackDepth
-              this,                        // pvParameters
-              this->taskConfig.priority,   // uxPriority
-              &taskHandle,                 // pvCreatedTask
-              this->taskConfig.coreId      // xCoreID
+              taskFunction,                //< pvTaskCode
+              this->taskConfig.name,       //< pcName
+              this->taskConfig.stackDepth, //< usStackDepth
+              this,                        //< pvParameters
+              this->taskConfig.priority,   //< uxPriority
+              &taskHandle,                 //< pvCreatedTask
+              this->taskConfig.coreId      //< xCoreID
             );
 
+            log_i("Created task %s, result %i", this->taskConfig.name, result);
             assert("Failed to create task" && result == pdPASS);
             if (!taskHandle) {
                 log_e("Failed to create task %s", this->taskConfig.name);
diff --git a/lib/util/senseshift/calibration.hpp b/lib/io/senseshift/input/calibration.hpp
similarity index 82%
rename from lib/util/senseshift/calibration.hpp
rename to lib/io/senseshift/input/calibration.hpp
index e1602582..bb8a0113 100644
--- a/lib/util/senseshift/calibration.hpp
+++ b/lib/io/senseshift/input/calibration.hpp
@@ -5,35 +5,31 @@
 
 #pragma once
 
-#include <senseshift/utility.hpp>
+#include "senseshift/utility.hpp"
 
-namespace SenseShift::Calibration {
+namespace SenseShift::Input {
     struct ICalibrated {
-        virtual void resetCalibration() = 0;
-        virtual void enableCalibration() = 0;
-        virtual void disableCalibration() = 0;
-    };
-
-    class Calibrated : public virtual ICalibrated {
-        bool calibrate_ = false;
-
-      public:
-        void resetCalibration() override = 0;
-        void enableCalibration() override { calibrate_ = true; }
-        void disableCalibration() override { calibrate_ = false; }
+        virtual void startCalibration() = 0;
+        virtual void stopCalibration() = 0;
+        virtual void reselCalibration() = 0;
     };
 
     template<typename Tp>
     struct ICalibrator {
-        static_assert(std::is_arithmetic_v<Tp>, "ICalibrator only can be used with arithmetic types");
-
+        /// Reset the calibration.
         virtual void reset() = 0;
+
+        /// Update the calibration with a new input value.
         virtual void update(Tp input) = 0;
-        virtual auto calibrate(Tp input) const -> Tp = 0;
+
+        /// Calibrate the input value.
+        [[nodiscard]] virtual auto calibrate(Tp input) const -> Tp = 0;
     };
 
     template<typename Tp, Tp output_min, Tp output_max>
     class MinMaxCalibrator : public ICalibrator<Tp> {
+        static_assert(std::is_arithmetic_v<Tp>, "MinMaxCalibrator only can be used with arithmetic types");
+
       public:
         using ValueType = Tp;
 
@@ -83,6 +79,8 @@ namespace SenseShift::Calibration {
 
     template<typename Tp, Tp sensor_max, Tp driver_max_deviation, Tp output_min, Tp output_max>
     class CenterPointDeviationCalibrator : public ICalibrator<Tp> {
+        static_assert(std::is_arithmetic_v<Tp>, "CenterPointDeviationCalibrator only can be used with arithmetic types");
+
       public:
         using ValueType = Tp;
 
@@ -104,7 +102,7 @@ namespace SenseShift::Calibration {
         }
 
         auto calibrate(ValueType input) const -> ValueType override {
-            // Find the center point of the sensor so we know how much we have deviated from it.
+            // Find the center point of the sensor, so we know how much we have deviated from it.
             Tp center = (range_min + range_max) / 2.0F;
 
             // Map the input to the sensor range of motion.
@@ -130,6 +128,8 @@ namespace SenseShift::Calibration {
 
     template<typename Tp, Tp sensor_max, Tp driver_max_deviation, Tp output_min, Tp output_max>
     class FixedCenterPointDeviationCalibrator : public ICalibrator<Tp> {
+        static_assert(std::is_arithmetic_v<Tp>, "FixedCenterPointDeviationCalibrator only can be used with arithmetic types");
+
       public:
         using ValueType = Tp;
 
@@ -137,7 +137,7 @@ namespace SenseShift::Calibration {
         void update(ValueType input) override {}
 
         auto calibrate(ValueType input) const -> ValueType override {
-            // Find the center point of the sensor so we know how much we have deviated from it.
+            // Find the center point of the sensor, so we know how much we have deviated from it.
             Tp center = sensor_max / 2.0F;
 
             // Map the input to the sensor range of motion.
@@ -156,4 +156,4 @@ namespace SenseShift::Calibration {
             );
         }
     };
-} // namespace SenseShift::Calibration
+} // namespace SenseShift::Input
diff --git a/lib/io/senseshift/input/filter.hpp b/lib/io/senseshift/input/filter.hpp
new file mode 100644
index 00000000..81b5b5d3
--- /dev/null
+++ b/lib/io/senseshift/input/filter.hpp
@@ -0,0 +1,284 @@
+#pragma once
+
+#include <algorithm>
+#include <array>
+#include <cstdint>
+#include <cstdlib>
+#include <deque>
+#include <functional>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include <senseshift/core/helpers.hpp>
+
+namespace SenseShift::Input {
+    template<typename Tp>
+    class ISimpleSensor;
+
+    template<typename Tp>
+    class Sensor;
+
+    template<typename Tp>
+    class IFilter {
+      public:
+        using ValueType = Tp;
+
+        virtual auto filter(ISimpleSensor<ValueType>* sensor, ValueType value) -> ValueType = 0;
+    };
+
+    template<typename Tp>
+    class AddFilter : public IFilter<Tp> {
+      public:
+        explicit AddFilter(Tp offset) : offset_(offset){};
+
+        auto filter(ISimpleSensor<Tp>* /*sensor*/, Tp value) -> Tp override
+        {
+            return value + this->offset_;
+        }
+
+      private:
+        Tp offset_;
+    };
+
+    template<typename Tp>
+    class IFiltered {
+      public:
+        using ValueType = Tp;
+
+        virtual void addFilter(IFilter<ValueType>* filter) = 0;
+
+        virtual void addFilters(std::vector<IFilter<ValueType>*> filters) = 0;
+
+        virtual void setFilters(std::vector<IFilter<ValueType>*> filters) = 0;
+
+        void clearFilters() = 0;
+    };
+
+    template<typename Tp>
+    class SubtractFilter : public IFilter<Tp> {
+      public:
+        explicit SubtractFilter(Tp offset) : offset_(offset){};
+
+        auto filter(ISimpleSensor<Tp>* /*sensor*/, Tp value) -> Tp override
+        {
+            return value - this->offset_;
+        }
+
+      private:
+        Tp offset_;
+    };
+
+    template<typename Tp>
+    class MultiplyFilter : public IFilter<Tp> {
+      public:
+        explicit MultiplyFilter(Tp factor) : factor_(factor){};
+
+        auto filter(ISimpleSensor<Tp>* /*sensor*/, Tp value) -> Tp override
+        {
+            return value * this->factor_;
+        }
+
+    private:
+        Tp factor_;
+    };
+
+    class VoltageDividerFilter : public MultiplyFilter<float> {
+      public:
+        /// \param r1 The resistance in Ohms of the first resistor in the voltage divider.
+        /// Example: 27000.0F.
+        /// \param r2 The resistance in Ohms of the second resistor in the voltage divider.
+        /// Example: 100000.0F.
+        ///
+        /// \example
+        /// \code
+        /// new VoltageDividerFilter(27000.0F, 100000.0F);
+        /// \endcode
+        explicit VoltageDividerFilter(float r1, float r2) : MultiplyFilter<float>(r2 / (r1 + r2)){};
+    };
+
+    template<typename Tp>
+    class ClampFilter : public IFilter<Tp> {
+      public:
+        ClampFilter(Tp min, Tp max) : min_(min), max_(max){};
+
+        auto filter(ISimpleSensor<Tp>* /*source*/, Tp value) -> Tp override
+        {
+            return std::clamp(value, this->min_, this->max_);
+        }
+
+      private:
+        Tp min_;
+        Tp max_;
+    };
+
+    /// An alias for ClampFilter.
+    template<typename Tp>
+    using MinMaxFilter = ClampFilter<Tp>;
+
+    /// An alias for ClampFilter.
+    template<typename Tp>
+    using RangeFilter = ClampFilter<Tp>;
+
+    template<typename Tp>
+    class LambdaFilter : public IFilter<Tp> {
+      public:
+        using Lambda = std::function<Tp(Tp)>;
+
+        explicit LambdaFilter(Lambda filter) : filter_(std::move(filter)) {};
+
+        auto filter(ISimpleSensor<Tp>* /*sensor*/, Tp value) -> Tp override
+        {
+            return this->filter_(value);
+        }
+
+      private:
+        Lambda filter_;
+    };
+
+    /// Average filter. Reads the value from the sensor and returns the average of the N values.
+    template<typename Tp, typename Sensor>
+    class SampleAverageFilter : public IFilter<Tp> {
+        static_assert(std::is_arithmetic_v<Tp>, "SampleAverageFilter only supports arithmetic types");
+        static_assert(std::is_same_v<typename Sensor::ValueType, Tp>, "Sensor type must match filter type");
+        static_assert(std::is_same_v<decltype(&Sensor::readRawValue), void (Sensor::*)()>, "Can only use sensors with readRawValue()");
+
+      public:
+        explicit SampleAverageFilter(std::size_t size) : size_(size){};
+
+        auto filter(ISimpleSensor<Tp>* sensor, Tp value) -> Tp override
+        {
+            auto sum = value;
+
+            // Read the acc_ from the sensor N-1 times and sum them up.
+            // We read it N-1 times because we already have the first acc_.
+            for (std::size_t i = 0; i < this->size_ - 1; i++) {
+                sum += sensor->readRawValue();
+            }
+
+            // Return the average of the values.
+            return sum / this->size_;
+        }
+
+      private:
+        std::size_t size_;
+    };
+
+    template<std::size_t N, typename Tp, typename Sensor>
+    class SampleMedianFilter : public IFilter<Tp> {
+        static_assert(N % 2 == 1, "SampleMedianFilter only supports odd sample sizes");
+        static_assert(std::is_same_v<typename Sensor::ValueType, Tp>, "Sensor type must match filter type");
+        static_assert(std::is_same_v<decltype(&Sensor::readRawValue), void (Sensor::*)()>, "Can only use sensors with readRawValue()");
+
+      public:
+        explicit SampleMedianFilter() = default;
+
+        auto filter(ISimpleSensor<Tp>* sensor, Tp value) -> Tp override
+        {
+            this->values = { value };
+
+            // Read the acc_ from the sensor N-1 times and put them in the array.
+            // We read it N-1 times because we already have the first acc_.
+            for (std::size_t i = 1; i <= this->size_ - 1; i++) {
+                this->values[i] = sensor->readRawValue();
+            }
+
+            // Sort the array.
+            std::sort(this->values.begin(), this->values.end());
+
+            // Return the median of the values.
+            return this->values[this->size_ / 2];
+        }
+
+      private:
+        std::array<Tp, N> values_;
+    };
+
+    template<typename Tp>
+    class SlidingWindowMovingAverageFilter : public IFilter<Tp> {
+        static_assert(std::is_arithmetic_v<Tp>, "SlidingWindowAverageFilter only supports arithmetic types");
+
+      public:
+        explicit SlidingWindowMovingAverageFilter(size_t window_size) : window_size_(window_size) { };
+
+        auto filter(ISimpleSensor<Tp>* /*sensor*/, Tp value) -> Tp override
+        {
+            while (this->queue_.size() >= this->window_size_) {
+                this->queue_.pop_front();
+            }
+            this->queue_.push_back(value);
+
+            return this->getAverage();
+        }
+
+      private:
+        std::size_t window_size_;
+        std::deque<Tp> values_;
+
+        [[nodiscard]] auto getAverage() const -> Tp
+        {
+            Tp sum = 0;
+            for (auto value : this->queue_) {
+                sum += value;
+            }
+            return sum / this->queue_.size();
+        }
+    };
+
+    template<typename Tp>
+    class ExponentialMovingAverageFilter : public IFilter<Tp> {
+        static_assert(std::is_arithmetic_v<Tp>, "ExponentialMovingAverageFilter only supports arithmetic types");
+
+      public:
+        explicit ExponentialMovingAverageFilter(float alpha) : alpha_(alpha) { };
+
+        auto filter(ISimpleSensor<Tp>* /*sensor*/, Tp value) -> Tp override
+        {
+            this->acc_ = this->alpha_ * value + (1 - this->alpha_) * this->acc_;
+
+            return this->acc_;
+        }
+
+      private:
+        float alpha_;
+        Tp acc_;
+    };
+
+    /// Deadzone filter. Clamps acc_ to center if it is within the deadzone.
+    /// Usually used to filter out noise in the joystick.
+    class CenterDeadzoneFilter : public IFilter<float>
+    {
+      public:
+        explicit CenterDeadzoneFilter(float deadzone) : deadzone_(deadzone){};
+
+        auto filter(ISimpleSensor<float>* /*sensor*/, float value) -> float override
+        {
+            float const deviation = std::abs(CENTER - value);
+            return deviation < deadzone_ ? CENTER : value;
+        }
+
+    private:
+        static constexpr float CENTER = 0.5F;
+
+        float deadzone_;
+    };
+
+    /// \tparam Container Type of the lookup table container.
+    template <typename Tp, typename Container>
+    class LookupTableInterpolationFilter : public IFilter<Tp>
+    {
+        static_assert(std::is_same_v<typename Container::value_type, Tp>);
+        static_assert(std::is_arithmetic_v<Tp>, "LookupTableInterpolationFilter only supports arithmetic types");
+
+      public:
+        explicit LookupTableInterpolationFilter(Container const& lookup_table) : lookup_table_(lookup_table) {};
+
+        auto filter(ISimpleSensor<float>* /*sensor*/, Tp value) -> Tp override
+        {
+            return SenseShift::lookup_interpolate<Tp, Container>(this->lookup_table_, value);
+        }
+
+      private:
+        Container const& lookup_table_;
+    };
+} // namespace SenseShift::Input
\ No newline at end of file
diff --git a/lib/io/senseshift/input/sensor.hpp b/lib/io/senseshift/input/sensor.hpp
index a8302172..c3fb8d02 100644
--- a/lib/io/senseshift/input/sensor.hpp
+++ b/lib/io/senseshift/input/sensor.hpp
@@ -1,20 +1,14 @@
 #pragma once
 
 #include <type_traits>
+#include <vector>
+#include <optional>
 
-#include <senseshift/calibration.hpp>
-#include <senseshift/core/component.hpp>
+#include "senseshift/input/filter.hpp"
+#include "senseshift/input/calibration.hpp"
 
-#if defined(__AVR__)
-#define ANALOG_MAX 1023
-#elif defined(ESP32)
-#define ANALOG_MAX 4095
-#elif !defined(ANALOG_MAX)
-#warning "This board doesn't have an auto ANALOG_MAX assignment, please set it manually"
-#define ANALOG_MAX static_assert(false, "ANALOG_MAX is not defined")
-// Uncomment and set as needed (only touch if you know what you are doing)
-// #define ANALOG_MAX 4095
-#endif
+#include <senseshift/core/component.hpp>
+#include <senseshift/core/helpers.hpp>
 
 namespace SenseShift::Input {
     /// Abstract hardware sensor (e.g. potentiometer, flex sensor, etc.)
@@ -24,148 +18,219 @@ namespace SenseShift::Input {
       public:
         using ValueType = Tp;
 
-        /// Get the current sensor value
+        explicit ISimpleSensor() = default;
+
+        /// Get the current sensor value.
         [[nodiscard]] virtual auto getValue() -> ValueType = 0;
     };
 
-    using IBinarySensor = ISimpleSensor<bool>;
-    using IFloatSensor = ISimpleSensor<float>;
+    using IBinarySimpleSensor = ISimpleSensor<bool>;
+    using IFloatSimpleSensor = ISimpleSensor<float>;
 
     template<typename Tp>
-    class ISensor : public virtual ISimpleSensor<Tp>, public ITickable {};
+    class ISensor : public virtual ISimpleSensor<Tp>, public ICalibrated {};
 
-    /// Memoized sensor decorator. Stores the last read value and returns it on subsequent calls
-    /// \tparam Tp Type of the sensor value
     template<typename Tp>
-    class MemoizedSensor : public ISensor<Tp> {
+    class Sensor : public ISensor<Tp> {
       public:
         using ValueType = Tp;
+        using CallbackManagerType = CallbackManager<void(ValueType)>;
+        using CallbackType = typename CallbackManagerType::CallbackType;
+
+        explicit Sensor() = default;
+
+        /// Appends a filter to the sensor's filter chain.
+        ///
+        /// \param filter The filter to add.
+        ///
+        /// \see addFilters for adding multiple filters.
+        void addFilter(IFilter<ValueType>* filter) { this->filters_.push_back(filter); }
+
+        /// Adds multiple filters to the sensor's filter chain. Appends to the end of the chain.
+        ///
+        /// \param filters The chain of filters to add.
+        ///
+        /// \example
+        /// \code
+        /// sensor->addFilters({
+        ///     new MinMaxFilter(0.1f, 0.9f),
+        ///     new CenterDeadzoneFilter(0.1f),
+        /// });
+        /// \endcode
+        void addFilters(std::vector<IFilter<ValueType>*> filters) {
+            this->filters_.insert(this->filters_.end(), filters.begin(), filters.end());
+        }
 
-        /// \param sensor Sensor to be decorated
-        explicit MemoizedSensor(ISimpleSensor<ValueType>* sensor) : sensor_(sensor){}
+        /// Replaces the sensor's filter chain with the given filters.
+        ///
+        /// \param filters New filter chain.
+        ///
+        /// \example
+        /// \code
+        /// sensor->setFilters({
+        ///     new MinMaxFilter(0.1f, 0.9f),
+        ///     new CenterDeadzoneFilter(0.1f),
+        /// });
+        /// \endcode
+        void setFilters(std::vector<IFilter<ValueType>*> filters) { this->filters_ = filters; }
 
-        /**
-         * Setup the sensor hardware
-         */
-        void init() override { this->sensor_->init(); }
+        /// Removes everything from the sensor's filter chain.
+        void clearFilters() { this->filters_.clear(); }
 
-        /**
-         * Read actual value from the hardware and memoize it
-         */
-        void tick() override { this->value_ = this->sensor_->getValue(); }
+        void setCalibrator(ICalibrator<ValueType>* calibrator) { this->calibrator_ = calibrator; }
 
-        /**
-         * Get the current memoized value
-         */
-        [[nodiscard]] auto getValue() -> ValueType override { return this->value_; }
+        void clearCalibrator() { this->calibrator_ = std::nullopt; }
 
-    private:
-        ISimpleSensor<ValueType>* sensor_;
-        ValueType value_;
-    };
-
-    template<typename Tp>
-    class ICalibratedSimpleSensor : public ISimpleSensor<Tp>, public Calibration::ICalibrated {};
+        void startCalibration() override { this->is_calibrating_ = true; }
 
-    /// Calibrated sensor decorator
-    /// \tparam Tp Type of the sensor value
-    template<typename Tp>
-    class CalibratedSimpleSensor : public ICalibratedSimpleSensor<Tp> {
-      public:
-        using ValueType = Tp;
+        void stopCalibration() override { this->is_calibrating_ = false; }
 
-        /// \param sensor Sensor to be decorated
-        /// \param calibrator ICalibrator algorithm to be used
-        CalibratedSimpleSensor(ISimpleSensor<ValueType>* sensor, Calibration::ICalibrator<ValueType>* calibrator) :
-          sensor_(sensor), calibrator_(calibrator){};
+        void reselCalibration() override {
+            if (this->calibrator_.has_value()) {
+                this->calibrator_.value()->reset();
+            }
+        }
 
-        void init() override { this->sensor_->init(); };
-        [[nodiscard]] auto getValue() -> ValueType override { return this->getCalibratedValue(); };
+        void addValueCallback(CallbackType &&callback) { this->callback_.add(std::move(callback)); }
 
-        void resetCalibration() override { this->calibrator_->reset(); };
-        void enableCalibration() override { is_calibrating_ = true; }
-        void disableCalibration() override { is_calibrating_ = false; }
+        void addRawValueCallback(CallbackType &&callback) { this->raw_callback_.add(std::move(callback)); }
 
-      protected:
-        [[nodiscard]] auto getCalibratedValue() -> ValueType
-        {
-            auto value = this->sensor_->getValue();
+        void init() override { }
 
-            if (this->is_calibrating_) {
-                this->calibrator_->update(value);
-            }
+        /// Publish the given state to the sensor.
+        ///
+        /// Firstly, the given state will be assigned to the sensor's raw_value_.
+        /// Then, the raw_value_ will be passed through the sensor's filter chain.
+        /// Finally, the filtered value will be assigned to the sensor's .value_.
+        ///
+        /// \param rawValue The new .raw_value_.
+        void publishState(ValueType rawValue) {
+            this->raw_value_ = rawValue;
+            this->raw_callback_.call(this->raw_value_);
 
-            return this->calibrator_->calibrate(value);
+            this->value_ = this->applyFilters(rawValue);
+            this->callback_.call(this->value_);
         }
 
-    private:
-        ISimpleSensor<ValueType>* sensor_;
-        Calibration::ICalibrator<ValueType>* calibrator_;
-        bool is_calibrating_ = false;
-    };
-
-    /// A sensor that returns the average value of N samples.
-    /// \tparam Tp Type of the sensor value
-    template<typename Tp>
-    class AverageSensor : public ISimpleSensor<Tp> {
-        static_assert(std::is_arithmetic_v<Tp>, "AverageSensor only supports arithmetic types");
+        /// Get the current sensor .value_.
+        [[nodiscard]] auto getValue() -> ValueType override {
+            return this->value_;
+        }
 
-      public:
-        using ValueType = Tp;
+        /// Get the current raw sensor .raw_value_.
+        [[nodiscard]] auto getRawValue() -> ValueType {
+            return this->raw_value_;
+        }
 
-        /// \param sensor Sensor to be decorated
-        /// \param samples Number of samples to be used
-        AverageSensor(ISimpleSensor<ValueType>* sensor, const size_t samples) : sensor_(sensor), samples_(samples) {}
+      protected:
+        /// Apply current filters to value.
+        [[nodiscard]] auto applyFilters(ValueType value) -> ValueType {
+            /// Apply filters
+            for (auto filter : this->filters_) {
+                value = filter->filter(this, value);
+            }
 
-        void init() override { this->sensor_->init(); };
+            /// Apply calibration
+            if (this->calibrator_.has_value()) {
+                if (this->is_calibrating_) {
+                    this->calibrator_.value()->update(value);
+                }
 
-        [[nodiscard]] auto getValue() const -> ValueType override
-        {
-            // TODO: another type for sum?
-            double sum = 0;
-            for (size_t i = 0; i < this->samples_; i++) {
-                sum += this->sensor_->getValue();
+                value = this->calibrator_.value()->calibrate(value);
             }
 
-            return sum / this->samples_;
+            return value;
         }
 
       private:
-        ISimpleSensor<ValueType>* sensor_;
-        size_t samples_;
+        friend class IFilter<ValueType>;
+        friend class ICalibrator<ValueType>;
+
+        /// The sensor's filter chain.
+        std::vector<IFilter<ValueType>*> filters_ = std::vector<IFilter<ValueType>*>();
+
+        bool is_calibrating_ = false;
+        std::optional<ICalibrator<ValueType>*> calibrator_ = std::nullopt;
+
+        ValueType raw_value_;
+        ValueType value_;
+
+        /// Storage for raw state callbacks.
+        CallbackManagerType raw_callback_;
+        /// Storage for filtered state callbacks.
+        CallbackManagerType callback_;
     };
 
-    /// A sensor that returns the median value of N samples.
-    /// \tparam Tp Type of the sensor value
-    /// \tparam NumSamples Number of samples to be used
-    template<typename Tp, size_t NumSamples>
-    class StaticMedianSensor : public ISimpleSensor<Tp> {
-        static_assert(std::is_arithmetic_v<Tp>, "StaticMedianSensor only supports arithmetic types");
-        static_assert(NumSamples % 2 == 1, "StaticMedianSensor only supports odd sample sizes");
+    using FloatSensor = Sensor<float>;
+    using BinarySensor = Sensor<bool>;
 
+    template<typename Tp>
+    class SimpleSensorDecorator : public Sensor<Tp>, public ITickable
+    {
       public:
         using ValueType = Tp;
+        using SourceType = ISimpleSensor<ValueType>;
 
-        explicit StaticMedianSensor(ISimpleSensor<ValueType>* sensor) : sensor_(sensor) {}
+        explicit SimpleSensorDecorator(SourceType* source) : source_(source) {}
 
-        void init() override { this->sensor_->init(); };
+        void init() override {
+            this->source_->init();
+        }
 
-        [[nodiscard]] auto getValue() -> ValueType override
-        {
-            for (size_t i = 0; i < NumSamples; i++) {
-                this->values_[i] = this->sensor_->getValue();
-            }
+        void tick() override {
+            this->updateValue();
+        }
 
-            std::sort(this->values_.begin(), this->values_.end());
+        auto updateValue() -> ValueType {
+            auto const raw_value = this->readRawValue();
+            this->publishState(raw_value);
 
-            return this->values_[NumSamples / 2];
+            LOG_D("decorator.simple", " raw_value=%f, value=%f", raw_value, this->getValue());
+
+            return this->getValue();
         }
 
-      private:
-        /// Buffer to store the last N samples.
-        /// We are using a static array to avoid dynamic allocation
-        std::array<ValueType, NumSamples> values_;
+        [[nodiscard]] auto readRawValue() -> ValueType {
+            return this->source_->getValue();
+        }
 
-        ISimpleSensor<Tp>* sensor_;
+      private:
+        SourceType* source_;
     };
+
+//    template<typename Tp>
+//    class SensorDecorator : public Sensor<Tp>
+//    {
+//      public:
+//        using ValueType = Tp;
+//        using SourceType = Sensor<ValueType>;
+//
+//        explicit SensorDecorator(SourceType* source) : source_(source) {}
+//
+//        void init() override {
+//            this->source_->init();
+//            this->source_->addValueCallback([this](ValueType value) {
+//                this->publishState(value);
+//            });
+//        }
+//
+//        void startCalibration() override {
+//            this->source_->startCalibration();
+//        }
+//
+//        void stopCalibration() override {
+//            this->source_->stopCalibration();
+//        }
+//
+//        void reselCalibration() override {
+//            this->source_->reselCalibration();
+//        }
+//
+//      private:
+//        SourceType* source_;
+//    };
+
+    namespace _private {
+        class TheFloatSensor : public Sensor<float> { };
+    }
 } // namespace SenseShift::Input
diff --git a/lib/io/senseshift/input/sensor/joystick.hpp b/lib/io/senseshift/input/sensor/joystick.hpp
deleted file mode 100644
index 0dd95dfe..00000000
--- a/lib/io/senseshift/input/sensor/joystick.hpp
+++ /dev/null
@@ -1,38 +0,0 @@
-#pragma once
-
-#include "senseshift/input/sensor.hpp"
-
-namespace SenseShift::Input {
-    /**
-     * Joystick axis sensor decorator
-     */
-    template<typename Tp>
-    class JoystickAxisSensor : public ISimpleSensor<Tp> {
-    public:
-        using ValueType = Tp;
-
-      private:
-        ISimpleSensor<ValueType>* sensor_;
-        float dead_zone_;
-
-        /// This function clamps the input to the center of the range if
-        /// the value is within the threshold. This is to eliminate at-rest
-        /// noise of the joystick.
-        [[nodiscard]] auto filterDeadZone(const int value_in) const -> ValueType {
-            constexpr ValueType center = ANALOG_MAX / 2;
-            return abs(center - value_in) < dead_zone_ * ANALOG_MAX ? center : value_in;
-        }
-
-      public:
-        JoystickAxisSensor(ISimpleSensor<Tp>* sensor, const float dead_zone) : sensor_(sensor), dead_zone_(dead_zone){};
-
-        void init() override { this->sensor_->init(); };
-
-        [[nodiscard]] auto getValue() -> uint16_t override
-        {
-            auto value = this->sensor_->getValue();
-            value = this->filterDeadZone(value);
-            return value;
-        }
-    };
-}; // namespace SenseShift::Input
diff --git a/lib/opengloves/senseshift/opengloves/interface.hpp b/lib/opengloves/senseshift/opengloves/interface.hpp
index 5e94b1e9..ba7ba8e0 100644
--- a/lib/opengloves/senseshift/opengloves/interface.hpp
+++ b/lib/opengloves/senseshift/opengloves/interface.hpp
@@ -2,6 +2,7 @@
 
 #include <cstddef>
 #include <cstdint>
+#include <vector>
 
 #include <senseshift/buffer.hpp>
 
@@ -10,17 +11,26 @@
 namespace SenseShift::OpenGloves {
     struct ITransport {
         virtual void setup(){};
-        virtual size_t send(const char* buffer, size_t length) = 0;
-        virtual bool hasData() = 0;
-        virtual size_t read(char* buffer, size_t length) = 0;
+        virtual auto send(const char* buffer, size_t length) -> size_t = 0;
+        virtual auto hasData() -> bool = 0;
+        virtual auto read(char* buffer, size_t length) -> size_t = 0;
     };
 
     struct IEncoding {
-        virtual size_t
-          serialize(const std::vector<::OpenGloves::IStringEncodedMemoizedSensor*>& sensors, char* buffer) const = 0;
+        /// \param [in] sensors
+        /// \param [out] buffer
+        virtual auto serialize(
+          const std::vector<::OpenGloves::IStringEncodedMemoizedSensor*>& sensors,
+          char* buffer
+        ) const -> size_t = 0;
 
-        virtual bool deserialize(
-          const char* buffer, const size_t length, std::map<::OpenGloves::Command, uint16_t>& commands
-        ) const = 0;
+        /// \param [in] buffer
+        /// \param [in] length
+        /// \param [out] commands
+        virtual auto deserialize(
+          const char* buffer,
+          size_t length,
+          std::map<::OpenGloves::Command, uint16_t>& commands
+        ) const -> bool = 0;
     };
 } // namespace SenseShift::OpenGloves
diff --git a/lib/opengloves/sensor/og_sensor.hpp b/lib/opengloves/sensor/og_sensor.hpp
index 6b95f0c1..e43dba74 100644
--- a/lib/opengloves/sensor/og_sensor.hpp
+++ b/lib/opengloves/sensor/og_sensor.hpp
@@ -1,25 +1,26 @@
 #pragma once
 
-#include <og_protocol.hpp>
-#include <senseshift/input/sensor.hpp>
-
+#include <cstdint>
 #include <optional>
 #include <vector>
 
+#include <og_protocol.hpp>
+#include <senseshift/input/sensor.hpp>
+
 namespace OpenGloves {
     struct FingerValue {
-        std::vector<uint16_t> curl = std::vector<uint16_t>({ 0 });
-        std::optional<uint16_t> splay = std::nullopt;
+        std::vector<std::uint16_t> curl = std::vector<std::uint16_t>({ 0 });
+        std::optional<std::uint16_t> splay = std::nullopt;
 
-        uint16_t getTotalCurl() const
+        [[nodiscard]] auto getTotalCurl() const -> std::uint16_t
         {
-            if (this->curl.size() == 0) {
+            if (this->curl.empty()) {
                 return 0;
             }
 
-            uint16_t total = 0;
-            for (auto curl : this->curl) {
-                total += curl;
+            std::uint16_t total = 0;
+            for (auto c_curl : this->curl) {
+                total += c_curl;
             }
             return total / this->curl.size();
         }
@@ -35,69 +36,10 @@ namespace OpenGloves {
 
         void init() override { this->getSensor()->init(); }
 
-        void updateValue() override { this->value = this->sensor_->getValue(); }
+        void updateValue() override { this->value_ = this->sensor_->getValue(); }
 
         [[nodiscard]] size_t getEncodedLength() const override;
 
         size_t encodeString(char* buffer) override;
     };
-
-    template<>
-    inline size_t StringEncodedMemoizedSensor<uint16_t>::getEncodedLength() const
-    {
-        return 6;
-    }
-
-    /// Format as "Axxxx", where A is the type and xxxxx is the value without leading zeros.
-    template<>
-    inline size_t StringEncodedMemoizedSensor<uint16_t>::encodeString(char* buffer)
-    {
-        return snprintf(buffer, this->getEncodedLength(), "%c%d", this->getType(), this->getValue());
-    }
-
-    template<>
-    size_t StringEncodedMemoizedSensor<bool>::getEncodedLength() const
-    {
-        return 1;
-    }
-
-    template<>
-    size_t StringEncodedMemoizedSensor<bool>::encodeString(char* buffer)
-    {
-        const auto value = this->getValue();
-
-        if (value) {
-            buffer[0] = this->getType();
-        }
-
-        return value ? this->getEncodedLength() : 0;
-    }
-
-    template<>
-    size_t StringEncodedMemoizedSensor<FingerValue>::getEncodedLength() const
-    {
-        // curl + splay + (3 * knuckle)
-        return 6 + 9 + (3 * 10);
-    }
-
-    template<>
-    size_t StringEncodedMemoizedSensor<FingerValue>::encodeString(char* buffer)
-    {
-        size_t offset = 0;
-        offset += snprintf(buffer + offset, 6, "%c%d", this->type, this->getValue().getTotalCurl());
-
-        if (this->getValue().curl.size() > 1) {
-            for (size_t i = 0; i < this->getValue().curl.size(); i++) {
-                char knuckle = 'A' + i;
-                offset += snprintf(buffer + offset, 10, "(%cA%c)%d", this->type, knuckle, this->getValue().curl[i]);
-            }
-        }
-
-        if (this->getValue().splay.has_value()) {
-            offset += snprintf(buffer + offset, 9, "(%cB)%d", this->type, this->getValue().splay.value());
-        }
-
-        return offset;
-    }
-
 } // namespace OpenGloves
diff --git a/test/test_util_calibration/main.cpp b/test/test_util_calibration/main.cpp
index 1fa42a23..ddc9d2b8 100644
--- a/test/test_util_calibration/main.cpp
+++ b/test/test_util_calibration/main.cpp
@@ -1,4 +1,4 @@
-#include <senseshift/calibration.hpp>
+#include "senseshift/input/calibration.hpp"
 #include <unity.h>
 
 using namespace SenseShift::Calibration;

From fa661e7d918f8c17b632f4e0a752fc3871431c56 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Fri, 2 Feb 2024 00:12:15 +0400
Subject: [PATCH 52/82] refactor: improve overall code readability

---
 firmware/mode_configs/bhaptics/tactal.cpp     |   1 +
 firmware/mode_configs/bhaptics/tactglove.cpp  |   1 +
 firmware/mode_configs/bhaptics/tactosy2.cpp   |   1 +
 firmware/mode_configs/bhaptics/tactosyf.cpp   |   1 +
 firmware/mode_configs/bhaptics/tactosyh.cpp   |   1 +
 .../mode_configs/bhaptics/tactsuit_x16.cpp    |   1 +
 .../bhaptics/tactsuit_x16_pca9685.cpp         |   1 +
 .../mode_configs/bhaptics/tactsuit_x40.cpp    |   1 +
 firmware/mode_configs/bhaptics/tactvisor.cpp  |   1 +
 .../input/sensor/{binary.hpp => digital.hpp}  |   8 +-
 lib/core/senseshift/core/helpers.hpp          |   7 +-
 .../senseshift/freertos/input/sensor.hpp      |   1 +
 lib/freertos/senseshift/freertos/task.hpp     |  30 +++
 .../senseshift/body/hands/hands_interface.hpp |  25 ++
 .../senseshift/body/hands/input/gesture.hpp   | 105 ++++++++
 .../body/hands/input/total_curl.hpp           |  53 ++++
 lib/haptics/senseshift/body/haptics/body.cpp  |   6 +-
 lib/haptics/senseshift/body/haptics/body.hpp  |  10 +
 .../senseshift/body/haptics/interface.hpp     |   2 +-
 lib/io/senseshift/input/calibration.hpp       |  22 +-
 lib/io/senseshift/input/filter.hpp            |  36 ++-
 lib/io/senseshift/input/sensor.hpp            |  79 ++++--
 .../senseshift/opengloves/constants.hpp       |   7 +
 lib/util/senseshift/container.hpp             |  17 +-
 test/test_bhaptics_encoding/main.cpp          | 227 +++++++++---------
 test/test_haptics_body/main.cpp               |  62 ++---
 test/test_haptics_plane/main.cpp              | 106 ++++----
 test/test_io_sensor/main.cpp                  |  79 +++---
 test/test_opengloves_gesture/main.cpp         |   2 +-
 29 files changed, 587 insertions(+), 306 deletions(-)
 rename lib/arduino/senseshift/arduino/input/sensor/{binary.hpp => digital.hpp} (62%)
 create mode 100644 lib/hands/senseshift/body/hands/hands_interface.hpp
 create mode 100644 lib/hands_input/senseshift/body/hands/input/gesture.hpp
 create mode 100644 lib/hands_input/senseshift/body/hands/input/total_curl.hpp
 create mode 100644 lib/opengloves/senseshift/opengloves/constants.hpp

diff --git a/firmware/mode_configs/bhaptics/tactal.cpp b/firmware/mode_configs/bhaptics/tactal.cpp
index e81c311d..6be10fed 100644
--- a/firmware/mode_configs/bhaptics/tactal.cpp
+++ b/firmware/mode_configs/bhaptics/tactal.cpp
@@ -16,6 +16,7 @@
 
 using namespace SenseShift;
 using namespace SenseShift::Input;
+using namespace SenseShift::Input::Filter;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
diff --git a/firmware/mode_configs/bhaptics/tactglove.cpp b/firmware/mode_configs/bhaptics/tactglove.cpp
index 2cca8354..874cd07a 100644
--- a/firmware/mode_configs/bhaptics/tactglove.cpp
+++ b/firmware/mode_configs/bhaptics/tactglove.cpp
@@ -17,6 +17,7 @@
 
 using namespace SenseShift;
 using namespace SenseShift::Input;
+using namespace SenseShift::Input::Filter;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
diff --git a/firmware/mode_configs/bhaptics/tactosy2.cpp b/firmware/mode_configs/bhaptics/tactosy2.cpp
index fb20ce80..572e52ad 100644
--- a/firmware/mode_configs/bhaptics/tactosy2.cpp
+++ b/firmware/mode_configs/bhaptics/tactosy2.cpp
@@ -16,6 +16,7 @@
 
 using namespace SenseShift;
 using namespace SenseShift::Input;
+using namespace SenseShift::Input::Filter;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
diff --git a/firmware/mode_configs/bhaptics/tactosyf.cpp b/firmware/mode_configs/bhaptics/tactosyf.cpp
index cc623ce2..51b67d9f 100644
--- a/firmware/mode_configs/bhaptics/tactosyf.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyf.cpp
@@ -16,6 +16,7 @@
 
 using namespace SenseShift;
 using namespace SenseShift::Input;
+using namespace SenseShift::Input::Filter;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
diff --git a/firmware/mode_configs/bhaptics/tactosyh.cpp b/firmware/mode_configs/bhaptics/tactosyh.cpp
index 05e6ba9f..277dcb0d 100644
--- a/firmware/mode_configs/bhaptics/tactosyh.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyh.cpp
@@ -16,6 +16,7 @@
 
 using namespace SenseShift;
 using namespace SenseShift::Input;
+using namespace SenseShift::Input::Filter;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
index f828ec19..5ba9322c 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
@@ -16,6 +16,7 @@
 
 using namespace SenseShift;
 using namespace SenseShift::Input;
+using namespace SenseShift::Input::Filter;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
index b1066077..256222a8 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
@@ -16,6 +16,7 @@
 
 using namespace SenseShift;
 using namespace SenseShift::Input;
+using namespace SenseShift::Input::Filter;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
index 48ea44ee..0057140f 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
@@ -17,6 +17,7 @@
 
 using namespace SenseShift;
 using namespace SenseShift::Input;
+using namespace SenseShift::Input::Filter;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
diff --git a/firmware/mode_configs/bhaptics/tactvisor.cpp b/firmware/mode_configs/bhaptics/tactvisor.cpp
index d5c7565c..f47cf4f5 100644
--- a/firmware/mode_configs/bhaptics/tactvisor.cpp
+++ b/firmware/mode_configs/bhaptics/tactvisor.cpp
@@ -16,6 +16,7 @@
 
 using namespace SenseShift;
 using namespace SenseShift::Input;
+using namespace SenseShift::Input::Filter;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
diff --git a/lib/arduino/senseshift/arduino/input/sensor/binary.hpp b/lib/arduino/senseshift/arduino/input/sensor/digital.hpp
similarity index 62%
rename from lib/arduino/senseshift/arduino/input/sensor/binary.hpp
rename to lib/arduino/senseshift/arduino/input/sensor/digital.hpp
index 44783b98..9a3d101c 100644
--- a/lib/arduino/senseshift/arduino/input/sensor/binary.hpp
+++ b/lib/arduino/senseshift/arduino/input/sensor/digital.hpp
@@ -6,11 +6,11 @@
 
 namespace SenseShift::Arduino::Input {
     template<bool Invert = false>
-    class BinarySensor : public IBinarySensor {
+    class DigitalSimpleSensor : public ::SenseShift::Input::IBinarySimpleSensor {
         uint8_t pin_;
 
       public:
-        BinarySensor(const uint8_t pin) : pin_(pin) {}
+        DigitalSimpleSensor(const uint8_t pin) : pin_(pin) {}
 
         void init() override { pinMode(this->pin_, INPUT_PULLUP); };
 
@@ -18,12 +18,12 @@ namespace SenseShift::Arduino::Input {
     };
 
     template<>
-    [[nodiscard]] inline auto BinarySensor<false>::getValue() -> bool {
+    [[nodiscard]] inline auto DigitalSimpleSensor<false>::getValue() -> bool {
         return digitalRead(this->pin_) == LOW;
     }
 
     template<>
-    [[nodiscard]] inline auto BinarySensor<true>::getValue() -> bool {
+    [[nodiscard]] inline auto DigitalSimpleSensor<true>::getValue() -> bool {
         return digitalRead(this->pin_) == HIGH;
     }
 } // namespace SenseShift::Arduino::Input
diff --git a/lib/core/senseshift/core/helpers.hpp b/lib/core/senseshift/core/helpers.hpp
index 35cfe2a0..f15909e9 100644
--- a/lib/core/senseshift/core/helpers.hpp
+++ b/lib/core/senseshift/core/helpers.hpp
@@ -32,6 +32,11 @@ namespace SenseShift {
         static_assert(std::is_arithmetic_v<Tp>, "Type must be arithmetic");
         static_assert(std::is_arithmetic_v<Up>, "Type must be arithmetic");
 
+        if (max <= min) {
+            LOG_E("util.remap", "Invalid input range, min <= max");
+            return (min_out + max_out) / 2;
+        }
+
         return (value - min) * (max_out - min_out) / (max - min) + min_out;
     }
 
@@ -120,4 +125,4 @@ namespace SenseShift {
       private:
         std::vector<std::function<void(Ts...)>> callbacks_;
     };
-}  // namespace SenseShift
\ No newline at end of file
+}  // namespace SenseShift
diff --git a/lib/freertos/senseshift/freertos/input/sensor.hpp b/lib/freertos/senseshift/freertos/input/sensor.hpp
index 667a2581..fbd34918 100644
--- a/lib/freertos/senseshift/freertos/input/sensor.hpp
+++ b/lib/freertos/senseshift/freertos/input/sensor.hpp
@@ -29,6 +29,7 @@ namespace SenseShift::FreeRTOS::Input {
             while (true) {
                 this->sensor_->tick();
                 delay(this->updateDelay_);
+                // log_i("high watermark %d", uxTaskGetStackHighWaterMark(NULL));
             }
         }
 
diff --git a/lib/freertos/senseshift/freertos/task.hpp b/lib/freertos/senseshift/freertos/task.hpp
index 0cbb1a12..ea7c942a 100644
--- a/lib/freertos/senseshift/freertos/task.hpp
+++ b/lib/freertos/senseshift/freertos/task.hpp
@@ -70,6 +70,36 @@ namespace SenseShift::FreeRTOS {
             }
         };
 
+      protected:
+        inline void monitorTask()
+        {
+            log_i("----------------------------------------\nFree Heap: %d\n", xPortGetFreeHeapSize());
+            volatile UBaseType_t uxArraySize = uxTaskGetNumberOfTasks();
+            TaskStatus_t* pxTaskStatusArray = (TaskStatus_t*) pvPortMalloc(uxArraySize * sizeof(TaskStatus_t));
+            if (!pxTaskStatusArray) {
+                log_e("Failed to allocate memory for task status array!");
+                return;
+            }
+            uxArraySize = uxTaskGetSystemState(pxTaskStatusArray, uxArraySize, NULL);
+            for (UBaseType_t i = 0; i < uxArraySize; i++) {
+                log_i(
+                        "Task: %s\n\tStack High Watermark: %d\n\tState: %d\n",
+                        pxTaskStatusArray[i].pcTaskName,
+                        pxTaskStatusArray[i].usStackHighWaterMark,
+                        pxTaskStatusArray[i].eCurrentState
+                );
+
+                if (pxTaskStatusArray[i].usStackHighWaterMark < 20) {
+                    log_w(
+                            "Warning: Task %s has low stack space, only %dB awailable!",
+                            pxTaskStatusArray[i].pcTaskName,
+                            pxTaskStatusArray[i].usStackHighWaterMark * 4
+                    );
+                }
+            }
+            vPortFree(pxTaskStatusArray);
+        }
+
       private:
         const TaskConfig& taskConfig;
         TaskHandle_t taskHandle = nullptr;
diff --git a/lib/hands/senseshift/body/hands/hands_interface.hpp b/lib/hands/senseshift/body/hands/hands_interface.hpp
new file mode 100644
index 00000000..4aa48b44
--- /dev/null
+++ b/lib/hands/senseshift/body/hands/hands_interface.hpp
@@ -0,0 +1,25 @@
+#pragma once
+
+#include <cstdint>
+#include "senseshift/body/haptics/interface.hpp"
+
+namespace SenseShift::Body {
+    namespace Hands {
+        using HandLateralityIndex = std::uint8_t;
+        enum class HandSide : HandLateralityIndex { Left, Right };
+        using FingerIndex = std::uint8_t;
+        enum class Finger : FingerIndex {
+            Thumb,
+            Index,
+            Middle,
+            Ring,
+            Little,
+        };
+
+        namespace Haptics {
+            /// @brief Helper with position of the haptic device on the fingertip.
+            /// Distal phalanx of the volar surface of the any finger.
+            static constexpr const ::SenseShift::Body::Haptics::Position FINGERTIP_POSITION(127, 16);
+        } // namespace Haptics
+    }     // namespace Hands
+} // namespace SenseShift::Body
diff --git a/lib/hands_input/senseshift/body/hands/input/gesture.hpp b/lib/hands_input/senseshift/body/hands/input/gesture.hpp
new file mode 100644
index 00000000..92ca2a94
--- /dev/null
+++ b/lib/hands_input/senseshift/body/hands/input/gesture.hpp
@@ -0,0 +1,105 @@
+#pragma once
+
+#include <senseshift/core/component.hpp>
+#include <senseshift/input/sensor.hpp>
+
+namespace SenseShift::Body::Hands::Input {
+    using Gesture = ::SenseShift::Input::BinarySensor;
+
+    /// An alias for semantic consistency.
+    using TriggerGesture = ::SenseShift::Input::AnalogThresholdSensor;
+
+    class GrabGesture : public Gesture, ITickable {
+      public:
+        struct Fingers {
+            ::SenseShift::Input::FloatSensor* index;
+            ::SenseShift::Input::FloatSensor* middle;
+            ::SenseShift::Input::FloatSensor* ring;
+            ::SenseShift::Input::FloatSensor* pinky;
+        };
+
+        explicit GrabGesture(
+          Fingers fingers,
+          float threshold = 0.5F,
+          bool attach_callbacks = false
+        ) : fingers_(fingers), threshold_(threshold), attach_callbacks_(attach_callbacks) {}
+
+        void init() override {
+            SS_SUBSENSOR_INIT(this->fingers_.index, this->attach_callbacks_, [this](float /*value*/) {
+                this->recalculateState();
+            });
+            SS_SUBSENSOR_INIT(this->fingers_.middle, this->attach_callbacks_, [this](float /*value*/) {
+                this->recalculateState();
+            });
+            SS_SUBSENSOR_INIT(this->fingers_.ring, this->attach_callbacks_, [this](float /*value*/) {
+                this->recalculateState();
+            });
+            SS_SUBSENSOR_INIT(this->fingers_.pinky, this->attach_callbacks_, [this](float /*value*/) {
+                this->recalculateState();
+            });
+        }
+
+        void tick() override {
+            if (this->attach_callbacks_) {
+                LOG_E("gesture.grab", "tick() called when attach_callbacks_ is true, infinite loop go wroom-wroom!");
+            }
+            this->recalculateState();
+        }
+
+        void recalculateState() {
+            return this->publishState(
+              this->fingers_.index->getValue() > this->threshold_
+              && this->fingers_.middle->getValue() > this->threshold_
+              && this->fingers_.ring->getValue() > this->threshold_
+              && this->fingers_.pinky->getValue() > this->threshold_
+            );
+        }
+
+      private:
+        Fingers fingers_;
+        float threshold_;
+        bool attach_callbacks_ = false;
+    };
+
+    class PinchGesture : public Gesture, ITickable {
+      public:
+        struct Fingers {
+            ::SenseShift::Input::FloatSensor* thumb;
+            ::SenseShift::Input::FloatSensor* index;
+        };
+
+        explicit PinchGesture(
+          Fingers fingers,
+          float threshold = 0.5F,
+          bool attach_callbacks = false
+        ) : fingers_(fingers), threshold_(threshold), attach_callbacks_(attach_callbacks) {}
+
+        void init() override {
+            SS_SUBSENSOR_INIT(this->fingers_.thumb, this->attach_callbacks_, [this](float /*value*/) {
+                this->recalculateState();
+            });
+            SS_SUBSENSOR_INIT(this->fingers_.index, this->attach_callbacks_, [this](float /*value*/) {
+                this->recalculateState();
+            });
+        }
+
+        void tick() override {
+            if (this->attach_callbacks_) {
+                LOG_E("gesture.pinch", "tick() called when attach_callbacks_ is true, infinite loop go wroom-wroom!");
+            }
+            this->recalculateState();
+        }
+
+        void recalculateState() {
+            return this->publishState(
+              this->fingers_.thumb->getValue() > this->threshold_
+              && this->fingers_.index->getValue() > this->threshold_
+            );
+        }
+
+      private:
+        Fingers fingers_;
+        float threshold_;
+        bool attach_callbacks_ = false;
+    };
+} // namespace SenseShift::Body::Hands::Input
\ No newline at end of file
diff --git a/lib/hands_input/senseshift/body/hands/input/total_curl.hpp b/lib/hands_input/senseshift/body/hands/input/total_curl.hpp
new file mode 100644
index 00000000..678f5b80
--- /dev/null
+++ b/lib/hands_input/senseshift/body/hands/input/total_curl.hpp
@@ -0,0 +1,53 @@
+#pragma once
+
+#include <functional>
+#include <utility>
+#include <vector>
+
+#include <senseshift/core/component.hpp>
+#include <senseshift/input/sensor.hpp>
+
+namespace SenseShift::Body::Hands::Input {
+    class TotalCurl : public ::SenseShift::Input::FloatSensor, public ::SenseShift::ITickable {
+      public:
+        /// \param joints The joints to calculate the total curl from.
+        /// \param attach_callbacks Whether to attach callbacks to the joints to recalculate the total curl when they update.
+        ///                         If false, the total curl will only be recalculated when the tick() method is called.
+        ///                         Setting this to <b>true is not recommended</b>, as it will cause the total curl to be
+        ///                         recalculated multiple times per tick (the same as number of joints).
+        explicit TotalCurl(
+          std::vector<::SenseShift::Input::FloatSensor> joints,
+          bool attach_callbacks = false
+        ) : joints_(std::move(joints)), attach_callbacks_(attach_callbacks) {}
+
+        void init() override {
+            for (auto& joint : this->joints_) {
+                SS_SUBSENSOR_INIT(&joint, this->attach_callbacks_, [this](float /*value*/) { this->recalculateState(); });
+            }
+        }
+
+        void tick() override {
+            if (this->attach_callbacks_) {
+                LOG_E("total_curl", "tick() called when attach_callbacks_ is true, infinite loop go wroom-wroom!");
+            }
+            this->recalculateState();
+        }
+
+        void recalculateState() {
+            float total = 0.0F;
+
+            for (auto& joint : this->joints_) {
+                total += joint.getState();
+            }
+
+            if (!this->joints_.empty()) {
+                this->publishState(total / static_cast<float>(this->joints_.size()));
+            }
+        }
+
+      private:
+        std::vector<::SenseShift::Input::FloatSensor> joints_;
+
+        bool attach_callbacks_ = false;
+    };
+}  // namespace SenseShift::Body::Hands::Input
diff --git a/lib/haptics/senseshift/body/haptics/body.cpp b/lib/haptics/senseshift/body/haptics/body.cpp
index 5caa7beb..bb373d3e 100644
--- a/lib/haptics/senseshift/body/haptics/body.cpp
+++ b/lib/haptics/senseshift/body/haptics/body.cpp
@@ -10,13 +10,13 @@ namespace SenseShift::Body::Haptics {
     template<typename Tp, typename Ta>
     void OutputBody<Tp, Ta>::effect(const Target& target, const Position& pos, const typename Plane::Value& val)
     {
-        auto find = this->targets_.find(target);
-        if (find == this->targets_.end()) {
+        auto plane = this->getTarget(target);
+        if (!plane.has_value()) {
             LOG_W(TAG, "No target found for effect: %d", target);
             return;
         }
 
-        find->second->effect(pos, val);
+        plane.value()->effect(pos, val);
     }
 
     template class OutputBody<Position::Value, Output::IFloatOutput::ValueType>;
diff --git a/lib/haptics/senseshift/body/haptics/body.hpp b/lib/haptics/senseshift/body/haptics/body.hpp
index 6cdd2cbd..6586220a 100644
--- a/lib/haptics/senseshift/body/haptics/body.hpp
+++ b/lib/haptics/senseshift/body/haptics/body.hpp
@@ -4,6 +4,7 @@
 #include "senseshift/body/haptics/plane.hpp"
 
 #include <map>
+#include <optional>
 
 #include <senseshift/output/output.hpp>
 
@@ -32,6 +33,15 @@ namespace SenseShift::Body::Haptics {
             this->targets_[target] = plane;
         }
 
+        auto getTarget(Target target) -> std::optional<Plane*> {
+            auto find = this->targets_.find(target);
+            if (find == this->targets_.end()) {
+                return std::nullopt;
+            }
+
+            return find->second;
+        }
+
         void effect(const Target& target, const Position& pos, const typename Plane::Value& val);
 
         [[nodiscard]] auto getTargets() const -> const TargetPlaneMap* { return &targets_; }
diff --git a/lib/haptics/senseshift/body/haptics/interface.hpp b/lib/haptics/senseshift/body/haptics/interface.hpp
index 89f14cff..f0952fd4 100644
--- a/lib/haptics/senseshift/body/haptics/interface.hpp
+++ b/lib/haptics/senseshift/body/haptics/interface.hpp
@@ -22,7 +22,7 @@ namespace SenseShift::Body::Haptics {
         ChestFront = 0x00,
         ChestBack = 0x01,
 
-        // Legacy backword compatibility
+        // Legacy backward compatibility
         Accessory [[deprecated]] = 0x02,
 
         FaceFront,
diff --git a/lib/io/senseshift/input/calibration.hpp b/lib/io/senseshift/input/calibration.hpp
index bb8a0113..b9baa95a 100644
--- a/lib/io/senseshift/input/calibration.hpp
+++ b/lib/io/senseshift/input/calibration.hpp
@@ -7,7 +7,7 @@
 
 #include "senseshift/utility.hpp"
 
-namespace SenseShift::Input {
+namespace SenseShift::Input::Calibration {
     struct ICalibrated {
         virtual void startCalibration() = 0;
         virtual void stopCalibration() = 0;
@@ -26,18 +26,18 @@ namespace SenseShift::Input {
         [[nodiscard]] virtual auto calibrate(Tp input) const -> Tp = 0;
     };
 
-    template<typename Tp, Tp output_min, Tp output_max>
+    template<typename Tp>
     class MinMaxCalibrator : public ICalibrator<Tp> {
         static_assert(std::is_arithmetic_v<Tp>, "MinMaxCalibrator only can be used with arithmetic types");
 
       public:
         using ValueType = Tp;
 
-        MinMaxCalibrator() : value_min_(output_max), value_max_(output_min) {}
+        MinMaxCalibrator(ValueType output_min, ValueType output_max) : output_min_(output_min), output_max_(output_max), value_min_(output_max), value_max_(output_min) {}
 
         void reset() override {
-            value_min_ = output_max;
-            value_max_ = output_min;
+            value_min_ = output_max_;
+            value_max_ = output_min_;
         }
 
         void update(ValueType input) override {
@@ -54,25 +54,27 @@ namespace SenseShift::Input {
             // This means we haven't had any calibration data yet.
             // Return a neutral value right in the middle of the output range.
             if (value_min_ > value_max_) {
-                return (output_min + output_max) / 2.0F;
+                return (output_min_ + output_max_) / 2.0F;
             }
 
             if (input <= value_min_) {
-                return output_min;
+                return output_min_;
             }
 
             if (input >= value_max_) {
-                return output_max;
+                return output_max_;
             }
 
             // Map the input range to the output range.
-            ValueType output = ::SenseShift::remap<ValueType, ValueType>(input, value_min_, value_max_, output_min, output_max);
+            ValueType output = ::SenseShift::remap<ValueType, ValueType>(input, value_min_, value_max_, output_min_, output_max_);
 
             // Lock the range to the output.
-            return std::clamp(output, output_min, output_max);
+            return std::clamp(output, output_min_, output_max_);
         }
 
       private:
+        const ValueType output_min_;
+        const ValueType output_max_;
         ValueType value_min_;
         ValueType value_max_;
     };
diff --git a/lib/io/senseshift/input/filter.hpp b/lib/io/senseshift/input/filter.hpp
index 81b5b5d3..a86ef341 100644
--- a/lib/io/senseshift/input/filter.hpp
+++ b/lib/io/senseshift/input/filter.hpp
@@ -11,10 +11,16 @@
 #include <vector>
 
 #include <senseshift/core/helpers.hpp>
+#include <senseshift/core/logging.hpp>
 
-namespace SenseShift::Input {
+namespace SenseShift::Input::Filter {
     template<typename Tp>
-    class ISimpleSensor;
+    class ISimpleSensor {
+      public:
+        using ValueType = Tp;
+
+        virtual auto getValue() -> ValueType = 0;
+    };
 
     template<typename Tp>
     class Sensor;
@@ -164,34 +170,42 @@ namespace SenseShift::Input {
         std::size_t size_;
     };
 
-    template<std::size_t N, typename Tp, typename Sensor>
+    template<typename Tp, typename Sensor>
     class SampleMedianFilter : public IFilter<Tp> {
-        static_assert(N % 2 == 1, "SampleMedianFilter only supports odd sample sizes");
         static_assert(std::is_same_v<typename Sensor::ValueType, Tp>, "Sensor type must match filter type");
-        static_assert(std::is_same_v<decltype(&Sensor::readRawValue), void (Sensor::*)()>, "Can only use sensors with readRawValue()");
+        // static_assert(std::is_same_v<decltype(&Sensor::readRawValue), Tp (Sensor::*)()>, "Can only use sensors with readRawValue()");
 
       public:
-        explicit SampleMedianFilter() = default;
+        explicit SampleMedianFilter(std::size_t size_) : size_(size_) {
+            // allocate the array
+            this->values = new Tp[size_];
+        };
 
         auto filter(ISimpleSensor<Tp>* sensor, Tp value) -> Tp override
         {
-            this->values = { value };
+            if (sensor == nullptr) {
+                LOG_E("filter.sampling_median", "Source sensor is null");
+                return value;
+            }
+
+            this->values[0] = value;
 
             // Read the acc_ from the sensor N-1 times and put them in the array.
             // We read it N-1 times because we already have the first acc_.
             for (std::size_t i = 1; i <= this->size_ - 1; i++) {
-                this->values[i] = sensor->readRawValue();
+                this->values[i] = sensor->getValue();
             }
 
             // Sort the array.
-            std::sort(this->values.begin(), this->values.end());
+            std::sort(this->values, this->values + this->size_);
 
             // Return the median of the values.
             return this->values[this->size_ / 2];
         }
 
       private:
-        std::array<Tp, N> values_;
+        std::size_t size_;
+        Tp* values;
     };
 
     template<typename Tp>
@@ -281,4 +295,4 @@ namespace SenseShift::Input {
       private:
         Container const& lookup_table_;
     };
-} // namespace SenseShift::Input
\ No newline at end of file
+} // namespace SenseShift::Input
diff --git a/lib/io/senseshift/input/sensor.hpp b/lib/io/senseshift/input/sensor.hpp
index c3fb8d02..86b22868 100644
--- a/lib/io/senseshift/input/sensor.hpp
+++ b/lib/io/senseshift/input/sensor.hpp
@@ -10,6 +10,12 @@
 #include <senseshift/core/component.hpp>
 #include <senseshift/core/helpers.hpp>
 
+#define SS_SUBSENSOR_INIT(SENSOR, ATTACH_CALLBACK, CALLBACK) \
+    (SENSOR)->init();                                        \
+    if (ATTACH_CALLBACK) {                                   \
+        (SENSOR)->addValueCallback(CALLBACK);                \
+    }
+
 namespace SenseShift::Input {
     /// Abstract hardware sensor (e.g. potentiometer, flex sensor, etc.)
     /// \tparam Tp Type of the sensor value
@@ -28,7 +34,7 @@ namespace SenseShift::Input {
     using IFloatSimpleSensor = ISimpleSensor<float>;
 
     template<typename Tp>
-    class ISensor : public virtual ISimpleSensor<Tp>, public ICalibrated {};
+    class ISensor : public virtual ISimpleSensor<Tp>, public Calibration::ICalibrated {};
 
     template<typename Tp>
     class Sensor : public ISensor<Tp> {
@@ -44,7 +50,7 @@ namespace SenseShift::Input {
         /// \param filter The filter to add.
         ///
         /// \see addFilters for adding multiple filters.
-        void addFilter(IFilter<ValueType>* filter) { this->filters_.push_back(filter); }
+        void addFilter(Filter::IFilter<ValueType>* filter) { this->filters_.push_back(filter); }
 
         /// Adds multiple filters to the sensor's filter chain. Appends to the end of the chain.
         ///
@@ -57,7 +63,7 @@ namespace SenseShift::Input {
         ///     new CenterDeadzoneFilter(0.1f),
         /// });
         /// \endcode
-        void addFilters(std::vector<IFilter<ValueType>*> filters) {
+        void addFilters(std::vector<Filter::IFilter<ValueType>*> filters) {
             this->filters_.insert(this->filters_.end(), filters.begin(), filters.end());
         }
 
@@ -72,12 +78,12 @@ namespace SenseShift::Input {
         ///     new CenterDeadzoneFilter(0.1f),
         /// });
         /// \endcode
-        void setFilters(std::vector<IFilter<ValueType>*> filters) { this->filters_ = filters; }
+        void setFilters(std::vector<Filter::IFilter<ValueType>*> filters) { this->filters_ = filters; }
 
         /// Removes everything from the sensor's filter chain.
         void clearFilters() { this->filters_.clear(); }
 
-        void setCalibrator(ICalibrator<ValueType>* calibrator) { this->calibrator_ = calibrator; }
+        void setCalibrator(Calibration::ICalibrator<ValueType>* calibrator) { this->calibrator_ = calibrator; }
 
         void clearCalibrator() { this->calibrator_ = std::nullopt; }
 
@@ -91,9 +97,9 @@ namespace SenseShift::Input {
             }
         }
 
-        void addValueCallback(CallbackType &&callback) { this->callback_.add(std::move(callback)); }
+        void addValueCallback(CallbackType &&callback) { this->callbacks_.add(std::move(callback)); }
 
-        void addRawValueCallback(CallbackType &&callback) { this->raw_callback_.add(std::move(callback)); }
+        void addRawValueCallback(CallbackType &&callback) { this->raw_callbacks_.add(std::move(callback)); }
 
         void init() override { }
 
@@ -106,10 +112,10 @@ namespace SenseShift::Input {
         /// \param rawValue The new .raw_value_.
         void publishState(ValueType rawValue) {
             this->raw_value_ = rawValue;
-            this->raw_callback_.call(this->raw_value_);
+            this->raw_callbacks_.call(this->raw_value_);
 
             this->value_ = this->applyFilters(rawValue);
-            this->callback_.call(this->value_);
+            this->callbacks_.call(this->value_);
         }
 
         /// Get the current sensor .value_.
@@ -125,11 +131,6 @@ namespace SenseShift::Input {
       protected:
         /// Apply current filters to value.
         [[nodiscard]] auto applyFilters(ValueType value) -> ValueType {
-            /// Apply filters
-            for (auto filter : this->filters_) {
-                value = filter->filter(this, value);
-            }
-
             /// Apply calibration
             if (this->calibrator_.has_value()) {
                 if (this->is_calibrating_) {
@@ -139,26 +140,31 @@ namespace SenseShift::Input {
                 value = this->calibrator_.value()->calibrate(value);
             }
 
+            /// Apply filters
+            for (auto filter : this->filters_) {
+                value = filter->filter(nullptr, value);
+            }
+
             return value;
         }
 
       private:
-        friend class IFilter<ValueType>;
-        friend class ICalibrator<ValueType>;
+        friend class Filter::IFilter<ValueType>;
+        friend class Calibration::ICalibrator<ValueType>;
 
         /// The sensor's filter chain.
-        std::vector<IFilter<ValueType>*> filters_ = std::vector<IFilter<ValueType>*>();
+        std::vector<Filter::IFilter<ValueType>*> filters_ = std::vector<Filter::IFilter<ValueType>*>();
 
         bool is_calibrating_ = false;
-        std::optional<ICalibrator<ValueType>*> calibrator_ = std::nullopt;
+        std::optional<Calibration::ICalibrator<ValueType>*> calibrator_ = std::nullopt;
 
         ValueType raw_value_;
         ValueType value_;
 
         /// Storage for raw state callbacks.
-        CallbackManagerType raw_callback_;
+        CallbackManagerType raw_callbacks_;
         /// Storage for filtered state callbacks.
-        CallbackManagerType callback_;
+        CallbackManagerType callbacks_;
     };
 
     using FloatSensor = Sensor<float>;
@@ -194,6 +200,8 @@ namespace SenseShift::Input {
             return this->source_->getValue();
         }
 
+      protected:
+
       private:
         SourceType* source_;
     };
@@ -230,6 +238,37 @@ namespace SenseShift::Input {
 //        SourceType* source_;
 //    };
 
+    class AnalogThresholdSensor : public BinarySensor, ITickable {
+      public:
+        explicit AnalogThresholdSensor(
+          ::SenseShift::Input::FloatSensor* index,
+          float threshold = 0.5F,
+          bool attach_callbacks = false
+        ) : index_(index), threshold_(threshold), attach_callbacks_(attach_callbacks) {}
+
+        void init() override {
+            SS_SUBSENSOR_INIT(this->index_, this->attach_callbacks_, [this](float /*value*/) {
+                this->recalculateState();
+            });
+        }
+
+        void tick() override {
+            if (this->attach_callbacks_) {
+                LOG_E("sensor.analog_threshold", "tick() called when attach_callbacks_ is true, infinite loop go wroom-wroom!");
+            }
+            this->recalculateState();
+        }
+
+        void recalculateState() {
+            return this->publishState(this->index_->getValue() > this->threshold_);
+        }
+
+      private:
+        ::SenseShift::Input::FloatSensor* index_;
+        float threshold_;
+        bool attach_callbacks_ = false;
+    };
+
     namespace _private {
         class TheFloatSensor : public Sensor<float> { };
     }
diff --git a/lib/opengloves/senseshift/opengloves/constants.hpp b/lib/opengloves/senseshift/opengloves/constants.hpp
new file mode 100644
index 00000000..df163940
--- /dev/null
+++ b/lib/opengloves/senseshift/opengloves/constants.hpp
@@ -0,0 +1,7 @@
+#pragma once
+
+#define OPENGLOVES_FINGERS_TASK_PRIORITY 1
+
+#define OPENGLOVES_COMM_SERIAL 0x1
+#define OPENGLOVES_COMM_BTSERIAL 0x2
+#define OPENGLOVES_COMM_BLESERIAL 0x3
diff --git a/lib/util/senseshift/container.hpp b/lib/util/senseshift/container.hpp
index f8da0365..613b962b 100644
--- a/lib/util/senseshift/container.hpp
+++ b/lib/util/senseshift/container.hpp
@@ -2,6 +2,7 @@
 
 #include <algorithm>
 #include <iterator>
+#include <string>
 #include <type_traits>
 
 namespace SenseShift {
@@ -11,29 +12,29 @@ namespace SenseShift {
      * @tparam _Cp The type of the container.
      * @tparam _Tp The type of the value.
      */
-    template<class _Cp, typename _Tp>
-    inline auto contains(const _Cp&& c, const _Tp val) -> bool
+    template<class Cp, typename Tp>
+    inline auto contains(Cp&& c, Tp val) -> bool
     {
         return std::find(std::begin(c), std::end(c), val) != std::end(c);
     };
 
     template<>
-    inline bool contains<std::string&, char>(std::string& s, char val)
+    inline auto contains<std::string&, char>(std::string& s, char val) -> bool
     {
         return s.find(val) != std::string::npos;
     };
 
-    template<typename _Tp>
-    inline bool contains(_Tp* begin, _Tp* end, const _Tp& val)
+    template<typename Tp>
+    inline auto contains(Tp* begin, Tp* end, const Tp& val) -> bool
     {
         return std::find(begin, end, val) != end;
     };
 
-    template<typename _Tp>
-    constexpr inline bool contains(const _Tp* arr, const std::size_t size, const _Tp& val)
+    template<typename Tp>
+    constexpr inline auto contains(const Tp* arr, const std::size_t size, const Tp& val) -> bool
     {
         static_assert(
-          std::is_same<_Tp, typename std::iterator_traits<_Tp*>::value_type>::value,
+          std::is_same_v<Tp, typename std::iterator_traits<Tp*>::value_type>,
           "Container and value must be of the same type"
         );
         return std::find(arr, arr + size, val) != arr + size;
diff --git a/test/test_bhaptics_encoding/main.cpp b/test/test_bhaptics_encoding/main.cpp
index 0ff45557..72b70776 100644
--- a/test/test_bhaptics_encoding/main.cpp
+++ b/test/test_bhaptics_encoding/main.cpp
@@ -6,25 +6,28 @@ using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 using namespace SenseShift::Output;
 
-class TestActuator : public IActuator<uint16_t> {
+class TestActuator : public IOutput<float> {
   public:
     bool isSetup = false;
-    uint16_t intensity = 0;
+    float intensity = 0;
 
-    TestActuator() : IActuator<uint16_t>() {}
-    void setup() override { this->isSetup = true; }
-    void writeOutput(uint16_t intensity) override { this->intensity = intensity; }
+    TestActuator() : IFloatOutput() {}
+    void init() override { this->isSetup = true; }
+    void writeState(float newIntensity) override { this->intensity = newIntensity; }
 };
 
+#define ASSERT_EQUAL_FLOAT_ROUNDED(expected, actual, precision)                 \
+    TEST_ASSERT_EQUAL_FLOAT(                                                    \
+      std::round(expected * std::pow(10, precision)) / std::pow(10, precision), \
+      std::round(actual * std::pow(10, precision)) / std::pow(10, precision)    \
+    )
+
 void test_layout_tactsuitx16(void)
 {
-    static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTSUITX16_SIZE;
-    static const OutputLayout bhLayout[bhLayoutSize] = BH_LAYOUT_TACTSUITX16;
-
-    static constexpr size_t layoutGroupsSize = BH_LAYOUT_TACTSUITX16_GROUPS_SIZE;
-    static const uint8_t layoutGroups[layoutGroupsSize] = BH_LAYOUT_TACTSUITX16_GROUPS;
+    static const std::array<OutputLayout, BH_LAYOUT_TACTSUITX16_SIZE> bhLayout = { BH_LAYOUT_TACTSUITX16 };
+    static const std::array<std::uint8_t,BH_LAYOUT_TACTSUITX16_GROUPS_SIZE> layoutGroups = BH_LAYOUT_TACTSUITX16_GROUPS;
 
-    auto body = new HapticBody();
+    auto body = new FloatBody();
 
     TestActuator* actuator0 = new TestActuator();
     TestActuator* actuator1 = new TestActuator();
@@ -43,62 +46,61 @@ void test_layout_tactsuitx16(void)
     TestActuator* actuator14 = new TestActuator();
     TestActuator* actuator15 = new TestActuator();
 
-    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
+    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<FloatPlane::Actuator>({
       { actuator0, actuator1, actuator2, actuator3 },
       { actuator4, actuator5, actuator6, actuator7 },
     });
-    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
+    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<FloatPlane::Actuator>({
       { actuator8, actuator9, actuator10, actuator11 },
       { actuator12, actuator13, actuator14, actuator15 },
     });
 
-    auto frontPlane = new VibroPlane(frontOutputs);
-    auto backPlane = new VibroPlane(backOutputs);
+    auto frontPlane = new FloatPlane(frontOutputs);
+    auto backPlane = new FloatPlane(backOutputs);
 
     body->addTarget(Target::ChestFront, frontPlane);
     body->addTarget(Target::ChestBack, backPlane);
     // body->setup();
 
-    const uint8_t values[] = {
+    const std::array<uint8_t, 20> values = {
         0x01, 0x00, 0x23, 0x00, 0x00, 0x45, 0x00, 0x67, 0x00, 0x00,
         0x89, 0x00, 0xab, 0x00, 0x00, 0xcd, 0x00, 0xef, 0x00, 0x00,
     };
 
     Decoder::applyVestGrouped(body, values, bhLayout, layoutGroups);
-    TEST_ASSERT_EQUAL_INT(0, actuator0->intensity);
-    TEST_ASSERT_EQUAL_INT(273, actuator1->intensity);
-    TEST_ASSERT_EQUAL_INT(3276, actuator2->intensity);
-    TEST_ASSERT_EQUAL_INT(3549, actuator3->intensity);
-    TEST_ASSERT_EQUAL_INT(546, actuator4->intensity);
-    TEST_ASSERT_EQUAL_INT(819, actuator5->intensity);
-    TEST_ASSERT_EQUAL_INT(3822, actuator6->intensity);
-    TEST_ASSERT_EQUAL_INT(4095, actuator7->intensity);
-
-    TEST_ASSERT_EQUAL_INT(1092, actuator8->intensity);
-    TEST_ASSERT_EQUAL_INT(1365, actuator9->intensity);
-    TEST_ASSERT_EQUAL_INT(2184, actuator10->intensity);
-    TEST_ASSERT_EQUAL_INT(2457, actuator11->intensity);
-    TEST_ASSERT_EQUAL_INT(1638, actuator12->intensity);
-    TEST_ASSERT_EQUAL_INT(1911, actuator13->intensity);
-    TEST_ASSERT_EQUAL_INT(2730, actuator14->intensity);
-    TEST_ASSERT_EQUAL_INT(3003, actuator15->intensity);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, actuator0->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(273.0F/4095.0F, actuator1->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(3276.0F/4095.0F, actuator2->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(3549.0F/4095.0F, actuator3->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(546.0F/4095.0F, actuator4->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(819.0F/4095.0F, actuator5->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(3822.0F/4095.0F, actuator6->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(4095.0F/4095.0F, actuator7->intensity, 2);
+
+    ASSERT_EQUAL_FLOAT_ROUNDED(1092.0F/4095.0F, actuator8->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1365.0F/4095.0F, actuator9->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(2184.0F/4095.0F, actuator10->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(2457.0F/4095.0F, actuator11->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1638.0F/4095.0F, actuator12->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1911.0F/4095.0F, actuator13->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(2730.0F/4095.0F, actuator14->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(3003.0F/4095.0F, actuator15->intensity, 2);
 }
 
 void test_layout_tactsuitx40(void)
 {
-    static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTSUITX40_SIZE;
-    static const OutputLayout bhLayout[bhLayoutSize] = BH_LAYOUT_TACTSUITX40;
+    static const std::array<OutputLayout, BH_LAYOUT_TACTSUITX40_SIZE> bhLayout = { BH_LAYOUT_TACTSUITX40 };
 
-    auto body = new HapticBody();
+    auto body = new FloatBody();
 
-    std::vector<std::vector<VibroPlane::Actuator*>> frontMatrix = {
+    std::vector<std::vector<FloatPlane::Actuator*>> frontMatrix = {
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
     };
-    std::vector<std::vector<VibroPlane::Actuator*>> backMatrix = {
+    std::vector<std::vector<FloatPlane::Actuator*>> backMatrix = {
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
@@ -106,11 +108,11 @@ void test_layout_tactsuitx40(void)
         { new TestActuator(), new TestActuator(), new TestActuator(), new TestActuator() },
     };
 
-    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>(frontMatrix);
-    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>(backMatrix);
+    auto frontOutputs = PlaneMapper_Margin::mapMatrixCoordinates<FloatPlane::Actuator>(frontMatrix);
+    auto backOutputs = PlaneMapper_Margin::mapMatrixCoordinates<FloatPlane::Actuator>(backMatrix);
 
-    auto frontPlane = new VibroPlane(frontOutputs);
-    auto backPlane = new VibroPlane(backOutputs);
+    auto frontPlane = new FloatPlane(frontOutputs);
+    auto backPlane = new FloatPlane(backOutputs);
 
     body->addTarget(Target::ChestFront, frontPlane);
     body->addTarget(Target::ChestBack, backPlane);
@@ -124,55 +126,54 @@ void test_layout_tactsuitx40(void)
       },
       bhLayout
     );
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(frontMatrix[0][0])->intensity);
-    TEST_ASSERT_EQUAL_INT(273, static_cast<TestActuator*>(frontMatrix[0][1])->intensity);
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(frontMatrix[0][2])->intensity);
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(frontMatrix[0][3])->intensity);
-    TEST_ASSERT_EQUAL_INT(546, static_cast<TestActuator*>(frontMatrix[1][0])->intensity);
-    TEST_ASSERT_EQUAL_INT(819, static_cast<TestActuator*>(frontMatrix[1][1])->intensity);
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(frontMatrix[1][2])->intensity);
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(frontMatrix[1][3])->intensity);
-    TEST_ASSERT_EQUAL_INT(1092, static_cast<TestActuator*>(frontMatrix[2][0])->intensity);
-    TEST_ASSERT_EQUAL_INT(1365, static_cast<TestActuator*>(frontMatrix[2][1])->intensity);
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(frontMatrix[2][2])->intensity);
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(frontMatrix[2][3])->intensity);
-    TEST_ASSERT_EQUAL_INT(1638, static_cast<TestActuator*>(frontMatrix[3][0])->intensity);
-    TEST_ASSERT_EQUAL_INT(1911, static_cast<TestActuator*>(frontMatrix[3][1])->intensity);
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(frontMatrix[3][2])->intensity);
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(frontMatrix[3][3])->intensity);
-    TEST_ASSERT_EQUAL_INT(2184, static_cast<TestActuator*>(frontMatrix[4][0])->intensity);
-    TEST_ASSERT_EQUAL_INT(2457, static_cast<TestActuator*>(frontMatrix[4][1])->intensity);
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(frontMatrix[4][2])->intensity);
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(frontMatrix[4][3])->intensity);
-
-    TEST_ASSERT_EQUAL_INT(2730, static_cast<TestActuator*>(backMatrix[0][0])->intensity);
-    TEST_ASSERT_EQUAL_INT(3003, static_cast<TestActuator*>(backMatrix[0][1])->intensity);
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(backMatrix[0][2])->intensity);
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(backMatrix[0][3])->intensity);
-    TEST_ASSERT_EQUAL_INT(3276, static_cast<TestActuator*>(backMatrix[1][0])->intensity);
-    TEST_ASSERT_EQUAL_INT(3549, static_cast<TestActuator*>(backMatrix[1][1])->intensity);
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(backMatrix[1][2])->intensity);
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(backMatrix[1][3])->intensity);
-    TEST_ASSERT_EQUAL_INT(3822, static_cast<TestActuator*>(backMatrix[2][0])->intensity);
-    TEST_ASSERT_EQUAL_INT(4095, static_cast<TestActuator*>(backMatrix[2][1])->intensity);
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(backMatrix[2][2])->intensity);
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(backMatrix[2][3])->intensity);
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(backMatrix[3][0])->intensity);
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(backMatrix[3][1])->intensity);
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(backMatrix[3][2])->intensity);
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(backMatrix[3][3])->intensity);
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(backMatrix[4][0])->intensity);
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(backMatrix[4][1])->intensity);
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(backMatrix[4][2])->intensity);
-    TEST_ASSERT_EQUAL_INT(0, static_cast<TestActuator*>(backMatrix[4][3])->intensity);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(frontMatrix[0][0])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(273.0F/4095.0F, static_cast<TestActuator*>(frontMatrix[0][1])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(frontMatrix[0][2])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(frontMatrix[0][3])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(546.0F/4095.0F, static_cast<TestActuator*>(frontMatrix[1][0])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(819.0F/4095.0F, static_cast<TestActuator*>(frontMatrix[1][1])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(frontMatrix[1][2])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(frontMatrix[1][3])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1092.0F/4095.0F, static_cast<TestActuator*>(frontMatrix[2][0])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1365.0F/4095.0F, static_cast<TestActuator*>(frontMatrix[2][1])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(frontMatrix[2][2])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(frontMatrix[2][3])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1638.0F/4095.0F, static_cast<TestActuator*>(frontMatrix[3][0])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1911.0F/4095.0F, static_cast<TestActuator*>(frontMatrix[3][1])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(frontMatrix[3][2])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(frontMatrix[3][3])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(2184.0F/4095.0F, static_cast<TestActuator*>(frontMatrix[4][0])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(2457.0F/4095.0F, static_cast<TestActuator*>(frontMatrix[4][1])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(frontMatrix[4][2])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(frontMatrix[4][3])->intensity, 2);
+
+    ASSERT_EQUAL_FLOAT_ROUNDED(2730.0F/4095.0F, static_cast<TestActuator*>(backMatrix[0][0])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(3003.0F/4095.0F, static_cast<TestActuator*>(backMatrix[0][1])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(backMatrix[0][2])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(backMatrix[0][3])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(3276.0F/4095.0F, static_cast<TestActuator*>(backMatrix[1][0])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(3549.0F/4095.0F, static_cast<TestActuator*>(backMatrix[1][1])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(backMatrix[1][2])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(backMatrix[1][3])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(3822.0F/4095.0F, static_cast<TestActuator*>(backMatrix[2][0])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(4095.0F/4095.0F, static_cast<TestActuator*>(backMatrix[2][1])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(backMatrix[2][2])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(backMatrix[2][3])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(backMatrix[3][0])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(backMatrix[3][1])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(backMatrix[3][2])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(backMatrix[3][3])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(backMatrix[4][0])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(backMatrix[4][1])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(backMatrix[4][2])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(backMatrix[4][3])->intensity, 2);
 }
 
 void test_layout_tactal(void)
 {
-    static constexpr size_t bhLayoutSize = BH_LAYOUT_TACTAL_SIZE;
-    static const ::SenseShift::Body::Haptics::Position bhLayout[bhLayoutSize] = BH_LAYOUT_TACTAL;
+    static const std::array<Position, BH_LAYOUT_TACTAL_SIZE> bhLayout = { BH_LAYOUT_TACTAL };
 
-    auto body = new HapticBody();
+    auto body = new FloatBody();
 
     TestActuator* actuator0 = new TestActuator();
     TestActuator* actuator1 = new TestActuator();
@@ -181,28 +182,28 @@ void test_layout_tactal(void)
     TestActuator* actuator4 = new TestActuator();
     TestActuator* actuator5 = new TestActuator();
 
-    auto outputs = PlaneMapper_Margin::mapMatrixCoordinates<VibroPlane::Actuator>({
+    auto outputs = PlaneMapper_Margin::mapMatrixCoordinates<FloatPlane::Actuator>({
       { actuator0, actuator1, actuator2, actuator3, actuator4, actuator5 },
     });
-    auto plane = new VibroPlane(outputs);
+    auto plane = new FloatPlane(outputs);
 
     body->addTarget(Target::FaceFront, plane);
 
     Decoder::applyPlain(body, { 0x64, 0x00, 0x00, 0x00, 0x00, 0x00 }, bhLayout, Effect::Vibro, Target::FaceFront);
-    TEST_ASSERT_EQUAL_INT(4095, actuator0->intensity);
-    TEST_ASSERT_EQUAL_INT(0, actuator1->intensity);
-    TEST_ASSERT_EQUAL_INT(0, actuator2->intensity);
-    TEST_ASSERT_EQUAL_INT(0, actuator3->intensity);
-    TEST_ASSERT_EQUAL_INT(0, actuator4->intensity);
-    TEST_ASSERT_EQUAL_INT(0, actuator5->intensity);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1.0F, actuator0->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, actuator1->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, actuator2->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, actuator3->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, actuator4->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, actuator5->intensity, 2);
 
     Decoder::applyPlain(body, { 0x10, 0x20, 0x30, 0x40, 0x50, 0x60 }, bhLayout, Effect::Vibro, Target::FaceFront);
-    TEST_ASSERT_EQUAL_INT(655, actuator0->intensity);
-    TEST_ASSERT_EQUAL_INT(1310, actuator1->intensity);
-    TEST_ASSERT_EQUAL_INT(1965, actuator2->intensity);
-    TEST_ASSERT_EQUAL_INT(2620, actuator3->intensity);
-    TEST_ASSERT_EQUAL_INT(3276, actuator4->intensity);
-    TEST_ASSERT_EQUAL_INT(3931, actuator5->intensity);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0.16F, actuator0->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1310.0F/4095.0F, actuator1->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1965.0F/4095.0F, actuator2->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(2620.0F/4095.0F, actuator3->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(3276.0F/4095.0F, actuator4->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(3931.0F/4095.0F, actuator5->intensity, 2);
 }
 
 void test_layout_tactglove(void)
@@ -214,7 +215,7 @@ void test_layout_tactglove(void)
     TestActuator* actuatorLittle = new TestActuator();
     TestActuator* actuatorWrist = new TestActuator();
 
-    auto body = new HapticBody();
+    auto body = new FloatBody();
     const auto& bhLayout = TactGloveLeftLayout;
 
     addTactGloveActuators(
@@ -229,20 +230,20 @@ void test_layout_tactglove(void)
     );
 
     Decoder::applyPlain(body, { 0x64, 0x00, 0x00, 0x00, 0x00, 0x00 }, bhLayout, Effect::Vibro);
-    TEST_ASSERT_EQUAL_INT(4095, actuatorThumb->intensity);
-    TEST_ASSERT_EQUAL_INT(0, actuatorIndex->intensity);
-    TEST_ASSERT_EQUAL_INT(0, actuatorMiddle->intensity);
-    TEST_ASSERT_EQUAL_INT(0, actuatorRing->intensity);
-    TEST_ASSERT_EQUAL_INT(0, actuatorLittle->intensity);
-    TEST_ASSERT_EQUAL_INT(0, actuatorWrist->intensity);
+    ASSERT_EQUAL_FLOAT_ROUNDED(4095.0F/4095.0F, actuatorThumb->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, actuatorIndex->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, actuatorMiddle->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, actuatorRing->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, actuatorLittle->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(0, actuatorWrist->intensity, 2);
 
     Decoder::applyPlain(body, { 0x10, 0x20, 0x30, 0x40, 0x50, 0x60 }, bhLayout, Effect::Vibro);
-    TEST_ASSERT_EQUAL_INT(655, actuatorThumb->intensity);
-    TEST_ASSERT_EQUAL_INT(1310, actuatorIndex->intensity);
-    TEST_ASSERT_EQUAL_INT(1965, actuatorMiddle->intensity);
-    TEST_ASSERT_EQUAL_INT(2620, actuatorRing->intensity);
-    TEST_ASSERT_EQUAL_INT(3276, actuatorLittle->intensity);
-    TEST_ASSERT_EQUAL_INT(3931, actuatorWrist->intensity);
+    ASSERT_EQUAL_FLOAT_ROUNDED(655.0F/4095.0F, actuatorThumb->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1310.0F/4095.0F, actuatorIndex->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1965.0F/4095.0F, actuatorMiddle->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(2620.0F/4095.0F, actuatorRing->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(3276.0F/4095.0F, actuatorLittle->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(3931.0F/4095.0F, actuatorWrist->intensity, 2);
 }
 
 int process(void)
diff --git a/test/test_haptics_body/main.cpp b/test/test_haptics_body/main.cpp
index 7f00d150..96b39736 100644
--- a/test/test_haptics_body/main.cpp
+++ b/test/test_haptics_body/main.cpp
@@ -4,27 +4,27 @@
 using namespace SenseShift::Body::Haptics;
 using namespace SenseShift::Output;
 
-class TestActuator : public IActuator<uint16_t> {
+class TestActuator : public IOutput<float> {
   public:
     bool isSetup = false;
-    uint16_t intensity = 0;
+    float intensity = 0;
 
-    TestActuator() : IActuator<uint16_t>() {}
-    void setup() override { this->isSetup = true; }
-    void writeOutput(uint16_t intensity) override { this->intensity = intensity; }
+    TestActuator() : IFloatOutput() {}
+    void init() override { this->isSetup = true; }
+    void writeState(float newIntensity) override { this->intensity = newIntensity; }
 };
 
 void test_it_sets_up_planes(void)
 {
-    auto body = new HapticBody();
+    auto body = new FloatBody();
 
-    VibroPlane::ActuatorMap outputs = {
+    FloatPlane::ActuatorMap outputs = {
         { { 0, 0 }, new TestActuator() },
         { { 0, 1 }, new TestActuator() },
         { { 1, 0 }, new TestActuator() },
         { { 1, 1 }, new TestActuator() },
     };
-    auto plane = new VibroPlane(outputs);
+    auto plane = new FloatPlane(outputs);
 
     body->addTarget(Target::ChestFront, plane);
     body->setup();
@@ -34,14 +34,14 @@ void test_it_sets_up_planes(void)
     }
 }
 
-void test_it_handles_effect__vibro(void)
+void test_it_handles_effect(void)
 {
     auto actuator1 = new TestActuator(), actuator2 = new TestActuator(), actuator3 = new TestActuator(),
          actuator4 = new TestActuator();
 
-    auto body = new HapticBody();
+    auto body = new FloatBody();
 
-    auto plane = new VibroPlane({
+    auto plane = new FloatPlane({
       { { 0, 0 }, actuator1 },
       { { 0, 1 }, actuator2 },
       { { 1, 0 }, actuator3 },
@@ -50,35 +50,17 @@ void test_it_handles_effect__vibro(void)
 
     body->addTarget(Target::ChestFront, plane);
 
-    body->effect({
-      .effect = Effect::Vibro,
-      .target = Target::ChestFront,
-      .position = { 0, 0 },
-      .data = (VibroEffectData) 64,
-    });
-    body->effect({
-      .effect = Effect::Vibro,
-      .target = Target::ChestFront,
-      .position = { 0, 1 },
-      .data = (VibroEffectData) 128,
-    });
-    body->effect({
-      .effect = Effect::Vibro,
-      .target = Target::ChestFront,
-      .position = { 1, 0 },
-      .data = (VibroEffectData) 192,
-    });
-    body->effect({
-      .effect = Effect::Vibro,
-      .target = Target::ChestFront,
-      .position = { 1, 1 },
-      .data = (VibroEffectData) 255,
-    });
+    TEST_ASSERT_TRUE(body->getTarget(Target::ChestFront).has_value());
+
+    body->effect(Target::ChestFront, { 0, 0 }, 0.25F);
+    body->effect(Target::ChestFront, { 0, 1 }, 0.5F);
+    body->effect(Target::ChestFront, { 1, 0 }, 0.75F);
+    body->effect(Target::ChestFront, { 1, 1 }, 1.0F);
 
-    TEST_ASSERT_EQUAL(64, actuator1->intensity);
-    TEST_ASSERT_EQUAL(128, actuator2->intensity);
-    TEST_ASSERT_EQUAL(192, actuator3->intensity);
-    TEST_ASSERT_EQUAL(255, actuator4->intensity);
+    TEST_ASSERT_EQUAL_FLOAT(0.25F, actuator1->intensity);
+    TEST_ASSERT_EQUAL_FLOAT(0.5F, actuator2->intensity);
+    TEST_ASSERT_EQUAL_FLOAT(0.75F, actuator3->intensity);
+    TEST_ASSERT_EQUAL_FLOAT(1.0F, actuator4->intensity);
 }
 
 int process(void)
@@ -86,7 +68,7 @@ int process(void)
     UNITY_BEGIN();
 
     RUN_TEST(test_it_sets_up_planes);
-    RUN_TEST(test_it_handles_effect__vibro);
+    RUN_TEST(test_it_handles_effect);
 
     return UNITY_END();
 }
diff --git a/test/test_haptics_plane/main.cpp b/test/test_haptics_plane/main.cpp
index 7c8890cc..a9c329a1 100644
--- a/test/test_haptics_plane/main.cpp
+++ b/test/test_haptics_plane/main.cpp
@@ -4,26 +4,26 @@
 using namespace SenseShift::Body::Haptics;
 using namespace SenseShift::Output;
 
-class TestActuator : public IActuator<uint16_t> {
+class TestActuator : public IOutput<float> {
   public:
     bool isSetup = false;
-    uint16_t intensity = 0;
+    float intensity = 0;
 
-    TestActuator() : IActuator<uint16_t>() {}
-    void setup() override { this->isSetup = true; }
-    void writeOutput(uint16_t intensity) override { this->intensity = intensity; }
+    TestActuator() : IFloatOutput() {}
+    void init() override { this->isSetup = true; }
+    void writeState(float newIntensity) override { this->intensity = newIntensity; }
 };
 
 void test_it_sets_up_actuators(void)
 {
-    VibroPlane::ActuatorMap outputs = {
+    FloatPlane::ActuatorMap outputs = {
         { { 0, 0 }, new TestActuator() },
         { { 0, 1 }, new TestActuator() },
         { { 1, 0 }, new TestActuator() },
         { { 1, 1 }, new TestActuator() },
     };
 
-    auto plane = new VibroPlane(outputs);
+    auto plane = new FloatPlane(outputs);
     plane->setup();
 
     TEST_ASSERT_EQUAL(outputs.size(), plane->getAvailablePoints()->size());
@@ -39,24 +39,24 @@ void test_it_writes_to_correct_output(void)
     auto actuator = new TestActuator(), actuator2 = new TestActuator(), actuator3 = new TestActuator(),
          actuator4 = new TestActuator();
 
-    VibroPlane::ActuatorMap outputs = {
+    FloatPlane::ActuatorMap outputs = {
         { { 0, 0 }, actuator },
         { { 0, 1 }, actuator2 },
         { { 1, 0 }, actuator3 },
         { { 1, 1 }, actuator4 },
     };
 
-    auto plane = new VibroPlane(outputs);
+    auto plane = new FloatPlane(outputs);
 
-    plane->effect({ 0, 0 }, 64);
-    plane->effect({ 0, 1 }, 128);
-    plane->effect({ 1, 0 }, 192);
-    plane->effect({ 1, 1 }, 255);
+    plane->effect({ 0, 0 }, 0.25F);
+    plane->effect({ 0, 1 }, 0.5F);
+    plane->effect({ 1, 0 }, 0.75F);
+    plane->effect({ 1, 1 }, 1.0F);
 
-    TEST_ASSERT_EQUAL_UINT8(64, actuator->intensity);
-    TEST_ASSERT_EQUAL_UINT8(128, actuator2->intensity);
-    TEST_ASSERT_EQUAL_UINT8(192, actuator3->intensity);
-    TEST_ASSERT_EQUAL_UINT8(255, actuator4->intensity);
+    TEST_ASSERT_EQUAL_FLOAT(0.25F, actuator->intensity);
+    TEST_ASSERT_EQUAL_FLOAT(0.5F, actuator2->intensity);
+    TEST_ASSERT_EQUAL_FLOAT(0.75F, actuator3->intensity);
+    TEST_ASSERT_EQUAL_FLOAT(1.0F, actuator4->intensity);
 }
 
 void test_it_updates_state(void)
@@ -64,31 +64,31 @@ void test_it_updates_state(void)
     auto actuator = new TestActuator(), actuator2 = new TestActuator(), actuator3 = new TestActuator(),
          actuator4 = new TestActuator();
 
-    VibroPlane::ActuatorMap outputs = {
+    FloatPlane::ActuatorMap outputs = {
         { { 0, 0 }, actuator },
         { { 0, 1 }, actuator2 },
         { { 1, 0 }, actuator3 },
         { { 1, 1 }, actuator4 },
     };
 
-    auto plane = new VibroPlane(outputs);
+    auto plane = new FloatPlane(outputs);
 
-    plane->effect({ 0, 0 }, 64);
-    plane->effect({ 0, 1 }, 128);
-    plane->effect({ 1, 0 }, 192);
-    plane->effect({ 1, 1 }, 255);
+    plane->effect({ 0, 0 }, 0.25F);
+    plane->effect({ 0, 1 }, 0.5F);
+    plane->effect({ 1, 0 }, 0.75F);
+    plane->effect({ 1, 1 }, 1.0F);
 
     TEST_ASSERT_TRUE(plane->getActuatorStates()->count({ 0, 0 }) > 0);
-    TEST_ASSERT_EQUAL_UINT8(64, plane->getActuatorStates()->at({ 0, 0 }));
+    TEST_ASSERT_EQUAL_FLOAT(0.25F, plane->getActuatorStates()->at({ 0, 0 }));
 
     TEST_ASSERT_TRUE(plane->getActuatorStates()->count({ 0, 1 }) > 0);
-    TEST_ASSERT_EQUAL_UINT8(128, plane->getActuatorStates()->at({ 0, 1 }));
+    TEST_ASSERT_EQUAL_FLOAT(0.5F, plane->getActuatorStates()->at({ 0, 1 }));
 
     TEST_ASSERT_TRUE(plane->getActuatorStates()->count({ 1, 0 }) > 0);
-    TEST_ASSERT_EQUAL_UINT8(192, plane->getActuatorStates()->at({ 1, 0 }));
+    TEST_ASSERT_EQUAL_FLOAT(0.75F, plane->getActuatorStates()->at({ 1, 0 }));
 
     TEST_ASSERT_TRUE(plane->getActuatorStates()->count({ 1, 1 }) > 0);
-    TEST_ASSERT_EQUAL_UINT8(255, plane->getActuatorStates()->at({ 1, 1 }));
+    TEST_ASSERT_EQUAL_FLOAT(1.0F, plane->getActuatorStates()->at({ 1, 1 }));
 }
 
 void test_closest_it_writes_to_correct_if_exact(void)
@@ -96,24 +96,24 @@ void test_closest_it_writes_to_correct_if_exact(void)
     auto actuator = new TestActuator(), actuator2 = new TestActuator(), actuator3 = new TestActuator(),
          actuator4 = new TestActuator();
 
-    VibroPlane_Closest::ActuatorMap outputs = {
+    FloatPlane_Closest::ActuatorMap outputs = {
         { { 0, 0 }, actuator },
         { { 0, 1 }, actuator2 },
         { { 1, 0 }, actuator3 },
         { { 1, 1 }, actuator4 },
     };
 
-    auto plane = new VibroPlane_Closest(outputs);
+    auto plane = new FloatPlane_Closest(outputs);
 
-    plane->effect({ 0, 0 }, 1);
-    plane->effect({ 0, 1 }, 2);
-    plane->effect({ 1, 0 }, 3);
-    plane->effect({ 1, 1 }, 4);
+    plane->effect({ 0, 0 }, 0.25F);
+    plane->effect({ 0, 1 }, 0.5F);
+    plane->effect({ 1, 0 }, 0.75F);
+    plane->effect({ 1, 1 }, 1.0F);
 
-    TEST_ASSERT_EQUAL(1, actuator->intensity);
-    TEST_ASSERT_EQUAL(2, actuator2->intensity);
-    TEST_ASSERT_EQUAL(3, actuator3->intensity);
-    TEST_ASSERT_EQUAL(4, actuator4->intensity);
+    TEST_ASSERT_EQUAL_FLOAT(0.25F, actuator->intensity);
+    TEST_ASSERT_EQUAL_FLOAT(0.5F, actuator2->intensity);
+    TEST_ASSERT_EQUAL_FLOAT(0.75F, actuator3->intensity);
+    TEST_ASSERT_EQUAL_FLOAT(1.0F, actuator4->intensity);
 }
 
 void test_closest_it_correctly_finds_closest(void)
@@ -121,22 +121,22 @@ void test_closest_it_correctly_finds_closest(void)
     auto actuator = new TestActuator(), actuator2 = new TestActuator(), actuator3 = new TestActuator(),
          actuator4 = new TestActuator();
 
-    VibroPlane_Closest::ActuatorMap outputs = {
+    FloatPlane_Closest::ActuatorMap outputs = {
         { { 0, 0 }, actuator },
         { { 0, 64 }, actuator2 },
         { { 64, 0 }, actuator3 },
         { { 64, 64 }, actuator4 },
     };
 
-    auto plane = new VibroPlane_Closest(outputs);
+    auto plane = new FloatPlane_Closest(outputs);
 
-    plane->effect({ 16, 16 }, 16);
-    plane->effect({ 65, 65 }, 65);
+    plane->effect({ 16, 16 }, 0.25F);
+    plane->effect({ 65, 65 }, 0.5F);
 
-    TEST_ASSERT_EQUAL(16, actuator->intensity);
-    TEST_ASSERT_EQUAL(0, actuator2->intensity);
-    TEST_ASSERT_EQUAL(0, actuator3->intensity);
-    TEST_ASSERT_EQUAL(65, actuator4->intensity);
+    TEST_ASSERT_EQUAL_FLOAT(0.25F, actuator->intensity);
+    TEST_ASSERT_EQUAL_FLOAT(0, actuator2->intensity);
+    TEST_ASSERT_EQUAL_FLOAT(0, actuator3->intensity);
+    TEST_ASSERT_EQUAL_FLOAT(0.5F, actuator4->intensity);
 }
 
 void test_closest_it_updates_state(void)
@@ -144,29 +144,29 @@ void test_closest_it_updates_state(void)
     auto actuator = new TestActuator(), actuator2 = new TestActuator(), actuator3 = new TestActuator(),
          actuator4 = new TestActuator();
 
-    VibroPlane_Closest::ActuatorMap outputs = {
+    FloatPlane_Closest::ActuatorMap outputs = {
         { { 0, 0 }, actuator },
         { { 0, 64 }, actuator2 },
         { { 64, 0 }, actuator3 },
         { { 64, 64 }, actuator4 },
     };
 
-    auto plane = new VibroPlane_Closest(outputs);
+    auto plane = new FloatPlane_Closest(outputs);
 
-    plane->effect({ 16, 16 }, 16);
-    plane->effect({ 65, 65 }, 65);
+    plane->effect({ 16, 16 }, 0.25F);
+    plane->effect({ 65, 65 }, 0.5F);
 
     TEST_ASSERT_TRUE(plane->getActuatorStates()->count({ 0, 0 }) > 0);
-    TEST_ASSERT_EQUAL(16, plane->getActuatorStates()->at({ 0, 0 }));
+    TEST_ASSERT_EQUAL_FLOAT(0.25F, plane->getActuatorStates()->at({ 0, 0 }));
 
     TEST_ASSERT_TRUE(plane->getActuatorStates()->count({ 0, 64 }) > 0);
-    TEST_ASSERT_EQUAL(0, plane->getActuatorStates()->at({ 0, 64 }));
+    TEST_ASSERT_EQUAL_FLOAT(0, plane->getActuatorStates()->at({ 0, 64 }));
 
     TEST_ASSERT_TRUE(plane->getActuatorStates()->count({ 64, 0 }) > 0);
-    TEST_ASSERT_EQUAL(0, plane->getActuatorStates()->at({ 64, 0 }));
+    TEST_ASSERT_EQUAL_FLOAT(0, plane->getActuatorStates()->at({ 64, 0 }));
 
     TEST_ASSERT_TRUE(plane->getActuatorStates()->count({ 64, 64 }) > 0);
-    TEST_ASSERT_EQUAL(65, plane->getActuatorStates()->at({ 64, 64 }));
+    TEST_ASSERT_EQUAL_FLOAT(0.5F, plane->getActuatorStates()->at({ 64, 64 }));
 }
 
 void test_plain_mapper_margin_map_points(void)
diff --git a/test/test_io_sensor/main.cpp b/test/test_io_sensor/main.cpp
index 74b4ace3..bbb9ecca 100644
--- a/test/test_io_sensor/main.cpp
+++ b/test/test_io_sensor/main.cpp
@@ -2,22 +2,31 @@
 #include <unity.h>
 
 using namespace SenseShift::Input;
-using namespace SenseShift::Calibration;
 
-class TestAnalogSensor : public ISimpleSensor<int> {
+class TestAnalogCountingSensor : public ISimpleSensor<int> {
   public:
     int count = 0;
     int setupCounter = 0;
 
     void init() override { this->setupCounter++; };
 
-    int getValue() override { return ++this->count; };
+    auto getValue() -> int override { return ++this->count; };
+};
+
+class TestAnalogSensor : public ISimpleSensor<int> {
+public:
+    int value = 0;
+    int setupCounter = 0;
+
+    void init() override { this->setupCounter++; };
+
+    auto getValue() -> int override { return this->value; };
 };
 
 void test_memoized_sensor(void)
 {
-    auto inner = new TestAnalogSensor();
-    auto sensor = new MemoizedSensor<int>(inner);
+    auto inner = new TestAnalogCountingSensor();
+    auto sensor = new SimpleSensorDecorator(inner);
 
     TEST_ASSERT_EQUAL_INT(0, inner->setupCounter);
     sensor->init();
@@ -32,7 +41,7 @@ void test_memoized_sensor(void)
     TEST_ASSERT_EQUAL_INT(1, sensor->getValue());
 }
 
-class DummyCalibrator : public ICalibrator<int> {
+class DummyCalibrator : public ::SenseShift::Input::Calibration::ICalibrator<int> {
   public:
     uint8_t resetCounter = 0;
     int calibrated = 0;
@@ -48,64 +57,53 @@ class DummyCalibrator : public ICalibrator<int> {
 
 void test_calibrated_sensor(void)
 {
-    auto inner = new TestAnalogSensor();
+    auto inner = new TestAnalogCountingSensor();
     auto calibrator = new DummyCalibrator();
-    auto sensor = new CalibratedSimpleSensor<int>(inner, calibrator);
+
+    auto sensor = new SimpleSensorDecorator(inner);
+    sensor->setCalibrator(calibrator);
 
     TEST_ASSERT_EQUAL_INT(0, inner->setupCounter);
     sensor->init();
     TEST_ASSERT_EQUAL_INT(1, inner->setupCounter);
 
     calibrator->update(-1);
+    sensor->tick();
     TEST_ASSERT_EQUAL_INT(-1, sensor->getValue());
 
-    sensor->enableCalibration();
+    sensor->startCalibration();
+    sensor->tick();
     TEST_ASSERT_EQUAL_INT(2, sensor->getValue());
 
-    sensor->disableCalibration();
+    sensor->stopCalibration();
+    sensor->tick();
     TEST_ASSERT_EQUAL_INT(2, sensor->getValue());
 
-    sensor->resetCalibration();
+    sensor->reselCalibration();
+    sensor->tick();
     TEST_ASSERT_EQUAL_INT(0, sensor->getValue());
     TEST_ASSERT_EQUAL_INT(1, calibrator->resetCounter);
 }
 
-void test_average_sensor(void)
+void test_sensor_filter_multiply(void)
 {
     auto inner = new TestAnalogSensor();
-    auto sensor = new AverageSensor<int>(inner, 3);
+    auto sensor = new SimpleSensorDecorator(inner);
+    sensor->addFilters({
+      new ::SenseShift::Input::Filter::MultiplyFilter(2)
+    });
 
     TEST_ASSERT_EQUAL_INT(0, inner->setupCounter);
     sensor->init();
     TEST_ASSERT_EQUAL_INT(1, inner->setupCounter);
 
-    // TODO: mock inner sensor, to return more interesting values
-    TEST_ASSERT_EQUAL_INT(2, sensor->getValue());  // (1 + 2 + 3) / 3 = 2
-    TEST_ASSERT_EQUAL_INT(5, sensor->getValue());  // (4 + 5 + 6) / 3 = 5
-    TEST_ASSERT_EQUAL_INT(8, sensor->getValue());  // (7 + 8 + 9) / 3 = 8
-    TEST_ASSERT_EQUAL_INT(11, sensor->getValue()); // (10 + 11 + 12) / 3 = 11
-
-    inner->count = 0;
-    sensor = new AverageSensor<int>(inner, 10);
-
-    TEST_ASSERT_EQUAL_INT(5, sensor->getValue()); // (1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10) / 10 = 5
-}
-
-void test_static_median_sensor(void)
-{
-    auto inner = new TestAnalogSensor();
-    auto sensor = new StaticMedianSensor<int, 3>(inner);
-
-    TEST_ASSERT_EQUAL_INT(0, inner->setupCounter);
-    sensor->init();
-    TEST_ASSERT_EQUAL_INT(1, inner->setupCounter);
+    inner->value = 1;
+    sensor->tick();
+    TEST_ASSERT_EQUAL_INT(2, sensor->getValue());
 
-    // lmao, literally the same as average sensor
-    // TODO: mock inner sensor, to return more interesting values
-    TEST_ASSERT_EQUAL_INT(2, sensor->getValue());  // (1, 2, 3) = 2
-    TEST_ASSERT_EQUAL_INT(5, sensor->getValue());  // (4, 5, 6) = 5
-    TEST_ASSERT_EQUAL_INT(8, sensor->getValue());  // (7, 8, 9) = 8
-    TEST_ASSERT_EQUAL_INT(11, sensor->getValue()); // (10, 11, 12) = 11
+    inner->value = 16;
+    sensor->tick();
+    TEST_ASSERT_EQUAL_INT(32, sensor->getValue());
 }
 
 int process(void)
@@ -114,8 +112,7 @@ int process(void)
 
     RUN_TEST(test_memoized_sensor);
     RUN_TEST(test_calibrated_sensor);
-    RUN_TEST(test_average_sensor);
-    RUN_TEST(test_static_median_sensor);
+    RUN_TEST(test_sensor_filter_multiply);
 
     return UNITY_END();
 }
diff --git a/test/test_opengloves_gesture/main.cpp b/test/test_opengloves_gesture/main.cpp
index 0d89a11a..db4f7057 100644
--- a/test/test_opengloves_gesture/main.cpp
+++ b/test/test_opengloves_gesture/main.cpp
@@ -1,4 +1,4 @@
-#include <sensor/og_gesture.hpp>
+#include <senseshift/body/hands/gestures/grab.hpp>
 #include <unity.h>
 
 using namespace OpenGloves;

From 0be58cce53250c6247e012d32c26e00267547c44 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Tue, 6 Feb 2024 13:25:43 +0400
Subject: [PATCH 53/82] refactor(Calibration): apply SFINAE

---
 lib/arduino_esp32/library.json               |  7 ++
 lib/bhaptics/senseshift/bh/devices.hpp       |  2 +-
 lib/core/senseshift/core/helpers.hpp         |  6 +-
 lib/hands/hand_interface.hpp                 | 27 ------
 lib/io/senseshift/input/analog_threshold.hpp | 55 ++++++++++++
 lib/io/senseshift/input/calibration.hpp      | 95 +++++++++++++-------
 lib/io/senseshift/input/sensor.hpp           | 31 -------
 platformio.ini                               |  2 +-
 test/test_io_sensor/main.cpp                 | 33 +++++++
 test/test_util_calibration/main.cpp          | 72 ++++++++-------
 10 files changed, 203 insertions(+), 127 deletions(-)
 create mode 100644 lib/arduino_esp32/library.json
 delete mode 100644 lib/hands/hand_interface.hpp
 create mode 100644 lib/io/senseshift/input/analog_threshold.hpp

diff --git a/lib/arduino_esp32/library.json b/lib/arduino_esp32/library.json
new file mode 100644
index 00000000..d97e8e1b
--- /dev/null
+++ b/lib/arduino_esp32/library.json
@@ -0,0 +1,7 @@
+{
+  "$schema": "https://raw.githubusercontent.com/platformio/platformio-core/develop/platformio/assets/schema/library.json",
+  "frameworks": "arduino",
+  "platforms": [
+    "espressif32"
+  ]
+}
diff --git a/lib/bhaptics/senseshift/bh/devices.hpp b/lib/bhaptics/senseshift/bh/devices.hpp
index 7b051c85..b90d627c 100644
--- a/lib/bhaptics/senseshift/bh/devices.hpp
+++ b/lib/bhaptics/senseshift/bh/devices.hpp
@@ -3,7 +3,7 @@
 #include <array>
 #include <tuple>
 
-#include <hand_interface.hpp>
+#include "senseshift/body/hands/hands_interface.hpp"
 
 #include <senseshift/body/haptics/plane.hpp>
 #include <senseshift/body/haptics/body.hpp>
diff --git a/lib/core/senseshift/core/helpers.hpp b/lib/core/senseshift/core/helpers.hpp
index f15909e9..14d8e897 100644
--- a/lib/core/senseshift/core/helpers.hpp
+++ b/lib/core/senseshift/core/helpers.hpp
@@ -4,6 +4,8 @@
 #include <type_traits>
 #include <vector>
 
+#include <senseshift/core/logging.hpp>
+
 namespace SenseShift {
     /// Linearly interpolate between \p start and \p end by \p completion (between 0 and 1).
     template<typename Tp>
@@ -26,7 +28,7 @@ namespace SenseShift {
     /// \param max_out The maximum value of the output range.
     ///
     /// \return The remapped value.
-    template<typename Tp, typename Up>
+    template<typename Tp, typename Up = Tp>
     constexpr auto remap(Up value, Up min, Up max, Tp min_out, Tp max_out) -> Tp
     {
         static_assert(std::is_arithmetic_v<Tp>, "Type must be arithmetic");
@@ -52,7 +54,7 @@ namespace SenseShift {
     /// \return The remapped value.
     ///
     /// \note This is a simplified version of remap() where the minimum values are 0.
-    template<typename Tp, typename Up>
+    template<typename Tp, typename Up = Tp>
     constexpr auto remap_simple(Up value, Up max, Tp max_out) noexcept -> Tp
     {
         static_assert(std::is_arithmetic_v<Up>, "Type must be arithmetic");
diff --git a/lib/hands/hand_interface.hpp b/lib/hands/hand_interface.hpp
deleted file mode 100644
index 907a553b..00000000
--- a/lib/hands/hand_interface.hpp
+++ /dev/null
@@ -1,27 +0,0 @@
-#pragma once
-
-#include <cstdint>
-#include <senseshift/body/haptics/interface.hpp>
-
-namespace SenseShift::Body {
-    namespace Hands {
-        using HandSideIndex = std::uint8_t;
-        enum class HandSide : HandSideIndex { Left, Right };
-        using FingerIndex = std::uint8_t;
-        enum class Finger : FingerIndex {
-            Thumb,
-            Index,
-            Middle,
-            Ring,
-            Little,
-        };
-
-        namespace Haptics {
-            /**
-             * @brief Helper with position of the haptic device on the fingertip.
-             * Distal phalanx of the volar surface of the any finger.
-             */
-            static constexpr const ::SenseShift::Body::Haptics::Position FINGERTIP_POSITION(127, 16);
-        } // namespace Haptics
-    }     // namespace Hands
-} // namespace SenseShift::Body
diff --git a/lib/io/senseshift/input/analog_threshold.hpp b/lib/io/senseshift/input/analog_threshold.hpp
new file mode 100644
index 00000000..eb03eec5
--- /dev/null
+++ b/lib/io/senseshift/input/analog_threshold.hpp
@@ -0,0 +1,55 @@
+#pragma once
+
+#include "senseshift/input/sensor.hpp"
+
+namespace SenseShift::Input {
+    template<typename Tp>
+    class AnalogThresholdSensor : public BinarySensor, ITickable {
+    public:
+        /// Analog threshold sensor with hysteresis.
+        ///
+        /// \param source The source sensor.
+        /// \param threshold_upper Upper threshold, that needs to be crossed to transition from `low` to `high` states.
+        /// \param threshold_lower Lower threshold, that needs to be crossed to transition from `high` to `low` states.
+        explicit AnalogThresholdSensor(
+                ::SenseShift::Input::Sensor<Tp>* source,
+                Tp threshold_upper,
+                float threshold_lower,
+                bool attach_callbacks = false
+        ) : source_(source), threshold_upper_(threshold_upper), threshold_lower_(threshold_lower), attach_callbacks_(attach_callbacks) {}
+
+        /// \param source The source sensor.
+        /// \param threshold Threshold, that will be used for both upper and lower thresholds.
+        template <typename U = Tp, std::enable_if_t<std::is_same_v<U, float>, int> = 0>
+        explicit AnalogThresholdSensor(
+                ::SenseShift::Input::Sensor<Tp>* source,
+                float threshold = 0.5f,
+                bool attach_callbacks = false
+        ) : AnalogThresholdSensor(source, threshold, threshold, attach_callbacks) { }
+
+        void init() override {
+            SS_SUBSENSOR_INIT(this->source_, this->attach_callbacks_, [this](Tp /*value*/) {
+                this->recalculateState();
+            });
+        }
+
+        void tick() override {
+            if (this->attach_callbacks_) {
+                LOG_E("sensor.analog_threshold", "tick() called when attach_callbacks_ is true, infinite loop go wroom-wroom!");
+            }
+            this->recalculateState();
+        }
+
+        void recalculateState() {
+            const auto sensor_value  = this->source_->getValue();
+            this->publishState(sensor_value >= (this->getValue() ? this->threshold_lower_ : this->threshold_upper_));
+        }
+
+    private:
+        ::SenseShift::Input::Sensor<Tp>* source_;
+
+        float threshold_lower_, threshold_upper_;
+
+        bool attach_callbacks_ = false;
+    };
+}
\ No newline at end of file
diff --git a/lib/io/senseshift/input/calibration.hpp b/lib/io/senseshift/input/calibration.hpp
index b9baa95a..51412389 100644
--- a/lib/io/senseshift/input/calibration.hpp
+++ b/lib/io/senseshift/input/calibration.hpp
@@ -1,12 +1,12 @@
-/**
- * Calibrated input
- * Credit: https://github.com/JohnRThomas/OpenGloves-Firmware/blob/main/open-gloves/Calibration.hpp
- */
+/// Calibrated input
+/// Credit: https://github.com/JohnRThomas/OpenGloves-Firmware/blob/main/open-gloves/Calibration.hpp
 
 #pragma once
 
 #include "senseshift/utility.hpp"
 
+#include <type_traits>
+
 namespace SenseShift::Input::Calibration {
     struct ICalibrated {
         virtual void startCalibration() = 0;
@@ -33,7 +33,14 @@ namespace SenseShift::Input::Calibration {
       public:
         using ValueType = Tp;
 
-        MinMaxCalibrator(ValueType output_min, ValueType output_max) : output_min_(output_min), output_max_(output_max), value_min_(output_max), value_max_(output_min) {}
+        explicit MinMaxCalibrator(Tp output_min, Tp output_max)
+            : output_min_(output_min), output_max_(output_max),
+              value_min_(output_max), value_max_(output_min) {}
+
+        template<typename U = Tp, std::enable_if_t<std::is_same_v<U, float>, int> = 0>
+        explicit MinMaxCalibrator(Tp output_min = 0.0F, Tp output_max = 1.0F)
+            : output_min_(output_min), output_max_(output_max),
+              value_min_(output_max), value_max_(output_min) {}
 
         void reset() override {
             value_min_ = output_max_;
@@ -75,87 +82,113 @@ namespace SenseShift::Input::Calibration {
       private:
         const ValueType output_min_;
         const ValueType output_max_;
+
         ValueType value_min_;
         ValueType value_max_;
     };
 
-    template<typename Tp, Tp sensor_max, Tp driver_max_deviation, Tp output_min, Tp output_max>
+    template<typename Tp>
     class CenterPointDeviationCalibrator : public ICalibrator<Tp> {
         static_assert(std::is_arithmetic_v<Tp>, "CenterPointDeviationCalibrator only can be used with arithmetic types");
 
       public:
         using ValueType = Tp;
 
-        CenterPointDeviationCalibrator() : range_min(sensor_max), range_max(0) {}
+        CenterPointDeviationCalibrator(
+          Tp sensor_max, Tp driver_max_deviation, Tp output_min, Tp output_max
+        ) : sensor_max_(sensor_max), driver_max_deviation_(driver_max_deviation), output_min_(output_min),
+            output_max_(output_max), range_min_(sensor_max), range_max_(0) {}
+
+        template<typename U = Tp, std::enable_if_t<std::is_same_v<U, float>, int> = 0>
+        CenterPointDeviationCalibrator(
+            Tp sensor_max, Tp driver_max_deviation, Tp output_min = 0.0F, Tp output_max = 1.0F
+        ) : sensor_max_(sensor_max), driver_max_deviation_(driver_max_deviation), output_min_(output_min),
+            output_max_(output_max), range_min_(sensor_max), range_max_(0) {}
 
         void reset() override {
-            range_min = sensor_max;
-            range_max = 0;
+            this->range_min_ = this->sensor_max_;
+            this->range_max_ = 0;
         }
 
         void update(Tp input) override {
             // Update the min and the max.
-            if (input < range_min) {
-                range_min = ::SenseShift::remap<ValueType>(input, output_min, output_max, 0, sensor_max);
+            if (input < this->range_min_) {
+                this->range_min_ = ::SenseShift::remap<ValueType>(input, this->output_min_, this->output_max_, 0, this->sensor_max_);
             }
-            if (input > range_max) {
-                range_max = ::SenseShift::remap<ValueType>(input, output_min, output_max, 0, sensor_max);
+            if (input > this->range_max_) {
+                this->range_max_ = ::SenseShift::remap<ValueType>(input, this->output_min_, this->output_max_, 0, this->sensor_max_);
             }
         }
 
         auto calibrate(ValueType input) const -> ValueType override {
             // Find the center point of the sensor, so we know how much we have deviated from it.
-            Tp center = (range_min + range_max) / 2.0F;
+            Tp center = (this->range_min_ + this->range_max_) / 2.0F;
 
             // Map the input to the sensor range of motion.
-            int output = ::SenseShift::accurateMap<Tp>(input, output_min, output_max, 0, sensor_max);
+            int output = ::SenseShift::accurateMap<Tp>(input, this->output_min_, this->output_max_, 0, this->sensor_max_);
 
             // Find the deviation from the center and clamp it to the maximum that the driver supports.
-            output = std::clamp<int>(output - center, -driver_max_deviation, driver_max_deviation);
+            output = std::clamp<int>(output - center, -(this->driver_max_deviation_), this->driver_max_deviation_);
 
             // Finally map the deviation from the center back to the output range.
             return SenseShift::remap<ValueType, int>(
               output,
-              -driver_max_deviation,
-              driver_max_deviation,
-              output_min,
-              output_max
+              -(this->driver_max_deviation_),
+              this->driver_max_deviation_,
+              this->output_min_,
+              this->output_max_
             );
         }
 
       private:
-        Tp range_min;
-        Tp range_max;
+        const Tp sensor_max_;
+        const Tp driver_max_deviation_;
+        const Tp output_min_;
+        const Tp output_max_;
+
+        Tp range_min_;
+        Tp range_max_;
     };
 
-    template<typename Tp, Tp sensor_max, Tp driver_max_deviation, Tp output_min, Tp output_max>
+    template<typename Tp>
     class FixedCenterPointDeviationCalibrator : public ICalibrator<Tp> {
         static_assert(std::is_arithmetic_v<Tp>, "FixedCenterPointDeviationCalibrator only can be used with arithmetic types");
 
       public:
         using ValueType = Tp;
 
+        explicit FixedCenterPointDeviationCalibrator(
+          Tp sensor_max, Tp driver_max_deviation, Tp output_min, Tp output_max
+        ) : sensor_max_(sensor_max), driver_max_deviation_(driver_max_deviation),
+            output_min_(output_min), output_max_(output_max) {}
+
         void reset() override {}
         void update(ValueType input) override {}
 
         auto calibrate(ValueType input) const -> ValueType override {
             // Find the center point of the sensor, so we know how much we have deviated from it.
-            Tp center = sensor_max / 2.0F;
+            Tp center = this->sensor_max_ / 2.0F;
 
             // Map the input to the sensor range of motion.
-            int output = ::SenseShift::remap<int, Tp>(input, output_min, output_max, 0, sensor_max);
+            int output = ::SenseShift::remap<Tp>(input, this->output_min_, this->output_max_, 0, this->sensor_max_);
 
             // Find the deviation from the center and clamp it to the maximum that the driver supports.
-            output = std::clamp<int>(output - center, -driver_max_deviation, driver_max_deviation);
+            output = std::clamp<int>(output - center, -(this->driver_max_deviation_), this->driver_max_deviation_);
 
             // Finally map the deviation from the center back to the output range.
             return SenseShift::remap<ValueType, int>(
               output,
-              -driver_max_deviation,
-              driver_max_deviation,
-              output_min,
-              output_max
+              -(this->driver_max_deviation_),
+              this->driver_max_deviation_,
+              this->output_min_,
+              this->output_max_
             );
         }
+
+    private:
+        const Tp sensor_max_;
+        const Tp driver_max_deviation_;
+        const Tp output_min_;
+        const Tp output_max_;
     };
-} // namespace SenseShift::Input
+}  // namespace SenseShift::Input::Calibration
diff --git a/lib/io/senseshift/input/sensor.hpp b/lib/io/senseshift/input/sensor.hpp
index 86b22868..5b1e8ddb 100644
--- a/lib/io/senseshift/input/sensor.hpp
+++ b/lib/io/senseshift/input/sensor.hpp
@@ -238,37 +238,6 @@ namespace SenseShift::Input {
 //        SourceType* source_;
 //    };
 
-    class AnalogThresholdSensor : public BinarySensor, ITickable {
-      public:
-        explicit AnalogThresholdSensor(
-          ::SenseShift::Input::FloatSensor* index,
-          float threshold = 0.5F,
-          bool attach_callbacks = false
-        ) : index_(index), threshold_(threshold), attach_callbacks_(attach_callbacks) {}
-
-        void init() override {
-            SS_SUBSENSOR_INIT(this->index_, this->attach_callbacks_, [this](float /*value*/) {
-                this->recalculateState();
-            });
-        }
-
-        void tick() override {
-            if (this->attach_callbacks_) {
-                LOG_E("sensor.analog_threshold", "tick() called when attach_callbacks_ is true, infinite loop go wroom-wroom!");
-            }
-            this->recalculateState();
-        }
-
-        void recalculateState() {
-            return this->publishState(this->index_->getValue() > this->threshold_);
-        }
-
-      private:
-        ::SenseShift::Input::FloatSensor* index_;
-        float threshold_;
-        bool attach_callbacks_ = false;
-    };
-
     namespace _private {
         class TheFloatSensor : public Sensor<float> { };
     }
diff --git a/platformio.ini b/platformio.ini
index 4815cf7a..2b718462 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -29,7 +29,7 @@ build_flags       =
 ;	-D DEBUG_MODE=0
 ;	-D DEBUG_ESP_PORT=Serial
 ;	-D SENSESHIFT_SERIAL_PLOTTER=true
-;	-D SENSESHIFT_BATTERY_ENABLED=true
+	-D SENSESHIFT_BATTERY_ENABLED=true
 ;	-D SENSESHIFT_BLE_USE_NIMBLE=true
 
 build_src_filter  =
diff --git a/test/test_io_sensor/main.cpp b/test/test_io_sensor/main.cpp
index bbb9ecca..f91382fe 100644
--- a/test/test_io_sensor/main.cpp
+++ b/test/test_io_sensor/main.cpp
@@ -1,4 +1,5 @@
 #include <senseshift/input/sensor.hpp>
+#include <senseshift/input/analog_threshold.hpp>
 #include <unity.h>
 
 using namespace SenseShift::Input;
@@ -106,6 +107,37 @@ void test_sensor_filter_multiply(void)
     TEST_ASSERT_EQUAL_INT(32, sensor->getValue());
 }
 
+void test_sensor_analog_threshold(void)
+{
+    auto inner = new TestAnalogSensor();
+    auto source = new SimpleSensorDecorator(inner);
+    auto sensor = new AnalogThresholdSensor(source, 120, 80, true);
+
+    TEST_ASSERT_EQUAL_INT(0, inner->setupCounter);
+    sensor->init();
+    TEST_ASSERT_EQUAL_INT(1, inner->setupCounter);
+
+    // 100 is below the threshold, so the sensor should be off
+    inner->value = 100;
+    source->tick();
+    TEST_ASSERT_FALSE(sensor->getValue());
+
+    // 130 is above the threshold, so the sensor should be on
+    inner->value = 130;
+    source->tick();
+    TEST_ASSERT_TRUE(sensor->getValue());
+
+    // 90 is below the upper threshold, but above the lower threshold, so the sensor should stay on due to hysteresis
+    inner->value = 90;
+    source->tick();
+    TEST_ASSERT_TRUE(sensor->getValue());
+
+    // 70 is below the lower threshold, so the sensor should be off
+    inner->value = 70;
+    source->tick();
+    TEST_ASSERT_FALSE(sensor->getValue());
+}
+
 int process(void)
 {
     UNITY_BEGIN();
@@ -113,6 +145,7 @@ int process(void)
     RUN_TEST(test_memoized_sensor);
     RUN_TEST(test_calibrated_sensor);
     RUN_TEST(test_sensor_filter_multiply);
+    RUN_TEST(test_sensor_analog_threshold);
 
     return UNITY_END();
 }
diff --git a/test/test_util_calibration/main.cpp b/test/test_util_calibration/main.cpp
index ddc9d2b8..f554fab5 100644
--- a/test/test_util_calibration/main.cpp
+++ b/test/test_util_calibration/main.cpp
@@ -1,63 +1,67 @@
 #include "senseshift/input/calibration.hpp"
 #include <unity.h>
 
-using namespace SenseShift::Calibration;
+using namespace SenseShift::Input::Calibration;
 
 void test_minmax_calibrator(void)
 {
-    auto calibrator = new MinMaxCalibrator<uint16_t, 0, 4096>();
+    const auto calibrator = new MinMaxCalibrator<float>();
 
     // test uncalibrated neutral value
-    TEST_ASSERT_EQUAL_UINT16(2048, calibrator->calibrate(0));
-    TEST_ASSERT_EQUAL_UINT16(2048, calibrator->calibrate(10));
-    TEST_ASSERT_EQUAL_UINT16(2048, calibrator->calibrate(2048));
-    TEST_ASSERT_EQUAL_UINT16(2048, calibrator->calibrate(4086));
-    TEST_ASSERT_EQUAL_UINT16(2048, calibrator->calibrate(4096));
-
-    calibrator->update(10);
-    calibrator->update(4086);
-
-    TEST_ASSERT_EQUAL_UINT16(0, calibrator->calibrate(0));
-    TEST_ASSERT_EQUAL_UINT16(0, calibrator->calibrate(10));
-    TEST_ASSERT_EQUAL_UINT16(118, calibrator->calibrate(128));
-    TEST_ASSERT_EQUAL_UINT16(2048, calibrator->calibrate(2048));
-    TEST_ASSERT_EQUAL_UINT16(3977, calibrator->calibrate(3968));
-    TEST_ASSERT_EQUAL_UINT16(4096, calibrator->calibrate(4086));
-    TEST_ASSERT_EQUAL_UINT16(4096, calibrator->calibrate(4096));
+    TEST_ASSERT_EQUAL_FLOAT(0.5F, calibrator->calibrate(0));
+    TEST_ASSERT_EQUAL_FLOAT(0.5F, calibrator->calibrate(10));
+    TEST_ASSERT_EQUAL_FLOAT(0.5F, calibrator->calibrate(2048));
+    TEST_ASSERT_EQUAL_FLOAT(0.5F, calibrator->calibrate(4086));
+    TEST_ASSERT_EQUAL_FLOAT(0.5F, calibrator->calibrate(4096));
+
+    calibrator->update(0.1F);
+    calibrator->update(0.9F);
+
+    TEST_ASSERT_EQUAL_FLOAT(0.0F, calibrator->calibrate(0.0F));
+    TEST_ASSERT_EQUAL_FLOAT(0.0F, calibrator->calibrate(0.1F));
+    TEST_ASSERT_EQUAL_FLOAT(0.125F, calibrator->calibrate(0.2F));
+    TEST_ASSERT_EQUAL_FLOAT(0.25F, calibrator->calibrate(0.3F));
+    TEST_ASSERT_EQUAL_FLOAT(0.375F, calibrator->calibrate(0.4F));
+    TEST_ASSERT_EQUAL_FLOAT(0.5F, calibrator->calibrate(0.5F));
+    TEST_ASSERT_EQUAL_FLOAT(0.625F, calibrator->calibrate(0.6F));
+    TEST_ASSERT_EQUAL_FLOAT(0.75F, calibrator->calibrate(0.7F));
+    TEST_ASSERT_EQUAL_FLOAT(0.875F, calibrator->calibrate(0.8F));
+    TEST_ASSERT_EQUAL_FLOAT(1.0F, calibrator->calibrate(0.9F));
+    TEST_ASSERT_EQUAL_FLOAT(1.0F, calibrator->calibrate(1.0F));
 
     calibrator->reset();
 
     // test uncalibrated neutral value (again)
-    TEST_ASSERT_EQUAL_UINT16(2048, calibrator->calibrate(0));
-    TEST_ASSERT_EQUAL_UINT16(2048, calibrator->calibrate(10));
-    TEST_ASSERT_EQUAL_UINT16(2048, calibrator->calibrate(2048));
-    TEST_ASSERT_EQUAL_UINT16(2048, calibrator->calibrate(4086));
-    TEST_ASSERT_EQUAL_UINT16(2048, calibrator->calibrate(4096));
+    TEST_ASSERT_EQUAL_FLOAT(0.5F, calibrator->calibrate(0));
+    TEST_ASSERT_EQUAL_FLOAT(0.5F, calibrator->calibrate(10));
+    TEST_ASSERT_EQUAL_FLOAT(0.5F, calibrator->calibrate(2048));
+    TEST_ASSERT_EQUAL_FLOAT(0.5F, calibrator->calibrate(4086));
+    TEST_ASSERT_EQUAL_FLOAT(0.5F, calibrator->calibrate(4096));
 }
 
 void test_center_point_deviation_calibrator(void)
 {
-    CenterPointDeviationCalibrator<int, 100, 10, 0, 255> calibrator;
+    auto calibrator = new CenterPointDeviationCalibrator<int>(100, 10, 0, 255);
 
     // Test reset function
-    calibrator.reset();
+    calibrator->reset();
 
     // Test update function
-    calibrator.update(50);
-    calibrator.update(75);
-    calibrator.update(25);
+    calibrator->update(50);
+    calibrator->update(75);
+    calibrator->update(25);
 
     // Test calibrate function
-    TEST_ASSERT_EQUAL_INT(255, calibrator.calibrate(100));
-    TEST_ASSERT_EQUAL_INT(191, calibrator.calibrate(75));
-    TEST_ASSERT_EQUAL_INT(63, calibrator.calibrate(50));
-    TEST_ASSERT_EQUAL_INT(0, calibrator.calibrate(25));
-    TEST_ASSERT_EQUAL_INT(0, calibrator.calibrate(0));
+    TEST_ASSERT_EQUAL_INT(255, calibrator->calibrate(100));
+    TEST_ASSERT_EQUAL_INT(191, calibrator->calibrate(75));
+    TEST_ASSERT_EQUAL_INT(63, calibrator->calibrate(50));
+    TEST_ASSERT_EQUAL_INT(0, calibrator->calibrate(25));
+    TEST_ASSERT_EQUAL_INT(0, calibrator->calibrate(0));
 }
 
 void test_fixed_center_point_deviation_calibrator(void)
 {
-    auto calibrator = new FixedCenterPointDeviationCalibrator<uint16_t, 512, 64, 0, 4096>();
+    auto calibrator = new FixedCenterPointDeviationCalibrator<uint16_t>(512, 64, 0, 4096);
 
     // below deviation
     TEST_ASSERT_EQUAL_UINT16(0, calibrator->calibrate(0));

From 2f6cfe1f2abfdb15790625c191d3646f7436111d Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Wed, 7 Feb 2024 22:33:00 +0400
Subject: [PATCH 54/82] refactor(OpenGloves): universal encoder

---
 lib/opengloves/og_constants.hpp               |   7 -
 lib/opengloves/og_ffb.hpp                     |  15 -
 lib/opengloves/og_protocol.hpp                |  79 ----
 lib/opengloves/opengloves/opengloves.cpp      | 113 ++++++
 lib/opengloves/opengloves/opengloves.hpp      | 350 ++++++++++++++++++
 .../senseshift/opengloves/encoding/alpha.cpp  |  79 ----
 .../senseshift/opengloves/encoding/alpha.hpp  |  55 ---
 .../senseshift/opengloves/interface.hpp       |  36 --
 .../senseshift/opengloves/opengloves.hpp      |  18 +
 lib/opengloves/sensor/og_gesture.hpp          |  59 ---
 10 files changed, 481 insertions(+), 330 deletions(-)
 delete mode 100644 lib/opengloves/og_constants.hpp
 delete mode 100644 lib/opengloves/og_ffb.hpp
 delete mode 100644 lib/opengloves/og_protocol.hpp
 create mode 100644 lib/opengloves/opengloves/opengloves.cpp
 create mode 100644 lib/opengloves/opengloves/opengloves.hpp
 delete mode 100644 lib/opengloves/senseshift/opengloves/encoding/alpha.cpp
 delete mode 100644 lib/opengloves/senseshift/opengloves/encoding/alpha.hpp
 delete mode 100644 lib/opengloves/senseshift/opengloves/interface.hpp
 create mode 100644 lib/opengloves/senseshift/opengloves/opengloves.hpp
 delete mode 100644 lib/opengloves/sensor/og_gesture.hpp

diff --git a/lib/opengloves/og_constants.hpp b/lib/opengloves/og_constants.hpp
deleted file mode 100644
index df163940..00000000
--- a/lib/opengloves/og_constants.hpp
+++ /dev/null
@@ -1,7 +0,0 @@
-#pragma once
-
-#define OPENGLOVES_FINGERS_TASK_PRIORITY 1
-
-#define OPENGLOVES_COMM_SERIAL 0x1
-#define OPENGLOVES_COMM_BTSERIAL 0x2
-#define OPENGLOVES_COMM_BLESERIAL 0x3
diff --git a/lib/opengloves/og_ffb.hpp b/lib/opengloves/og_ffb.hpp
deleted file mode 100644
index ca2c5b9d..00000000
--- a/lib/opengloves/og_ffb.hpp
+++ /dev/null
@@ -1,15 +0,0 @@
-#pragma once
-
-#include <senseshift/output/actuator.hpp>
-
-#include <optional>
-
-namespace OpenGloves {
-    struct HandActuators {
-        std::optional<::SenseShift::Output::IActuator<uint16_t>*> thumb = std::nullopt;
-        std::optional<::SenseShift::Output::IActuator<uint16_t>*> index = std::nullopt;
-        std::optional<::SenseShift::Output::IActuator<uint16_t>*> middle = std::nullopt;
-        std::optional<::SenseShift::Output::IActuator<uint16_t>*> ring = std::nullopt;
-        std::optional<::SenseShift::Output::IActuator<uint16_t>*> pinky = std::nullopt;
-    };
-} // namespace OpenGloves
diff --git a/lib/opengloves/og_protocol.hpp b/lib/opengloves/og_protocol.hpp
deleted file mode 100644
index f1849a3b..00000000
--- a/lib/opengloves/og_protocol.hpp
+++ /dev/null
@@ -1,79 +0,0 @@
-#pragma once
-
-#include <cstdint>
-#include <functional>
-#include <vector>
-
-namespace OpenGloves {
-    struct IEncodedInput {
-      public:
-        enum Type : char {
-            THUMB = 'A',
-            INDEX = 'B',
-            MIDDLE = 'C',
-            RING = 'D',
-            PINKY = 'E',
-            JOY_X = 'F',
-            JOY_Y = 'G',
-            JOY_BTN = 'H',
-            TRIGGER = 'I',
-            A_BTN = 'J',
-            B_BTN = 'K',
-            GRAB = 'L',
-            PINCH = 'M',
-            MENU = 'N',
-            CALIBRATE = 'O'
-        };
-
-        IEncodedInput(Type type) : type(type){};
-
-        constexpr Type getType() const { return this->type; }
-
-      protected:
-        Type type;
-    };
-
-    class IStringEncoded : public IEncodedInput {
-      public:
-        IStringEncoded(Type type) : IEncodedInput(type){};
-
-        virtual size_t getEncodedLength() const = 0;
-        virtual size_t encodeString(char* buffer) = 0;
-    };
-
-    class IStringEncodedMemoizedSensor : public IStringEncoded {
-      public:
-        IStringEncodedMemoizedSensor(Type type) : IStringEncoded(type){};
-
-        virtual void init() = 0;
-        virtual void updateValue() = 0;
-    };
-
-    class ICommunication {
-      public:
-        virtual void setup() = 0;
-        virtual void send(std::vector<IStringEncodedMemoizedSensor*>& sensors) = 0;
-        virtual bool hasData() = 0;
-        virtual size_t readCommand(char* buffer, size_t length) = 0;
-    };
-
-    using CommandIndex = uint16_t;
-    enum Command : CommandIndex {
-        ThumbCurl,
-        ThumbSplay,
-
-        IndexCurl,
-        IndexSplay,
-
-        MiddleCurl,
-        MiddleSplay,
-
-        RingCurl,
-        RingSplay,
-
-        PinkyCurl,
-        PinkySplay,
-    };
-
-    using CommandCallback = std::function<void(Command, uint16_t)>;
-} // namespace OpenGloves
diff --git a/lib/opengloves/opengloves/opengloves.cpp b/lib/opengloves/opengloves/opengloves.cpp
new file mode 100644
index 00000000..2752e7fc
--- /dev/null
+++ b/lib/opengloves/opengloves/opengloves.cpp
@@ -0,0 +1,113 @@
+#include "opengloves/opengloves.hpp"
+
+#include <cstdint>
+#include <cstdio>
+#include <variant>
+
+namespace og {
+
+    auto AlphaEncoder::encode_input(const Input& input, char* buffer, size_t length) const -> size_t
+    {
+        if (std::holds_alternative<InputInfoData>(input)) {
+            const auto& info = std::get<InputInfoData>(input);
+
+            const auto& keyFirmwareVersion = AlphaEncoder::INFO_FIRMWARE_VERSION_KEY;
+            const auto& keyDeviceType = AlphaEncoder::INFO_DEVICE_TYPE_KEY;
+            const auto& keyHand = AlphaEncoder::INFO_HAND_KEY;
+
+            return snprintf(
+              buffer,
+              length,
+              "%s%u%s%u%s%u\n",
+              keyFirmwareVersion,
+              info.firmware_version,
+              keyDeviceType,
+              info.device_type,
+              keyHand,
+              info.hand
+            );
+        }
+
+        if (std::holds_alternative<InputPeripheralData>(input)) {
+            const auto peripheral = std::get<InputPeripheralData>(input);
+            auto written = 0;
+
+            const auto& curl = peripheral.curl.fingers;
+#ifdef OG_ENCODE_FAST
+            for (auto i = 0; i < curl.size(); i++) {
+                const auto& finger = curl[i];
+                const auto& finger_curl = finger.curl_total;
+
+                written +=
+                  snprintf(buffer + written, length - written, "%c%.0f", 'A' + i, finger_curl * MAX_ANALOG_VALUE);
+            }
+#else
+            for (auto i = 0; i < curl.size(); i++) {
+                const auto& finger = curl[i];
+                const auto& joints = finger.curl;
+
+                for (auto j = 0; j < joints.size(); j++) {
+                    const auto& joint = joints[j];
+
+                    // skip if joint is 0.0, except total_curl (it is probably not enabled)
+                    if (joint == 0.0F && j != 0) {
+                        continue;
+                    }
+
+                    const auto& jointKey = AlphaEncoder::FINGER_CURL_JOINT_ALPHA_KEY[i][j];
+
+                    written +=
+                      snprintf(buffer + written, length - written, "%s%.0f", jointKey.data(), joint * MAX_ANALOG_VALUE);
+                }
+            }
+
+            const auto& splay = peripheral.splay.fingers;
+            for (auto i = 0; i < splay.size(); i++) {
+                const auto& finger = splay[i];
+
+                if (finger == 0.0F) {
+                    continue;
+                }
+
+                written += snprintf(
+                  buffer + written,
+                  length - written,
+                  "(%cB)%.0f",
+                  AlphaEncoder::FINGER_ALPHA_KEY[i],
+                  finger * MAX_ANALOG_VALUE
+                );
+            }
+#endif
+
+            const auto& buttons = peripheral.buttons;
+            for (auto i = 0; i < buttons.size(); i++) {
+                const auto& button = buttons[i];
+                if (button.press) {
+                    const auto& buttonKey = AlphaEncoder::BUTTON_ALPHA_KEY[i];
+                    written += snprintf(buffer + written, length - written, "%c", buttonKey);
+                }
+            }
+
+            const auto& analog_buttons = peripheral.analog_buttons;
+            for (auto i = 0; i < analog_buttons.size(); i++) {
+                const auto& button = analog_buttons[i];
+                if (button.press) {
+                    const auto& buttonKey = AlphaEncoder::ANALOG_BUTTON_ALPHA_KEY[i];
+                    written += snprintf(buffer + written, length - written, "%c", buttonKey);
+                }
+            }
+
+            // Add newline and null terminator
+            written += snprintf(buffer + written, length - written, "\n");
+
+            return written;
+        }
+
+        return 0;
+    }
+
+    auto AlphaEncoder::parse_output(const char* data, size_t length) const -> Output
+    {
+        return {};
+    }
+} // namespace og
\ No newline at end of file
diff --git a/lib/opengloves/opengloves/opengloves.hpp b/lib/opengloves/opengloves/opengloves.hpp
new file mode 100644
index 00000000..ae8d80aa
--- /dev/null
+++ b/lib/opengloves/opengloves/opengloves.hpp
@@ -0,0 +1,350 @@
+#pragma once
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <map>
+#include <string>
+#include <variant>
+#include <vector>
+
+#ifdef ARDUINO
+#include <WString.h>
+#endif // ARDUINO
+
+#ifdef OG_USE_FROZEN
+#include <frozen/map.h>
+#include <frozen/string.h>
+#endif
+
+#ifndef OG_BUFFER_SIZE
+#define OG_BUFFER_SIZE 256
+#endif // OG_BUFFER_SIZE
+
+namespace og {
+    using HandIndex = std::uint8_t;
+    enum Hand : HandIndex {
+        Hand_Left,
+        Hand_Right,
+    };
+
+    using DeviceTypeIndex = std::uint8_t;
+    enum DeviceType : DeviceTypeIndex {
+        DeviceType_LucidGloves,
+    };
+
+    union InputFingerCurlData {
+        std::array<float, 4> curl; // NOLINT(*-magic-numbers): I'm sure our finger aren't changing anytime soon
+        struct {
+            float curl_total;
+            float curl_joint1;
+            float curl_joint2;
+            float curl_joint3;
+        };
+    };
+
+    template<typename Tp>
+    union InputFingerData {
+        std::array<Tp, 5> fingers; // NOLINT(*-magic-numbers): We aren't going to grow any new fingers soon tbh
+        struct {
+            Tp thumb;
+            Tp index;
+            Tp middle;
+            Tp ring;
+
+            union {
+                Tp pinky;
+                Tp little;
+            };
+        };
+    };
+
+    struct InputJoystickData {
+        float x, y;
+        bool press;
+    };
+
+    struct InputButtonData {
+        bool press;
+        // bool touch;
+    };
+
+    struct InputAnalogButtonData : InputButtonData {
+        float value;
+    };
+
+    /// Input data structure.
+    ///
+    /// I know, it is not the prettiest one, but we need this type of punning to efficiently encode/decode the data
+    struct InputPeripheralData {
+        InputFingerData<InputFingerCurlData> curl;
+        InputFingerData<float> splay;
+
+        InputJoystickData joystick;
+
+        union {
+            std::array<InputButtonData, 4> buttons;
+            struct {
+                InputButtonData button_a;
+                InputButtonData button_b;
+                InputButtonData button_menu;
+                InputButtonData button_calibrate;
+                InputButtonData pinch;
+            };
+        };
+
+        union {
+            std::array<InputAnalogButtonData, 2> analog_buttons;
+            struct {
+                InputAnalogButtonData trigger;
+                InputAnalogButtonData grab;
+            };
+        };
+    };
+
+    struct InputInfoData {
+        Hand hand;
+        DeviceType device_type;
+
+        unsigned int firmware_version;
+    };
+
+    using Input = std::variant<InputInfoData, InputPeripheralData>;
+
+    class Output {};
+
+    class IEncoder {
+      public:
+        [[nodiscard]] virtual auto encode_input(const Input& input, char* buffer, size_t length) const -> size_t = 0;
+
+        [[nodiscard]] auto encode_input(const Input& input) const -> std::string
+        {
+            std::string buffer;
+            buffer.resize(OG_BUFFER_SIZE);
+
+            const auto length = this->encode_input(input, buffer.data(), buffer.length());
+            buffer.resize(length);
+
+            return buffer;
+        }
+
+        [[nodiscard]] virtual auto parse_output(const char* data, size_t length) const -> Output = 0;
+
+        [[nodiscard]] auto parse_output(const std::vector<char>& data) const -> Output
+        {
+            return this->parse_output(data.data(), data.size());
+        }
+
+        [[nodiscard]] auto parse_output(const std::string& data) const -> Output
+        {
+            return this->parse_output(data.data(), data.length());
+        }
+
+#ifdef ARDUINO
+        [[nodiscard]] auto parse_output(const String& data) const -> Output
+        {
+            return this->parse_output(data.c_str(), data.length());
+        }
+#endif // ARDUINO
+    };
+
+    class AlphaEncoder : public IEncoder {
+      public:
+        using CommandIndex = std::uint8_t;
+        enum Command : CommandIndex {
+            FingerThumb_CurlTotal,
+            FingerThumb_Splay,
+            FingerThumb_CurlJoint0,
+            FingerThumb_CurlJoint1,
+            FingerThumb_CurlJoint2,
+            FingerThumb_CurlJoint3 [[maybe_unused]],
+
+            FingerIndex_CurlTotal,
+            FingerIndex_Splay,
+            FingerIndex_CurlJoint0,
+            FingerIndex_CurlJoint1,
+            FingerIndex_CurlJoint2,
+            FingerIndex_CurlJoint3,
+
+            FingerMiddle_CurlTotal,
+            FingerMiddle_Splay,
+            FingerMiddle_CurlJoint0,
+            FingerMiddle_CurlJoint1,
+            FingerMiddle_CurlJoint2,
+            FingerMiddle_CurlJoint3,
+
+            FingerRing_CurlTotal,
+            FingerRing_Splay,
+            FingerRing_CurlJoint0,
+            FingerRing_CurlJoint1,
+            FingerRing_CurlJoint2,
+            FingerRing_CurlJoint3,
+
+            FingerPinky_CurlTotal,
+            FingerPinky_Splay,
+            FingerPinky_CurlJoint0,
+            FingerPinky_CurlJoint1,
+            FingerPinky_CurlJoint2,
+            FingerPinky_CurlJoint3,
+
+            MainJoystick_X,
+            MainJoystick_Y,
+            MainJoystick_Click,
+
+            ButtonA_Click,
+            ButtonB_Click,
+            ButtonMenu_Click,
+            ButtonCalibrate_Click,
+
+            GestureTrigger_Value,
+            GestureTrigger_Click,
+
+            GestureGrab_Click,
+
+            GesturePinch_Click,
+
+            Info,
+            Info_StartStreaming,
+            Info_StopStreaming,
+            Info_FirmwareVersion,
+            Info_DeviceType,
+            Info_Hand,
+
+            Haptics_Frequency,
+            Haptics_Duration,
+            Haptics_Amplitude,
+        };
+
+        inline static constexpr const char* INFO_FIRMWARE_VERSION_KEY = "(ZV)";
+        inline static constexpr const char* INFO_DEVICE_TYPE_KEY = "(ZG)";
+        inline static constexpr const char* INFO_HAND_KEY = "(ZH)";
+
+        inline static constexpr const uint16_t MAX_ANALOG_VALUE = 4096;
+
+#ifdef OG_USE_FROZEN
+        inline static constexpr const auto ALPHA_KEYS_TO_COMMAND = frozen::make_map<frozen::string, Command>
+#else
+        inline static const auto ALPHA_KEYS_TO_COMMAND = std::map<std::string, Command>
+#endif
+
+          ({
+            { "A", Command::FingerThumb_CurlTotal },  // Thumb finger force feedback
+            { "B", Command::FingerIndex_CurlTotal },  // Index finger force feedback
+            { "C", Command::FingerMiddle_CurlTotal }, // Middle finger force feedback
+            { "D", Command::FingerRing_CurlTotal },   // Ring finger force feedback
+            { "E", Command::FingerPinky_CurlTotal },  // Pinky finger force feedback
+
+            { "F", Command::Haptics_Frequency }, //
+            { "G", Command::Haptics_Duration },  //
+            { "H", Command::Haptics_Amplitude }, //
+
+            { "Z", Command::Info },
+          });
+
+#ifdef OG_USE_FROZEN
+        inline static constexpr const auto COMMAND_TO_ALPHA_KEY = frozen::make_map<Command, frozen::string>
+#else
+        inline static const auto COMMAND_TO_ALPHA_KEY = std::map<Command, std::string>
+#endif
+          ({
+            { Command::FingerThumb_CurlTotal, "A" },      // Whole thumb curl
+            { Command::FingerThumb_Splay, "(AB)" },       // Whole thumb splay
+            { Command::FingerThumb_CurlJoint0, "(AAA)" }, //
+            { Command::FingerThumb_CurlJoint1, "(AAB)" }, //
+            { Command::FingerThumb_CurlJoint2, "(AAC)" }, //
+            // { Command::FingerThumb_CurlJoint3, "(AAD)" }, // not used
+
+            { Command::FingerIndex_CurlTotal, "B" },      //
+            { Command::FingerIndex_Splay, "(BB)" },       //
+            { Command::FingerIndex_CurlJoint0, "(BAA)" }, //
+            { Command::FingerIndex_CurlJoint1, "(BAB)" }, //
+            { Command::FingerIndex_CurlJoint2, "(BAC)" }, //
+            { Command::FingerIndex_CurlJoint3, "(BAD)" }, //
+
+            { Command::FingerMiddle_CurlTotal, "C" },      //
+            { Command::FingerMiddle_Splay, "(CB)" },       //
+            { Command::FingerMiddle_CurlJoint0, "(CAA)" }, //
+            { Command::FingerMiddle_CurlJoint1, "(CAB)" }, //
+            { Command::FingerMiddle_CurlJoint2, "(CAC)" }, //
+            { Command::FingerMiddle_CurlJoint3, "(CAD)" }, //
+
+            { Command::FingerRing_CurlTotal, "D" },      //
+            { Command::FingerRing_Splay, "(DB)" },       //
+            { Command::FingerRing_CurlJoint0, "(DAA)" }, //
+            { Command::FingerRing_CurlJoint1, "(DAB)" }, //
+            { Command::FingerRing_CurlJoint2, "(DAC)" }, //
+            { Command::FingerRing_CurlJoint3, "(DAD)" }, //
+
+            { Command::FingerPinky_CurlTotal, "E" },      //
+            { Command::FingerPinky_Splay, "(EB)" },       //
+            { Command::FingerPinky_CurlJoint0, "(EAA)" }, //
+            { Command::FingerPinky_CurlJoint1, "(EAB)" }, //
+            { Command::FingerPinky_CurlJoint2, "(EAC)" }, //
+            { Command::FingerPinky_CurlJoint3, "(EAD)" }, //
+
+            { Command::MainJoystick_X, "F" },     //
+            { Command::MainJoystick_Y, "G" },     //
+            { Command::MainJoystick_Click, "H" }, //
+
+            { Command::GestureTrigger_Click, "I" }, // Binary trigger button/gesture
+
+            { Command::ButtonA_Click, "J" }, //
+            { Command::ButtonB_Click, "K" }, //
+
+            { Command::GestureGrab_Click, "L" },  //
+            { Command::GesturePinch_Click, "M" }, //
+
+            { Command::ButtonMenu_Click, "N" },      //
+            { Command::ButtonCalibrate_Click, "O" }, //
+
+            { Command::GestureTrigger_Value, "P" }, // Analog trigger button/gesture
+
+            { Command::Info, "Z" },
+            { Command::Info_FirmwareVersion, INFO_FIRMWARE_VERSION_KEY },
+            { Command::Info_DeviceType, INFO_DEVICE_TYPE_KEY },
+            { Command::Info_Hand, INFO_HAND_KEY },
+          });
+
+        /// Alpha keys for fingers.
+        /// <b>MUST</b> be in the same order as the `InputFingerData` struct.
+        inline static constexpr const std::array<unsigned char, 5> FINGER_ALPHA_KEY = { {
+          'A', // Thumb
+          'B', // Index
+          'C', // Middle
+          'D', // Ring
+          'E', // Pinky
+        } };
+
+        /// Alpha keys for finger curl joints. Top level is the finger, second level is the joint.
+        /// The top level <b>MUST</b> be in the same order as the `InputFingerData` struct.
+        /// Second level array <b>MUST</b> be in the same order as the `InputFingerCurlData` struct.
+        inline static const std::array<std::array<std::string, 4>, 5> FINGER_CURL_JOINT_ALPHA_KEY = { {
+          { "A", "(AAB)", "(AAC)", "(AAD)" }, // Thumb (total, joint1, joint2, joint3)
+          { "B", "(BAB)", "(BAC)", "(BAD)" }, // Index (total, joint1, joint2, joint3)
+          { "C", "(CAB)", "(CAC)", "(CAD)" }, // Middle (total, joint1, joint2, joint3)
+          { "D", "(DAB)", "(DAC)", "(DAD)" }, // Ring (total, joint1, joint2, joint3)
+          { "E", "(EAB)", "(EAC)", "(EAD)" }, // Pinky (total, joint1, joint2, joint3)
+        } };
+
+        /// Alpha keys for buttons.
+        /// <b>MUST</b> be in the same order as the `InputPeripheralData` struct.
+        inline static constexpr const std::array<unsigned char, 5> BUTTON_ALPHA_KEY = { {
+          'J', // Button A
+          'K', // Button B
+          'N', // Button Menu
+          'O', // Button Calibrate
+          'M', // Button Pinch
+        } };
+
+        /// Alpha keys for analog buttons.
+        /// <b>MUST</b> be in the same order as the `InputPeripheralData` struct.
+        inline static constexpr const std::array<unsigned char, 2> ANALOG_BUTTON_ALPHA_KEY = { {
+          'P', // Trigger
+          'I', // Grab
+        } };
+
+        auto encode_input(const Input& input, char* buffer, size_t length) const -> size_t override;
+
+        auto parse_output(const char* data, size_t length) const -> Output override;
+    };
+} // namespace og
\ No newline at end of file
diff --git a/lib/opengloves/senseshift/opengloves/encoding/alpha.cpp b/lib/opengloves/senseshift/opengloves/encoding/alpha.cpp
deleted file mode 100644
index c9dc738d..00000000
--- a/lib/opengloves/senseshift/opengloves/encoding/alpha.cpp
+++ /dev/null
@@ -1,79 +0,0 @@
-#include "senseshift/opengloves/encoding/alpha.hpp"
-
-#include <string.h>
-
-namespace SenseShift::OpenGloves {
-    size_t AlphaEncodingService::serialize(
-      const std::vector<::OpenGloves::IStringEncodedMemoizedSensor*>& sensors, char* buffer
-    ) const
-    {
-        buffer[0] = '\0';
-        size_t offset = 0;
-
-        for (size_t i = 0; i < sensors.size(); i++) {
-            auto* sensor = sensors[i];
-            offset += sensor->encodeString(buffer + offset);
-        }
-
-        buffer[offset++] = '\n';
-        buffer[offset] = '\0';
-
-        return offset;
-    }
-
-    bool AlphaEncodingService::deserialize(
-      const char* buffer, const size_t length, std::map<::OpenGloves::Command, uint16_t>& commands
-    ) const
-    {
-        std::string input_string(buffer, length);
-
-        size_t start = 0; // Start of the current command
-        for (size_t i = 0; i < input_string.length(); i++) {
-            const char ch = input_string[i];
-
-            // Start a new command if the character is non-numeric or an opening parenthesis
-            // and previous character is a numeric character
-            if ((!(isdigit(ch)) || ch == '(') && i > 0 && isdigit(input_string[i - 1])) {
-                AlphaEncodingService::splitCommand(input_string, start, i, commands);
-                start = i;
-            }
-        }
-
-        AlphaEncodingService::splitCommand(input_string, start, input_string.size(), commands);
-
-        return true;
-    }
-
-    void AlphaEncodingService::splitCommand(
-      const std::string& input_string, size_t start, size_t end, std::map<Command, uint16_t>& commands
-    )
-    {
-        std::string current_command = input_string.substr(start, end - start);
-
-        if (current_command.empty()) {
-            return;
-        }
-
-        // Split the command into prefix and number
-        size_t split_index = current_command.find_last_not_of(valueSymbols.data()) + 1;
-
-        if (split_index >= current_command.size()) {
-            log_w("Invalid command: %s", current_command.c_str());
-            return;
-        }
-
-        std::string prefix = current_command.substr(0, split_index);
-        int number = std::stoi(current_command.substr(split_index));
-
-        // Check if the command prefix is in commandMap
-        auto it = commandMap.find(prefix);
-        if (it == commandMap.end()) {
-            log_w("Unknown command prefix: %s", prefix.c_str());
-            return;
-        }
-
-        // Look up the Command enum value for the prefix in the commandMap
-        Command command = it->second;
-        commands[command] = number;
-    }
-} // namespace SenseShift::OpenGloves
diff --git a/lib/opengloves/senseshift/opengloves/encoding/alpha.hpp b/lib/opengloves/senseshift/opengloves/encoding/alpha.hpp
deleted file mode 100644
index a8f81f7a..00000000
--- a/lib/opengloves/senseshift/opengloves/encoding/alpha.hpp
+++ /dev/null
@@ -1,55 +0,0 @@
-#pragma once
-
-#include <algorithm>
-#include <cstdint>
-#include <functional>
-#include <map>
-#include <string>
-#include <vector>
-
-#include <frozen/map.h>
-#include <frozen/string.h>
-#include <frozen/unordered_map.h>
-
-#include <og_protocol.hpp>
-#include <senseshift/core/logging.hpp>
-#include <senseshift/opengloves/interface.hpp>
-
-#define SENSESHIFT_OPENGLOVES_ALPHA_ENCODING_BUFFER_SIZE 256
-
-namespace SenseShift::OpenGloves {
-    class AlphaEncodingService : public IEncoding {
-        using Command = ::OpenGloves::Command;
-
-      public:
-        inline static constexpr frozen::string valueSymbols = "0123456789";
-        inline static const auto commandMap = frozen::make_map<std::string, Command>({
-          // clang-format off
-            { "A", Command::ThumbCurl },
-            { "(AB)", Command::ThumbSplay },
-            { "B", Command::IndexCurl },
-            { "(BB)", Command::IndexSplay },
-            { "C", Command::MiddleCurl },
-            { "(CB)", Command::MiddleSplay },
-            { "D", Command::RingCurl },
-            { "(DB)", Command::RingSplay },
-            { "E", Command::PinkyCurl },
-            { "(EB)", Command::PinkySplay },
-          // clang-format on
-        });
-
-        AlphaEncodingService(){};
-
-        virtual size_t serialize(const std::vector<::OpenGloves::IStringEncodedMemoizedSensor*>& sensors, char* buffer)
-          const override;
-
-        virtual bool deserialize(
-          const char* buffer, const size_t length, std::map<::OpenGloves::Command, uint16_t>& commands
-        ) const override;
-
-      private:
-        static void splitCommand(
-          const std::string& input_string, size_t start, size_t end, std::map<Command, uint16_t>& commands
-        );
-    };
-} // namespace SenseShift::OpenGloves
diff --git a/lib/opengloves/senseshift/opengloves/interface.hpp b/lib/opengloves/senseshift/opengloves/interface.hpp
deleted file mode 100644
index ba7ba8e0..00000000
--- a/lib/opengloves/senseshift/opengloves/interface.hpp
+++ /dev/null
@@ -1,36 +0,0 @@
-#pragma once
-
-#include <cstddef>
-#include <cstdint>
-#include <vector>
-
-#include <senseshift/buffer.hpp>
-
-#include "og_protocol.hpp"
-
-namespace SenseShift::OpenGloves {
-    struct ITransport {
-        virtual void setup(){};
-        virtual auto send(const char* buffer, size_t length) -> size_t = 0;
-        virtual auto hasData() -> bool = 0;
-        virtual auto read(char* buffer, size_t length) -> size_t = 0;
-    };
-
-    struct IEncoding {
-        /// \param [in] sensors
-        /// \param [out] buffer
-        virtual auto serialize(
-          const std::vector<::OpenGloves::IStringEncodedMemoizedSensor*>& sensors,
-          char* buffer
-        ) const -> size_t = 0;
-
-        /// \param [in] buffer
-        /// \param [in] length
-        /// \param [out] commands
-        virtual auto deserialize(
-          const char* buffer,
-          size_t length,
-          std::map<::OpenGloves::Command, uint16_t>& commands
-        ) const -> bool = 0;
-    };
-} // namespace SenseShift::OpenGloves
diff --git a/lib/opengloves/senseshift/opengloves/opengloves.hpp b/lib/opengloves/senseshift/opengloves/opengloves.hpp
new file mode 100644
index 00000000..267711c3
--- /dev/null
+++ b/lib/opengloves/senseshift/opengloves/opengloves.hpp
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <cstddef>
+#include <cstdint>
+#include <map>
+#include <optional>
+#include <string>
+
+#include <opengloves/opengloves.hpp>
+
+namespace SenseShift::OpenGloves {
+    class ITransport : public IInitializable {
+      public:
+        virtual auto send(const char* buffer, size_t length) -> size_t = 0;
+        virtual auto hasData() -> bool = 0;
+        virtual auto read(char* buffer, size_t length) -> size_t = 0;
+    };
+} // namespace SenseShift::OpenGloves
diff --git a/lib/opengloves/sensor/og_gesture.hpp b/lib/opengloves/sensor/og_gesture.hpp
deleted file mode 100644
index c64365c1..00000000
--- a/lib/opengloves/sensor/og_gesture.hpp
+++ /dev/null
@@ -1,59 +0,0 @@
-#pragma once
-
-#include <sensor/og_finger.hpp>
-
-namespace OpenGloves {
-    class Gesture : public SenseShift::Input::ISimpleSensor<bool> {};
-
-    class GrabGesture : public Gesture {
-      private:
-        ICurl& index;
-        ICurl& middle;
-        ICurl& ring;
-        ICurl& pinky;
-        uint16_t threshold;
-
-      public:
-        GrabGesture(ICurl& index, ICurl& middle, ICurl& ring, ICurl& pinky, uint16_t threshold) :
-          index(index), middle(middle), ring(ring), pinky(pinky), threshold(threshold){};
-
-        void init() override{};
-
-        [[nodiscard]] auto getValue() -> bool override
-        {
-            return this->index.getCurl() > this->threshold && this->middle.getCurl() > this->threshold
-                   && this->ring.getCurl() > this->threshold && this->pinky.getCurl() > this->threshold;
-        }
-    };
-
-    class TriggerGesture : public Gesture {
-      private:
-        ICurl& index;
-        uint16_t threshold;
-
-      public:
-        TriggerGesture(ICurl& index, uint16_t threshold) : index(index), threshold(threshold){};
-
-        void init() override{};
-
-        [[nodiscard]] auto getValue() -> bool override { return this->index.getCurl() > this->threshold; }
-    };
-
-    class PinchGesture : public Gesture {
-      private:
-        ICurl& index;
-        ICurl& thumb;
-        uint16_t threshold;
-
-      public:
-        PinchGesture(ICurl& index, ICurl& thumb, const uint16_t threshold) :
-          index(index), thumb(thumb), threshold(threshold){};
-
-        void init() override{};
-
-        [[nodiscard]] auto getValue() -> bool override
-        {
-            return this->index.getCurl() > this->threshold && this->thumb.getCurl() > this->threshold;
-        }
-    };
-} // namespace OpenGloves

From d1059009e6a8b222efb898bac2bf78c10394c667 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Thu, 8 Feb 2024 13:47:06 +0400
Subject: [PATCH 55/82] refactor(OpenGloves): reuse same structure for data and
 source sensors

---
 .../mode_configs/opengloves/opengloves.cpp    | 225 +++++--------
 ini/opengloves-indexer.ini                    | 304 +++++++++---------
 ini/opengloves-lucidgloves.ini                | 199 ++++++------
 .../senseshift/body/hands/hands_interface.hpp |   5 +-
 .../senseshift/body/hands/input/gesture.hpp   |   3 +-
 .../body/hands/input/total_curl.hpp           |  33 +-
 lib/io/senseshift/input/sensor.hpp            |  87 ++---
 lib/opengloves/opengloves/opengloves.cpp      |   2 +-
 lib/opengloves/opengloves/opengloves.hpp      | 101 +++---
 .../senseshift/opengloves/autoconfig.hpp      | 147 +++++----
 .../senseshift/opengloves/opengloves.hpp      |   2 +
 lib/opengloves/sensor/og_finger.hpp           | 156 ---------
 lib/opengloves/sensor/og_sensor.hpp           |  45 ---
 .../opengloves/transport/stream.hpp           |  11 +-
 lib/opengloves_task/opengloves_task.hpp       |   7 +-
 15 files changed, 538 insertions(+), 789 deletions(-)
 delete mode 100644 lib/opengloves/sensor/og_finger.hpp
 delete mode 100644 lib/opengloves/sensor/og_sensor.hpp

diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp
index bded27d3..6666dc3f 100644
--- a/firmware/mode_configs/opengloves/opengloves.cpp
+++ b/firmware/mode_configs/opengloves/opengloves.cpp
@@ -1,225 +1,154 @@
-#include <og_constants.hpp>
-#include <opengloves_task.hpp>
+#include "senseshift/body/hands/input/gesture.hpp"
+#include "senseshift/body/hands/input/total_curl.hpp"
 #include <senseshift/arduino/input/sensor/analog.hpp>
-#include <senseshift/arduino/input/sensor/binary.hpp>
-#include <senseshift/arduino/output/actuator/servo.hpp>
-#include <senseshift/calibration.hpp>
+#include <senseshift/arduino/input/sensor/digital.hpp>
+#include <senseshift/body/hands/input/gesture.hpp>
+#include <senseshift/input/calibration.hpp>
+#include <senseshift/input/filter.hpp>
 #include <senseshift/input/sensor.hpp>
-#include <senseshift/input/sensor/joystick.hpp>
 #include <senseshift/opengloves/autoconfig.hpp>
-#include <senseshift/opengloves/transport/stream.hpp>
 #include <senseshift/utility.hpp>
-#include <sensor/og_finger.hpp>
-#include <sensor/og_gesture.hpp>
 
-using namespace OpenGloves;
-using namespace SenseShift::OpenGloves;
+using namespace ::SenseShift::Input;
+using namespace ::SenseShift::Arduino::Input;
+using namespace ::SenseShift::Body::Hands::Input;
+using namespace ::SenseShift::OpenGloves;
 
-HandSensors handSensors = {
+InputSensors input_sensors;
+
+void setupMode()
+{
 #if FINGER_THUMB_SPLAY
-    .thumb = FINGER_SPLAY_CLASS(
-      IEncodedInput::Type::THUMB,
+    DEFINE_FINGER_SPLAY(
+      thumb,
       PIN_FINGER_THUMB,
       FINGER_THUMB_INVERT,
       CALIBRATION_CURL,
       PIN_FINGER_THUMB_SPLAY,
       FINGER_THUMB_SPLAY_INVERT,
       CALIBRATION_SPLAY
-    ),
+    );
 #elif FINGER_THUMB_ENABLED
-    .thumb = FINGER_CLASS(IEncodedInput::Type::THUMB, PIN_FINGER_THUMB, FINGER_THUMB_INVERT, CALIBRATION_CURL),
+    DEFINE_FINGER(thumb_curl, PIN_FINGER_THUMB, FINGER_THUMB_INVERT, CALIBRATION_CURL);
+    input_sensors.curl.thumb.curl_total = thumb_curl_sensor;
 #endif
 
 #if FINGER_INDEX_SPLAY
-    .index = FINGER_SPLAY_CLASS(
-      IEncodedInput::Type::INDEX,
+    DEFINE_FINGER_SPLAY(
+      index,
       PIN_FINGER_INDEX,
       FINGER_INDEX_INVERT,
       CALIBRATION_CURL,
       PIN_FINGER_INDEX_SPLAY,
       FINGER_INDEX_SPLAY_INVERT,
       CALIBRATION_SPLAY
-    ),
+    );
 #elif FINGER_INDEX_ENABLED
-    .index = FINGER_CLASS(IEncodedInput::Type::INDEX, PIN_FINGER_INDEX, FINGER_INDEX_INVERT, CALIBRATION_CURL),
+    DEFINE_FINGER(index_curl, PIN_FINGER_INDEX, FINGER_INDEX_INVERT, CALIBRATION_CURL);
+    input_sensors.curl.index.curl_total = index_curl_sensor;
 #endif
 
 #if FINGER_MIDDLE_SPLAY
-    .middle = FINGER_SPLAY_CLASS(
-      IEncodedInput::Type::MIDDLE,
+    DEFINE_FINGER_SPLAY(
+      middle,
       PIN_FINGER_MIDDLE,
       FINGER_MIDDLE_INVERT,
       CALIBRATION_CURL,
       PIN_FINGER_MIDDLE_SPLAY,
       FINGER_MIDDLE_SPLAY_INVERT,
       CALIBRATION_SPLAY
-    ),
+    );
 #elif FINGER_MIDDLE_ENABLED
-    .middle = FINGER_CLASS(IEncodedInput::Type::MIDDLE, PIN_FINGER_MIDDLE, FINGER_MIDDLE_INVERT, CALIBRATION_CURL),
+    DEFINE_FINGER(middle_curl, PIN_FINGER_MIDDLE, FINGER_MIDDLE_INVERT, CALIBRATION_CURL);
+    input_sensors.curl.middle.curl_total = middle_curl_sensor;
 #endif
 
 #if FINGER_RING_SPLAY
-    .ring = FINGER_SPLAY_CLASS(
-      IEncodedInput::Type::RING,
+    DEFINE_FINGER_SPLAY(
+      ring,
       PIN_FINGER_RING,
       FINGER_RING_INVERT,
       CALIBRATION_CURL,
       PIN_FINGER_RING_SPLAY,
       FINGER_RING_SPLAY_INVERT,
       CALIBRATION_SPLAY
-    ),
+    );
 #elif FINGER_RING_ENABLED
-    .ring = FINGER_CLASS(IEncodedInput::Type::RING, PIN_FINGER_RING, FINGER_RING_INVERT, CALIBRATION_CURL),
+    DEFINE_FINGER(ring_curl, PIN_FINGER_RING, FINGER_RING_INVERT, CALIBRATION_CURL);
+    input_sensors.curl.ring.curl_total = ring_curl_sensor;
 #endif
 
 #if FINGER_PINKY_SPLAY
-    .pinky = FINGER_SPLAY_CLASS(
-      IEncodedInput::Type::PINKY,
+    DEFINE_FINGER_SPLAY(
+      pinky,
       PIN_FINGER_PINKY,
       FINGER_PINKY_INVERT,
       CALIBRATION_CURL,
       PIN_FINGER_PINKY_SPLAY,
       FINGER_PINKY_SPLAY_INVERT,
       CALIBRATION_SPLAY
-    ),
+    );
 #elif FINGER_PINKY_ENABLED
-    .pinky = FINGER_CLASS(IEncodedInput::Type::PINKY, PIN_FINGER_PINKY, FINGER_PINKY_INVERT, CALIBRATION_CURL),
+    DEFINE_FINGER(pinky_curl, PIN_FINGER_PINKY, FINGER_PINKY_INVERT, CALIBRATION_CURL);
+    input_sensors.curl.pinky.curl_total = pinky_curl_sensor;
 #endif
-};
 
-#if BUTTON_CALIBRATE_ENABLED
-std::optional<StringEncodedMemoizedSensor<bool>> calibrateButton =
-  BUTTON_CLASS(IEncodedInput::Type::CALIBRATE, PIN_BUTTON_CALIBRATE, BUTTON_CALIBRATE_INVERT);
-#else
-std::optional<StringEncodedMemoizedSensor<bool>> calibrateButton = std::nullopt;
+#if JOYSTICK_ENABLED
+    DEFINE_JOYSTICK_AXIS(joystick_x, PIN_JOYSTICK_X, JOYSTICK_X_INVERT, JOYSTICK_DEADZONE);
+    DEFINE_JOYSTICK_AXIS(joystick_y, PIN_JOYSTICK_Y, JOYSTICK_Y_INVERT, JOYSTICK_DEADZONE);
+
+    input_sensors.joystick.x = joystick_x_sensor;
+    input_sensors.joystick.y = joystick_y_sensor;
 #endif
 
-std::vector<StringEncodedMemoizedSensor<bool>*> buttons = std::vector<StringEncodedMemoizedSensor<bool>*>
-{
-    // https://github.com/llvm/llvm-project/issues/63776
-    // clang-format off
 #if BUTTON_A_ENABLED
-    new BUTTON_CLASS(IEncodedInput::Type::A_BTN, PIN_BUTTON_A, BUTTON_A_INVERT),
+    auto* button_a = new BUTTON_CLASS(PIN_BUTTON_A, BUTTON_A_INVERT);
+    input_sensors.button_a.press = button_a;
 #endif
-
 #if BUTTON_B_ENABLED
-    new BUTTON_CLASS(IEncodedInput::Type::B_BTN, PIN_BUTTON_B, BUTTON_B_INVERT),
+    auto* button_b = new BUTTON_CLASS(PIN_BUTTON_B, BUTTON_B_INVERT);
+    input_sensors.button_b.press = button_b;
 #endif
-
 #if BUTTON_JOYSTICK_ENABLED
-    new BUTTON_CLASS(IEncodedInput::Type::JOY_BTN, PIN_BUTTON_JOYSTICK, BUTTON_JOYSTICK_INVERT),
+    auto* button_joystick = new BUTTON_CLASS(PIN_BUTTON_JOYSTICK, BUTTON_JOYSTICK_INVERT);
+    input_sensors.joystick.press = button_joystick;
 #endif
-
 #if BUTTON_MENU_ENABLED
-    new BUTTON_CLASS(IEncodedInput::Type::MENU, PIN_BUTTON_MENU, BUTTON_MENU_INVERT),
+    auto* button_menu = new BUTTON_CLASS(PIN_BUTTON_MENU, BUTTON_MENU_INVERT);
+#endif
+#if BUTTON_CALIBRATE_ENABLED
+    auto* button_calibrate = new BUTTON_CLASS(PIN_BUTTON_CALIBRATE, BUTTON_CALIBRATE_INVERT);
+    input_sensors.button_calibrate.press = button_calibrate;
 #endif
 
-#if GESTURE_TRIGGER_ENABLED && FINGER_INDEX_ENABLED
-    new GESTURE_CLASS(
-      IEncodedInput::Type::TRIGGER,
-      new TriggerGesture(handSensors.index.value(), GESTURE_TRIGGER_THRESHOLD)
-    ),
+#if GESTURE_TRIGGER_ENABLED
+    auto* trigger = new AnalogThresholdSensor(index_curl_sensor, GESTURE_TRIGGER_THRESHOLD);
+    input_sensors.trigger.press = trigger;
 #elif BUTTON_TRIGGER_ENABLED
-    new BUTTON_CLASS(IEncodedInput::Type::TRIGGER, PIN_BUTTON_TRIGGER, BUTTON_TRIGGER_INVERT),
+    auto trigger = new BUTTON_CLASS(PIN_BUTTON_TRIGGER, BUTTON_TRIGGER_INVERT);
 #endif
 
-#if GESTURE_GRAB_ENABLED && FINGER_INDEX_ENABLED && FINGER_MIDDLE_ENABLED && FINGER_RING_ENABLED && FINGER_PINKY_ENABLED
-    new GESTURE_CLASS(
-      IEncodedInput::Type::GRAB,
-      new GrabGesture(
-        handSensors.index.value(),
-        handSensors.middle.value(),
-        handSensors.ring.value(),
-        handSensors.pinky.value(),
-        GESTURE_GRAB_THRESHOLD
-      )
-    ),
+#if GESTURE_GRAB_ENABLED
+    auto* grab = new GrabGesture(
+      GrabGesture::Fingers{ .index = index_curl_sensor,
+                            .middle = middle_curl_sensor,
+                            .ring = ring_curl_sensor,
+                            .pinky = pinky_curl_sensor },
+      GESTURE_GRAB_THRESHOLD
+    );
+    input_sensors.grab.press = grab;
 #elif BUTTON_GRAB_ENABLED
-    new BUTTON_CLASS(IEncodedInput::Type::GRAB, PIN_BUTTON_GRAB, BUTTON_GRAB_INVERT),
+    auto* grab = new BUTTON_CLASS(PIN_BUTTON_GRAB, BUTTON_GRAB_INVERT);
 #endif
 
-#if GESTURE_PINCH_ENABLED && FINGER_THUMB_ENABLED && FINGER_INDEX_ENABLED
-    new GESTURE_CLASS(
-      IEncodedInput::Type::PINCH,
-      new PinchGesture(handSensors.thumb.value(), handSensors.index.value(), GESTURE_PINCH_THRESHOLD)
-    ),
-#elif BUTTON_PINCH_ENABLED
-    new BUTTON_CLASS(IEncodedInput::Type::PINCH, PIN_BUTTON_PINCH, BUTTON_PINCH_INVERT),
-#endif
-    // clang-format on
-};
-
-std::vector<StringEncodedMemoizedSensor<uint16_t>*> joysticks = {
-#if JOYSTICK_ENABLED
-    new JOYSTICK_CLASS(IEncodedInput::Type::JOY_X, PIN_JOYSTICK_X, JOYSTICK_X_INVERT, JOYSTICK_DEADZONE),
-    new JOYSTICK_CLASS(IEncodedInput::Type::JOY_Y, PIN_JOYSTICK_Y, JOYSTICK_Y_INVERT, JOYSTICK_DEADZONE),
-#endif
-    // todo: get joystick movement from Thumb finger movement?
-};
-
-std::vector<IStringEncodedMemoizedSensor*> otherSensors = std::vector<IStringEncodedMemoizedSensor*>();
-
-OpenGlovesTrackingTaskConfig config =
-  OpenGlovesTrackingTaskConfig(UPDATE_RATE, CALIBRATION_DURATION, CALIBRATION_ALWAYS_CALIBRATE);
-OpenGlovesTrackingTask* trackingTask;
-
-#if FFB_ENABLED
-HandActuators handActuators = {
-#if FFB_THUMB_ENABLED
-    .thumb = new SenseShift::Arduino::Output::ActuatorServo(PIN_FFB_THUMB),
-#endif
-
-#if FFB_INDEX_ENABLED
-    .index = new SenseShift::Arduino::Output::ActuatorServo(PIN_FFB_INDEX),
-#endif
-
-#if FFB_MIDDLE_ENABLED
-    .middle = new SenseShift::Arduino::Output::ActuatorServo(PIN_FFB_MIDDLE),
-#endif
-
-#if FFB_RING_ENABLED
-    .ring = new SenseShift::Arduino::Output::ActuatorServo(PIN_FFB_RING),
-#endif
-
-#if FFB_PINKY_ENABLED
-    .pinky = new SenseShift::Arduino::Output::ActuatorServo(PIN_FFB_PINKY),
-#endif
-};
-OpenGlovesForceFeedbackTask* ffbTask;
-#endif
-
-void setupMode()
-{
-    auto* communication = AutoConfig::setupTransport();
-
-    trackingTask = new OpenGlovesTrackingTask(
-      config,
-      *communication,
-      handSensors,
-      buttons,
-      joysticks,
-      otherSensors,
-      calibrateButton,
-      {
-        .name = "OpenGlovesSensorTask",
-        .stackDepth = 8192,
-        .priority = OPENGLOVES_FINGERS_TASK_PRIORITY,
-      }
+#if GESTURE_PINCH_ENABLED
+    auto* pinch = new PinchGesture(
+      PinchGesture::Fingers{ .thumb = thumb_curl_sensor, .index = index_curl_sensor },
+      GESTURE_PINCH_THRESHOLD
     );
-    trackingTask->begin();
-
-#if FFB_ENABLED
-    ffbTask = new OpenGlovesForceFeedbackTask(
-      *communication,
-      handActuators,
-      UPDATE_RATE,
-      {
-        .name = "OpenGlovesForceFeedbackTask",
-        .stackDepth = 8192,
-        .priority = OPENGLOVES_FINGERS_TASK_PRIORITY,
-      }
-    );
-    ffbTask->begin();
+    input_sensors.pinch.press = pinch;
+#elif BUTTON_PINCH_ENABLED
+    auto* pinch = new BUTTON_CLASS(PIN_BUTTON_PINCH, BUTTON_PINCH_INVERT);
 #endif
 }
 
diff --git a/ini/opengloves-indexer.ini b/ini/opengloves-indexer.ini
index 7d5cd163..e9e8f5a8 100644
--- a/ini/opengloves-indexer.ini
+++ b/ini/opengloves-indexer.ini
@@ -3,173 +3,173 @@
 ; Wiring Diagram: https://github.com/Valsvirtuals/Indexer/wiki/wiring-and-pinout
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 [env:indexer-c]
-platform			= ${opengloves.platform}
-platform_packages	= ${opengloves.platform_packages}
-framework			= ${opengloves.framework}
-board				= wemos_d1_mini32
-upload_speed		= ${opengloves.upload_speed}
-monitor_speed		= ${opengloves.monitor_speed}
-
-build_flags 		= ${opengloves.build_flags}
-	; Pins configuration
-	; Comment out to disable
-	-D PIN_FINGER_THUMB=25
-	-D PIN_FINGER_INDEX=14
-	-D PIN_FINGER_MIDDLE=33
-	-D PIN_FINGER_RING=39
-	-D PIN_FINGER_PINKY=36
-
-	-D PIN_JOYSTICK_X=12
-	-D PIN_JOYSTICK_Y=4
-	-D PIN_BUTTON_JOYSTICK=0
-
-	-D PIN_BUTTON_A=2
-	-D PIN_BUTTON_B=11
-	; -D PIN_BUTTON_MENU=5
-	-D PIN_BUTTON_CALIBRATE=27
-	; -D PIN_BUTTON_TRIGGER=19 ; unused if GESTURE_TRIGGER is true
-	; -D PIN_BUTTON_GRAB=18 ; unused if GESTURE_GRAB is true
-	; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
-
-build_unflags 		= ${opengloves.build_unflags}
-build_src_filter	= ${opengloves.build_src_filter}
-	+<mode_configs/opengloves/opengloves.cpp>
-lib_deps    		= ${opengloves.lib_deps}
+platform = ${opengloves.platform}
+platform_packages = ${opengloves.platform_packages}
+framework = ${opengloves.framework}
+board = wemos_d1_mini32
+upload_speed = ${opengloves.upload_speed}
+monitor_speed = ${opengloves.monitor_speed}
+
+build_flags = ${opengloves.build_flags} -D OG_ENCODE_FAST
+              ; Pins configuration
+              ; Comment out to disable
+              -D PIN_FINGER_THUMB=25
+              -D PIN_FINGER_INDEX=14
+              -D PIN_FINGER_MIDDLE=33
+              -D PIN_FINGER_RING=39
+              -D PIN_FINGER_PINKY=36
+
+              -D PIN_JOYSTICK_X=12
+              -D PIN_JOYSTICK_Y=4
+              -D PIN_BUTTON_JOYSTICK=0
+
+              -D PIN_BUTTON_A=2
+              -D PIN_BUTTON_B=11
+              ; -D PIN_BUTTON_MENU=5
+              -D PIN_BUTTON_CALIBRATE=27
+; -D PIN_BUTTON_TRIGGER=19 ; unused if GESTURE_TRIGGER is true
+; -D PIN_BUTTON_GRAB=18 ; unused if GESTURE_GRAB is true
+; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
+
+build_unflags = ${opengloves.build_unflags}
+build_src_filter = ${opengloves.build_src_filter}
+                   +<mode_configs/opengloves/opengloves.cpp>
+lib_deps = ${opengloves.lib_deps}
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ; Indexer CF
 ; Wiring Diagram: https://github.com/Valsvirtuals/Indexer/wiki/wiring-and-pinout
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 [env:indexer-cf]
-platform			= ${opengloves.platform}
-platform_packages	= ${opengloves.platform_packages}
-framework			= ${opengloves.framework}
-board				= wemos_d1_mini32
-upload_speed		= ${opengloves.upload_speed}
-monitor_speed		= ${opengloves.monitor_speed}
-
-build_flags 		= ${opengloves.build_flags}
-	; Pins configuration
-	; Comment out to disable
-	-D PIN_FINGER_THUMB=25
-	-D PIN_FINGER_INDEX=14
-	-D PIN_FINGER_MIDDLE=33
-	-D PIN_FINGER_RING=39
-	-D PIN_FINGER_PINKY=36
-
-	-D PIN_JOYSTICK_X=12
-	-D PIN_JOYSTICK_Y=4
-	-D PIN_BUTTON_JOYSTICK=0
-
-	-D PIN_BUTTON_A=2
-	-D PIN_BUTTON_B=11
-	; -D PIN_BUTTON_MENU=5
-	-D PIN_BUTTON_CALIBRATE=27
-	; -D PIN_BUTTON_TRIGGER=19 ; unused if GESTURE_TRIGGER is true
-	; -D PIN_BUTTON_GRAB=18 ; unused if GESTURE_GRAB is true
-	; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
-
-	-D PIN_FFB_THUMB=16
-	-D PIN_FFB_INDEX=17
-	-D PIN_FFB_MIDDLE=21
-	-D PIN_FFB_RING=22
-	-D PIN_FFB_PINKY=1
-
-build_unflags 		= ${opengloves.build_unflags}
-build_src_filter	= ${opengloves.build_src_filter}
-	+<mode_configs/opengloves/opengloves.cpp>
-lib_deps    		= ${opengloves.lib_deps}
+platform = ${opengloves.platform}
+platform_packages = ${opengloves.platform_packages}
+framework = ${opengloves.framework}
+board = wemos_d1_mini32
+upload_speed = ${opengloves.upload_speed}
+monitor_speed = ${opengloves.monitor_speed}
+
+build_flags = ${opengloves.build_flags} -D OG_ENCODE_FAST
+              ; Pins configuration
+              ; Comment out to disable
+              -D PIN_FINGER_THUMB=25
+              -D PIN_FINGER_INDEX=14
+              -D PIN_FINGER_MIDDLE=33
+              -D PIN_FINGER_RING=39
+              -D PIN_FINGER_PINKY=36
+
+              -D PIN_JOYSTICK_X=12
+              -D PIN_JOYSTICK_Y=4
+              -D PIN_BUTTON_JOYSTICK=0
+
+              -D PIN_BUTTON_A=2
+              -D PIN_BUTTON_B=11
+              ; -D PIN_BUTTON_MENU=5
+              -D PIN_BUTTON_CALIBRATE=27
+              ; -D PIN_BUTTON_TRIGGER=19 ; unused if GESTURE_TRIGGER is true
+              ; -D PIN_BUTTON_GRAB=18 ; unused if GESTURE_GRAB is true
+              ; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
+
+              -D PIN_FFB_THUMB=16
+              -D PIN_FFB_INDEX=17
+              -D PIN_FFB_MIDDLE=21
+              -D PIN_FFB_RING=22
+              -D PIN_FFB_PINKY=1
+
+build_unflags = ${opengloves.build_unflags}
+build_src_filter = ${opengloves.build_src_filter}
+                   +<mode_configs/opengloves/opengloves.cpp>
+lib_deps = ${opengloves.lib_deps}
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ; Indexer CS
 ; Wiring Diagram: https://github.com/Valsvirtuals/Indexer/wiki/wiring-and-pinout
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 [env:indexer-cs]
-platform			= ${opengloves.platform}
-platform_packages	= ${opengloves.platform_packages}
-framework			= ${opengloves.framework}
-board				= wemos_d1_mini32
-upload_speed		= ${opengloves.upload_speed}
-monitor_speed		= ${opengloves.monitor_speed}
-
-build_flags 		= ${opengloves.build_flags}
-	; Pins configuration
-	; Comment out to disable
-	-D PIN_FINGER_THUMB=25
-	-D PIN_FINGER_INDEX=14
-	-D PIN_FINGER_MIDDLE=33
-	-D PIN_FINGER_RING=39
-	-D PIN_FINGER_PINKY=36
-
-	-D PIN_FINGER_THUMB_SPLAY=32
-	-D PIN_FINGER_INDEX_SPLAY=13
-	-D PIN_FINGER_MIDDLE_SPLAY=34
-	-D PIN_FINGER_RING_SPLAY=35
-	-D PIN_FINGER_PINKY_SPLAY=26
-
-	-D PIN_JOYSTICK_X=12
-	-D PIN_JOYSTICK_Y=4
-	-D PIN_BUTTON_JOYSTICK=0
-
-	-D PIN_BUTTON_A=2
-	-D PIN_BUTTON_B=11
-	; -D PIN_BUTTON_MENU=5
-	-D PIN_BUTTON_CALIBRATE=27
-	; -D PIN_BUTTON_TRIGGER=19 ; unused if GESTURE_TRIGGER is true
-	; -D PIN_BUTTON_GRAB=18 ; unused if GESTURE_GRAB is true
-	; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
-
-build_unflags 		= ${opengloves.build_unflags}
-build_src_filter	= ${opengloves.build_src_filter}
-	+<mode_configs/opengloves/opengloves.cpp>
-lib_deps    		= ${opengloves.lib_deps}
+platform = ${opengloves.platform}
+platform_packages = ${opengloves.platform_packages}
+framework = ${opengloves.framework}
+board = wemos_d1_mini32
+upload_speed = ${opengloves.upload_speed}
+monitor_speed = ${opengloves.monitor_speed}
+
+build_flags = ${opengloves.build_flags}
+              ; Pins configuration
+              ; Comment out to disable
+              -D PIN_FINGER_THUMB=25
+              -D PIN_FINGER_INDEX=14
+              -D PIN_FINGER_MIDDLE=33
+              -D PIN_FINGER_RING=39
+              -D PIN_FINGER_PINKY=36
+
+              -D PIN_FINGER_THUMB_SPLAY=32
+              -D PIN_FINGER_INDEX_SPLAY=13
+              -D PIN_FINGER_MIDDLE_SPLAY=34
+              -D PIN_FINGER_RING_SPLAY=35
+              -D PIN_FINGER_PINKY_SPLAY=26
+
+              -D PIN_JOYSTICK_X=12
+              -D PIN_JOYSTICK_Y=4
+              -D PIN_BUTTON_JOYSTICK=0
+
+              -D PIN_BUTTON_A=2
+              -D PIN_BUTTON_B=11
+              ; -D PIN_BUTTON_MENU=5
+              -D PIN_BUTTON_CALIBRATE=27
+; -D PIN_BUTTON_TRIGGER=19 ; unused if GESTURE_TRIGGER is true
+; -D PIN_BUTTON_GRAB=18 ; unused if GESTURE_GRAB is true
+; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
+
+build_unflags = ${opengloves.build_unflags}
+build_src_filter = ${opengloves.build_src_filter}
+                   +<mode_configs/opengloves/opengloves.cpp>
+lib_deps = ${opengloves.lib_deps}
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ; Indexer CSF
 ; Wiring Diagram: https://github.com/Valsvirtuals/Indexer/wiki/wiring-and-pinout
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 [env:indexer-csf]
-platform			= ${opengloves.platform}
-platform_packages	= ${opengloves.platform_packages}
-framework			= ${opengloves.framework}
-board				= wemos_d1_mini32
-upload_speed		= ${opengloves.upload_speed}
-monitor_speed		= ${opengloves.monitor_speed}
-
-build_flags 		= ${opengloves.build_flags}
-	; Pins configuration
-	; Comment out to disable
-	-D PIN_FINGER_THUMB=25
-	-D PIN_FINGER_INDEX=14
-	-D PIN_FINGER_MIDDLE=33
-	-D PIN_FINGER_RING=39
-	-D PIN_FINGER_PINKY=36
-
-	-D PIN_FINGER_THUMB_SPLAY=32
-	-D PIN_FINGER_INDEX_SPLAY=13
-	-D PIN_FINGER_MIDDLE_SPLAY=34
-	-D PIN_FINGER_RING_SPLAY=35
-	-D PIN_FINGER_PINKY_SPLAY=26
-
-	-D PIN_JOYSTICK_X=12
-	-D PIN_JOYSTICK_Y=4
-	-D PIN_BUTTON_JOYSTICK=0
-
-	-D PIN_BUTTON_A=2
-	-D PIN_BUTTON_B=11
-	; -D PIN_BUTTON_MENU=5
-	-D PIN_BUTTON_CALIBRATE=27
-	; -D PIN_BUTTON_TRIGGER=19 ; unused if GESTURE_TRIGGER is true
-	; -D PIN_BUTTON_GRAB=18 ; unused if GESTURE_GRAB is true
-	; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
-
-	-D PIN_FFB_THUMB=16
-	-D PIN_FFB_INDEX=17
-	-D PIN_FFB_MIDDLE=21
-	-D PIN_FFB_RING=22
-	-D PIN_FFB_PINKY=1
-
-build_unflags 		= ${opengloves.build_unflags}
-build_src_filter	= ${opengloves.build_src_filter}
-	+<mode_configs/opengloves/opengloves.cpp>
-lib_deps    		= ${opengloves.lib_deps}
+platform = ${opengloves.platform}
+platform_packages = ${opengloves.platform_packages}
+framework = ${opengloves.framework}
+board = wemos_d1_mini32
+upload_speed = ${opengloves.upload_speed}
+monitor_speed = ${opengloves.monitor_speed}
+
+build_flags = ${opengloves.build_flags}
+              ; Pins configuration
+              ; Comment out to disable
+              -D PIN_FINGER_THUMB=25
+              -D PIN_FINGER_INDEX=14
+              -D PIN_FINGER_MIDDLE=33
+              -D PIN_FINGER_RING=39
+              -D PIN_FINGER_PINKY=36
+
+              -D PIN_FINGER_THUMB_SPLAY=32
+              -D PIN_FINGER_INDEX_SPLAY=13
+              -D PIN_FINGER_MIDDLE_SPLAY=34
+              -D PIN_FINGER_RING_SPLAY=35
+              -D PIN_FINGER_PINKY_SPLAY=26
+
+              -D PIN_JOYSTICK_X=12
+              -D PIN_JOYSTICK_Y=4
+              -D PIN_BUTTON_JOYSTICK=0
+
+              -D PIN_BUTTON_A=2
+              -D PIN_BUTTON_B=11
+              ; -D PIN_BUTTON_MENU=5
+              -D PIN_BUTTON_CALIBRATE=27
+              ; -D PIN_BUTTON_TRIGGER=19 ; unused if GESTURE_TRIGGER is true
+              ; -D PIN_BUTTON_GRAB=18 ; unused if GESTURE_GRAB is true
+              ; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
+
+              -D PIN_FFB_THUMB=16
+              -D PIN_FFB_INDEX=17
+              -D PIN_FFB_MIDDLE=21
+              -D PIN_FFB_RING=22
+              -D PIN_FFB_PINKY=1
+
+build_unflags = ${opengloves.build_unflags}
+build_src_filter = ${opengloves.build_src_filter}
+                   +<mode_configs/opengloves/opengloves.cpp>
+lib_deps = ${opengloves.lib_deps}
diff --git a/ini/opengloves-lucidgloves.ini b/ini/opengloves-lucidgloves.ini
index f4047f55..f535f768 100644
--- a/ini/opengloves-lucidgloves.ini
+++ b/ini/opengloves-lucidgloves.ini
@@ -3,114 +3,115 @@
 ; Wiring Diagram: https://github.com/LucidVR/lucidgloves/wiki/Prototype-3-Wiring-Diagram
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 [env:lucidgloves-prototype3]
-platform			= ${opengloves.platform}
-platform_packages	= ${opengloves.platform_packages}
-framework			= ${opengloves.framework}
-board				= ${opengloves.board}
-upload_speed		= ${opengloves.upload_speed}
-monitor_speed		= ${opengloves.monitor_speed}
-
-build_flags 		= ${opengloves.build_flags}
-	; Pins configuration
-	; Comment out to disable
-	-D PIN_FINGER_THUMB=32
-	-D PIN_FINGER_INDEX=35
-	-D PIN_FINGER_MIDDLE=34
-	-D PIN_FINGER_RING=39
-	-D PIN_FINGER_PINKY=36
-
-	-D PIN_JOYSTICK_X=33
-	-D PIN_JOYSTICK_Y=25
-	-D PIN_BUTTON_JOYSTICK=26
-
-	-D PIN_BUTTON_A=27
-	-D PIN_BUTTON_B=14
-
-build_unflags 		= ${opengloves.build_unflags}
-build_src_filter	= ${opengloves.build_src_filter}
-	+<mode_configs/opengloves/opengloves.cpp>
-lib_deps    		= ${opengloves.lib_deps}
+platform = ${opengloves.platform}
+platform_packages = ${opengloves.platform_packages}
+framework = ${opengloves.framework}
+board = ${opengloves.board}
+upload_speed = ${opengloves.upload_speed}
+monitor_speed = ${opengloves.monitor_speed}
+
+build_flags = ${opengloves.build_flags} -D OG_ENCODE_FAST
+
+              ; Pins configuration
+              ; Comment out to disable
+              -D PIN_FINGER_THUMB=32
+              -D PIN_FINGER_INDEX=35
+              -D PIN_FINGER_MIDDLE=34
+              -D PIN_FINGER_RING=39
+              -D PIN_FINGER_PINKY=36
+
+              -D PIN_JOYSTICK_X=33
+              -D PIN_JOYSTICK_Y=25
+              -D PIN_BUTTON_JOYSTICK=26
+
+              -D PIN_BUTTON_A=27
+              -D PIN_BUTTON_B=14
+
+build_unflags = ${opengloves.build_unflags}
+build_src_filter = ${opengloves.build_src_filter}
+                   +<mode_configs/opengloves/opengloves.cpp>
+lib_deps = ${opengloves.lib_deps}
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ; LucidGloves Prototype 4
 ; Wiring Diagram: https://github.com/LucidVR/lucidgloves/wiki/Prototype-4-Wiring-Diagram
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 [env:lucidgloves-prototype4]
-platform			= ${opengloves.platform}
-platform_packages	= ${opengloves.platform_packages}
-framework			= ${opengloves.framework}
-board				= ${opengloves.board}
-upload_speed		= ${opengloves.upload_speed}
-monitor_speed		= ${opengloves.monitor_speed}
-
-build_flags 		= ${opengloves.build_flags}
-	; Pins configuration
-	; Comment out to disable
-	-D PIN_FINGER_THUMB=32
-	-D PIN_FINGER_INDEX=35
-	-D PIN_FINGER_MIDDLE=34
-	-D PIN_FINGER_RING=39
-	-D PIN_FINGER_PINKY=36
-
-	-D PIN_JOYSTICK_X=33
-	-D PIN_JOYSTICK_Y=25
-	-D PIN_BUTTON_JOYSTICK=26
-
-	-D PIN_BUTTON_A=27
-	-D PIN_BUTTON_B=14
-	; -D PIN_BUTTON_MENU=27
-	-D PIN_BUTTON_CALIBRATE=12
-	; -D PIN_BUTTON_TRIGGER=12 ; unused if GESTURE_TRIGGER is true
-	; -D PIN_BUTTON_GRAB=13 ; unused if GESTURE_GRAB is true
-	; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
-
-	; todo: add servo pins
-
-build_unflags 		= ${opengloves.build_unflags}
-build_src_filter	= ${opengloves.build_src_filter}
-	+<mode_configs/opengloves/opengloves.cpp>
-lib_deps    		= ${opengloves.lib_deps}
+platform = ${opengloves.platform}
+platform_packages = ${opengloves.platform_packages}
+framework = ${opengloves.framework}
+board = ${opengloves.board}
+upload_speed = ${opengloves.upload_speed}
+monitor_speed = ${opengloves.monitor_speed}
+
+build_flags = ${opengloves.build_flags} -D OG_ENCODE_FAST
+              ; Pins configuration
+              ; Comment out to disable
+              -D PIN_FINGER_THUMB=32
+              -D PIN_FINGER_INDEX=35
+              -D PIN_FINGER_MIDDLE=34
+              -D PIN_FINGER_RING=39
+              -D PIN_FINGER_PINKY=36
+
+              -D PIN_JOYSTICK_X=33
+              -D PIN_JOYSTICK_Y=25
+              -D PIN_BUTTON_JOYSTICK=26
+
+              -D PIN_BUTTON_A=27
+              -D PIN_BUTTON_B=14
+              ; -D PIN_BUTTON_MENU=27
+              -D PIN_BUTTON_CALIBRATE=12
+; -D PIN_BUTTON_TRIGGER=12 ; unused if GESTURE_TRIGGER is true
+; -D PIN_BUTTON_GRAB=13 ; unused if GESTURE_GRAB is true
+; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
+
+; todo: add servo pins
+
+build_unflags = ${opengloves.build_unflags}
+build_src_filter = ${opengloves.build_src_filter}
+                   +<mode_configs/opengloves/opengloves.cpp>
+lib_deps = ${opengloves.lib_deps}
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ; LucidGloves Prototype 4 + Force Feedback
 ; Wiring Diagram: https://github.com/LucidVR/lucidgloves/wiki/Prototype-4-Wiring-Diagram
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 [env:lucidgloves-prototype4-ffb]
-platform			= ${opengloves.platform}
-platform_packages	= ${opengloves.platform_packages}
-framework			= ${opengloves.framework}
-board				= ${opengloves.board}
-upload_speed		= ${opengloves.upload_speed}
-monitor_speed		= ${opengloves.monitor_speed}
-
-build_flags 		= ${opengloves.build_flags}
-	; Pins configuration
-	; Comment out to disable
-	-D PIN_FINGER_THUMB=32
-	-D PIN_FINGER_INDEX=35
-	-D PIN_FINGER_MIDDLE=34
-	-D PIN_FINGER_RING=39
-	-D PIN_FINGER_PINKY=36
-
-	-D PIN_JOYSTICK_X=33
-	-D PIN_JOYSTICK_Y=25
-	-D PIN_BUTTON_JOYSTICK=26
-
-	-D PIN_BUTTON_A=27
-	-D PIN_BUTTON_B=14
-	; -D PIN_BUTTON_MENU=27
-	-D PIN_BUTTON_CALIBRATE=12
-	; -D PIN_BUTTON_TRIGGER=12 ; unused if GESTURE_TRIGGER is true
-	; -D PIN_BUTTON_GRAB=13 ; unused if GESTURE_GRAB is true
-	; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
-
-	-D PIN_FFB_THUMB=17
-	-D PIN_FFB_INDEX=21
-	-D PIN_FFB_MIDDLE=19
-	-D PIN_FFB_RING=18
-	-D PIN_FFB_PINKY=5
-
-build_unflags 		= ${opengloves.build_unflags}
-build_src_filter	= ${opengloves.build_src_filter}
-	+<mode_configs/opengloves/opengloves.cpp>
-lib_deps    		= ${opengloves.lib_deps}
+platform = ${opengloves.platform}
+platform_packages = ${opengloves.platform_packages}
+framework = ${opengloves.framework}
+board = ${opengloves.board}
+upload_speed = ${opengloves.upload_speed}
+monitor_speed = ${opengloves.monitor_speed}
+
+build_flags = ${opengloves.build_flags}
+              ; Pins configuration
+              ; Comment out to disable
+              -D PIN_FINGER_THUMB=32
+              -D PIN_FINGER_INDEX=35
+              -D PIN_FINGER_MIDDLE=34
+              -D PIN_FINGER_RING=39
+              -D PIN_FINGER_PINKY=36
+
+              -D PIN_JOYSTICK_X=33
+              -D PIN_JOYSTICK_Y=25
+              -D PIN_BUTTON_JOYSTICK=26
+
+              -D PIN_BUTTON_A=27
+              -D PIN_BUTTON_B=14
+              ; -D PIN_BUTTON_MENU=27
+              -D PIN_BUTTON_CALIBRATE=12
+              ; -D PIN_BUTTON_TRIGGER=12 ; unused if GESTURE_TRIGGER is true
+              ; -D PIN_BUTTON_GRAB=13 ; unused if GESTURE_GRAB is true
+              ; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
+
+              -D PIN_FFB_THUMB=17
+              -D PIN_FFB_INDEX=21
+              -D PIN_FFB_MIDDLE=19
+              -D PIN_FFB_RING=18
+              -D PIN_FFB_PINKY=5
+
+build_unflags = ${opengloves.build_unflags}
+build_src_filter = ${opengloves.build_src_filter}
+                   +<mode_configs/opengloves/opengloves.cpp>
+lib_deps = ${opengloves.lib_deps}
diff --git a/lib/hands/senseshift/body/hands/hands_interface.hpp b/lib/hands/senseshift/body/hands/hands_interface.hpp
index 4aa48b44..a8887906 100644
--- a/lib/hands/senseshift/body/hands/hands_interface.hpp
+++ b/lib/hands/senseshift/body/hands/hands_interface.hpp
@@ -1,7 +1,8 @@
 #pragma once
 
+#include <senseshift/body/haptics/interface.hpp>
+
 #include <cstdint>
-#include "senseshift/body/haptics/interface.hpp"
 
 namespace SenseShift::Body {
     namespace Hands {
@@ -21,5 +22,5 @@ namespace SenseShift::Body {
             /// Distal phalanx of the volar surface of the any finger.
             static constexpr const ::SenseShift::Body::Haptics::Position FINGERTIP_POSITION(127, 16);
         } // namespace Haptics
-    }     // namespace Hands
+    } // namespace Hands
 } // namespace SenseShift::Body
diff --git a/lib/hands_input/senseshift/body/hands/input/gesture.hpp b/lib/hands_input/senseshift/body/hands/input/gesture.hpp
index 92ca2a94..ad462051 100644
--- a/lib/hands_input/senseshift/body/hands/input/gesture.hpp
+++ b/lib/hands_input/senseshift/body/hands/input/gesture.hpp
@@ -2,12 +2,13 @@
 
 #include <senseshift/core/component.hpp>
 #include <senseshift/input/sensor.hpp>
+#include <senseshift/input/analog_threshold.hpp>
 
 namespace SenseShift::Body::Hands::Input {
     using Gesture = ::SenseShift::Input::BinarySensor;
 
     /// An alias for semantic consistency.
-    using TriggerGesture = ::SenseShift::Input::AnalogThresholdSensor;
+    using TriggerGesture = ::SenseShift::Input::AnalogThresholdSensor<float>;
 
     class GrabGesture : public Gesture, ITickable {
       public:
diff --git a/lib/hands_input/senseshift/body/hands/input/total_curl.hpp b/lib/hands_input/senseshift/body/hands/input/total_curl.hpp
index 678f5b80..26538afc 100644
--- a/lib/hands_input/senseshift/body/hands/input/total_curl.hpp
+++ b/lib/hands_input/senseshift/body/hands/input/total_curl.hpp
@@ -11,33 +11,38 @@ namespace SenseShift::Body::Hands::Input {
     class TotalCurl : public ::SenseShift::Input::FloatSensor, public ::SenseShift::ITickable {
       public:
         /// \param joints The joints to calculate the total curl from.
-        /// \param attach_callbacks Whether to attach callbacks to the joints to recalculate the total curl when they update.
+        /// \param attach_callbacks Whether to attach callbacks to the joints to update the total curl when they update.
         ///                         If false, the total curl will only be recalculated when the tick() method is called.
-        ///                         Setting this to <b>true is not recommended</b>, as it will cause the total curl to be
-        ///                         recalculated multiple times per tick (the same as number of joints).
-        explicit TotalCurl(
-          std::vector<::SenseShift::Input::FloatSensor> joints,
-          bool attach_callbacks = false
-        ) : joints_(std::move(joints)), attach_callbacks_(attach_callbacks) {}
-
-        void init() override {
+        ///                         Setting this to <b>true is not recommended</b>, as it will cause the total curl to
+        ///                         be recalculated multiple times per tick (the same as number of joints).
+        explicit TotalCurl(std::vector<::SenseShift::Input::FloatSensor> joints, bool attach_callbacks = false) :
+          joints_(std::move(joints)), attach_callbacks_(attach_callbacks)
+        {
+        }
+
+        void init() override
+        {
             for (auto& joint : this->joints_) {
-                SS_SUBSENSOR_INIT(&joint, this->attach_callbacks_, [this](float /*value*/) { this->recalculateState(); });
+                SS_SUBSENSOR_INIT(&joint, this->attach_callbacks_, [this](float /*value*/) {
+                    this->recalculateState();
+                });
             }
         }
 
-        void tick() override {
+        void tick() override
+        {
             if (this->attach_callbacks_) {
                 LOG_E("total_curl", "tick() called when attach_callbacks_ is true, infinite loop go wroom-wroom!");
             }
             this->recalculateState();
         }
 
-        void recalculateState() {
+        void recalculateState()
+        {
             float total = 0.0F;
 
             for (auto& joint : this->joints_) {
-                total += joint.getState();
+                total += joint.getValue();
             }
 
             if (!this->joints_.empty()) {
@@ -50,4 +55,4 @@ namespace SenseShift::Body::Hands::Input {
 
         bool attach_callbacks_ = false;
     };
-}  // namespace SenseShift::Body::Hands::Input
+} // namespace SenseShift::Body::Hands::Input
diff --git a/lib/io/senseshift/input/sensor.hpp b/lib/io/senseshift/input/sensor.hpp
index 5b1e8ddb..6d2046ea 100644
--- a/lib/io/senseshift/input/sensor.hpp
+++ b/lib/io/senseshift/input/sensor.hpp
@@ -1,11 +1,11 @@
 #pragma once
 
+#include <optional>
 #include <type_traits>
 #include <vector>
-#include <optional>
 
-#include "senseshift/input/filter.hpp"
 #include "senseshift/input/calibration.hpp"
+#include "senseshift/input/filter.hpp"
 
 #include <senseshift/core/component.hpp>
 #include <senseshift/core/helpers.hpp>
@@ -63,7 +63,8 @@ namespace SenseShift::Input {
         ///     new CenterDeadzoneFilter(0.1f),
         /// });
         /// \endcode
-        void addFilters(std::vector<Filter::IFilter<ValueType>*> filters) {
+        void addFilters(std::vector<Filter::IFilter<ValueType>*> filters)
+        {
             this->filters_.insert(this->filters_.end(), filters.begin(), filters.end());
         }
 
@@ -91,17 +92,18 @@ namespace SenseShift::Input {
 
         void stopCalibration() override { this->is_calibrating_ = false; }
 
-        void reselCalibration() override {
+        void reselCalibration() override
+        {
             if (this->calibrator_.has_value()) {
                 this->calibrator_.value()->reset();
             }
         }
 
-        void addValueCallback(CallbackType &&callback) { this->callbacks_.add(std::move(callback)); }
+        void addValueCallback(CallbackType&& callback) { this->callbacks_.add(std::move(callback)); }
 
-        void addRawValueCallback(CallbackType &&callback) { this->raw_callbacks_.add(std::move(callback)); }
+        void addRawValueCallback(CallbackType&& callback) { this->raw_callbacks_.add(std::move(callback)); }
 
-        void init() override { }
+        void init() override {}
 
         /// Publish the given state to the sensor.
         ///
@@ -110,7 +112,8 @@ namespace SenseShift::Input {
         /// Finally, the filtered value will be assigned to the sensor's .value_.
         ///
         /// \param rawValue The new .raw_value_.
-        void publishState(ValueType rawValue) {
+        void publishState(ValueType rawValue)
+        {
             this->raw_value_ = rawValue;
             this->raw_callbacks_.call(this->raw_value_);
 
@@ -119,18 +122,15 @@ namespace SenseShift::Input {
         }
 
         /// Get the current sensor .value_.
-        [[nodiscard]] auto getValue() -> ValueType override {
-            return this->value_;
-        }
+        [[nodiscard]] auto getValue() -> ValueType override { return this->value_; }
 
         /// Get the current raw sensor .raw_value_.
-        [[nodiscard]] auto getRawValue() -> ValueType {
-            return this->raw_value_;
-        }
+        [[nodiscard]] auto getRawValue() -> ValueType { return this->raw_value_; }
 
       protected:
         /// Apply current filters to value.
-        [[nodiscard]] auto applyFilters(ValueType value) -> ValueType {
+        [[nodiscard]] auto applyFilters(ValueType value) -> ValueType
+        {
             /// Apply calibration
             if (this->calibrator_.has_value()) {
                 if (this->is_calibrating_) {
@@ -168,26 +168,24 @@ namespace SenseShift::Input {
     };
 
     using FloatSensor = Sensor<float>;
+
+    // todo: support double/triple/N-times/long click and so on
     using BinarySensor = Sensor<bool>;
 
     template<typename Tp>
-    class SimpleSensorDecorator : public Sensor<Tp>, public ITickable
-    {
+    class SimpleSensorDecorator : public Sensor<Tp>, public ITickable {
       public:
         using ValueType = Tp;
         using SourceType = ISimpleSensor<ValueType>;
 
         explicit SimpleSensorDecorator(SourceType* source) : source_(source) {}
 
-        void init() override {
-            this->source_->init();
-        }
+        void init() override { this->source_->init(); }
 
-        void tick() override {
-            this->updateValue();
-        }
+        void tick() override { this->updateValue(); }
 
-        auto updateValue() -> ValueType {
+        auto updateValue() -> ValueType
+        {
             auto const raw_value = this->readRawValue();
             this->publishState(raw_value);
 
@@ -196,49 +194,14 @@ namespace SenseShift::Input {
             return this->getValue();
         }
 
-        [[nodiscard]] auto readRawValue() -> ValueType {
-            return this->source_->getValue();
-        }
+        [[nodiscard]] auto readRawValue() -> ValueType { return this->source_->getValue(); }
 
       protected:
-
       private:
         SourceType* source_;
     };
 
-//    template<typename Tp>
-//    class SensorDecorator : public Sensor<Tp>
-//    {
-//      public:
-//        using ValueType = Tp;
-//        using SourceType = Sensor<ValueType>;
-//
-//        explicit SensorDecorator(SourceType* source) : source_(source) {}
-//
-//        void init() override {
-//            this->source_->init();
-//            this->source_->addValueCallback([this](ValueType value) {
-//                this->publishState(value);
-//            });
-//        }
-//
-//        void startCalibration() override {
-//            this->source_->startCalibration();
-//        }
-//
-//        void stopCalibration() override {
-//            this->source_->stopCalibration();
-//        }
-//
-//        void reselCalibration() override {
-//            this->source_->reselCalibration();
-//        }
-//
-//      private:
-//        SourceType* source_;
-//    };
-
     namespace _private {
-        class TheFloatSensor : public Sensor<float> { };
-    }
+        class TheFloatSensor : public Sensor<float> {};
+    } // namespace _private
 } // namespace SenseShift::Input
diff --git a/lib/opengloves/opengloves/opengloves.cpp b/lib/opengloves/opengloves/opengloves.cpp
index 2752e7fc..951391b6 100644
--- a/lib/opengloves/opengloves/opengloves.cpp
+++ b/lib/opengloves/opengloves/opengloves.cpp
@@ -6,7 +6,7 @@
 
 namespace og {
 
-    auto AlphaEncoder::encode_input(const Input& input, char* buffer, size_t length) const -> size_t
+    auto AlphaEncoder::encode_input(const InputData& input, char* buffer, size_t length) const -> size_t
     {
         if (std::holds_alternative<InputInfoData>(input)) {
             const auto& info = std::get<InputInfoData>(input);
diff --git a/lib/opengloves/opengloves/opengloves.hpp b/lib/opengloves/opengloves/opengloves.hpp
index ae8d80aa..d01bbfeb 100644
--- a/lib/opengloves/opengloves/opengloves.hpp
+++ b/lib/opengloves/opengloves/opengloves.hpp
@@ -33,18 +33,20 @@ namespace og {
         DeviceType_LucidGloves,
     };
 
-    union InputFingerCurlData {
-        std::array<float, 4> curl; // NOLINT(*-magic-numbers): I'm sure our finger aren't changing anytime soon
+    template<typename Tf = float>
+    union InputFingerCurl {
+        std::array<Tf, 4> curl; // NOLINT(*-magic-numbers): I'm sure our finger aren't changing anytime soon
         struct {
-            float curl_total;
-            float curl_joint1;
-            float curl_joint2;
-            float curl_joint3;
+            Tf curl_total;
+            Tf curl_joint1;
+            Tf curl_joint2;
+            Tf curl_joint3;
         };
     };
+    using InputFingerCurlData = InputFingerCurl<float>;
 
     template<typename Tp>
-    union InputFingerData {
+    union InputFinger {
         std::array<Tp, 5> fingers; // NOLINT(*-magic-numbers): We aren't going to grow any new fingers soon tbh
         struct {
             Tp thumb;
@@ -58,49 +60,58 @@ namespace og {
             };
         };
     };
+    using InputFingerData = InputFinger<float>;
 
-    struct InputJoystickData {
-        float x, y;
-        bool press;
+    template<typename Tf = float, typename Tb = bool>
+    struct InputJoystick {
+        Tf x, y;
+        Tb press;
     };
+    using InputJoystickData = InputJoystick<float, bool>;
 
-    struct InputButtonData {
-        bool press;
+    template<typename Tb = bool>
+    struct InputButton {
+        Tb press;
         // bool touch;
     };
+    using InputButtonData = InputButton<bool>;
 
-    struct InputAnalogButtonData : InputButtonData {
-        float value;
+    template<typename Tf = float, typename Tb = bool>
+    struct InputAnalogButton : InputButton<Tb> {
+        Tf value;
     };
+    using InputAnalogButtonData = InputAnalogButton<float, bool>;
 
     /// Input data structure.
     ///
     /// I know, it is not the prettiest one, but we need this type of punning to efficiently encode/decode the data
-    struct InputPeripheralData {
-        InputFingerData<InputFingerCurlData> curl;
-        InputFingerData<float> splay;
+    template<typename Tf = float, typename Tb = bool>
+    struct InputPeripheral {
+        InputFinger<InputFingerCurl<Tf>> curl;
+        InputFinger<Tf> splay;
 
-        InputJoystickData joystick;
+        InputJoystick<Tf, Tb> joystick;
 
         union {
-            std::array<InputButtonData, 4> buttons;
+            std::array<InputButton<Tb>, 4> buttons;
             struct {
-                InputButtonData button_a;
-                InputButtonData button_b;
-                InputButtonData button_menu;
-                InputButtonData button_calibrate;
-                InputButtonData pinch;
+                InputButton<Tb> button_a;
+                InputButton<Tb> button_b;
+                InputButton<Tb> button_menu;
+                InputButton<Tb> button_calibrate;
+                InputButton<Tb> pinch;
             };
         };
 
         union {
-            std::array<InputAnalogButtonData, 2> analog_buttons;
+            std::array<InputAnalogButton<Tf, Tb>, 2> analog_buttons;
             struct {
-                InputAnalogButtonData trigger;
-                InputAnalogButtonData grab;
+                InputAnalogButton<Tf, Tb> trigger;
+                InputAnalogButton<Tf, Tb> grab;
             };
         };
     };
+    using InputPeripheralData = InputPeripheral<float, bool>;
 
     struct InputInfoData {
         Hand hand;
@@ -109,15 +120,16 @@ namespace og {
         unsigned int firmware_version;
     };
 
-    using Input = std::variant<InputInfoData, InputPeripheralData>;
+    using InputData = std::variant<InputInfoData, InputPeripheralData>;
 
     class Output {};
 
     class IEncoder {
       public:
-        [[nodiscard]] virtual auto encode_input(const Input& input, char* buffer, size_t length) const -> size_t = 0;
+        [[nodiscard]] virtual auto encode_input(const InputData& input, char* buffer, size_t length) const
+          -> size_t = 0;
 
-        [[nodiscard]] auto encode_input(const Input& input) const -> std::string
+        [[nodiscard]] auto encode_input(const InputData& input) const -> std::string
         {
             std::string buffer;
             buffer.resize(OG_BUFFER_SIZE);
@@ -300,9 +312,15 @@ namespace og {
             { Command::GestureTrigger_Value, "P" }, // Analog trigger button/gesture
 
             { Command::Info, "Z" },
+#ifdef OG_USE_FROZEN
+            { Command::Info_FirmwareVersion, frozen::string(INFO_FIRMWARE_VERSION_KEY) },
+            { Command::Info_DeviceType, frozen::string(INFO_DEVICE_TYPE_KEY) },
+            { Command::Info_Hand, frozen::string(INFO_HAND_KEY) },
+#else
             { Command::Info_FirmwareVersion, INFO_FIRMWARE_VERSION_KEY },
             { Command::Info_DeviceType, INFO_DEVICE_TYPE_KEY },
             { Command::Info_Hand, INFO_HAND_KEY },
+#endif
           });
 
         /// Alpha keys for fingers.
@@ -318,13 +336,18 @@ namespace og {
         /// Alpha keys for finger curl joints. Top level is the finger, second level is the joint.
         /// The top level <b>MUST</b> be in the same order as the `InputFingerData` struct.
         /// Second level array <b>MUST</b> be in the same order as the `InputFingerCurlData` struct.
-        inline static const std::array<std::array<std::string, 4>, 5> FINGER_CURL_JOINT_ALPHA_KEY = { {
-          { "A", "(AAB)", "(AAC)", "(AAD)" }, // Thumb (total, joint1, joint2, joint3)
-          { "B", "(BAB)", "(BAC)", "(BAD)" }, // Index (total, joint1, joint2, joint3)
-          { "C", "(CAB)", "(CAC)", "(CAD)" }, // Middle (total, joint1, joint2, joint3)
-          { "D", "(DAB)", "(DAC)", "(DAD)" }, // Ring (total, joint1, joint2, joint3)
-          { "E", "(EAB)", "(EAC)", "(EAD)" }, // Pinky (total, joint1, joint2, joint3)
-        } };
+#ifdef OG_USE_FROZEN
+        inline static constexpr const std::array<std::array<frozen::string, 4>, 5> FINGER_CURL_JOINT_ALPHA_KEY =
+#else
+        inline static const std::array<std::array<std::string, 4>, 5> FINGER_CURL_JOINT_ALPHA_KEY =
+#endif
+          { {
+            { "A", "(AAB)", "(AAC)", "(AAD)" }, // Thumb (total, joint1, joint2, joint3)
+            { "B", "(BAB)", "(BAC)", "(BAD)" }, // Index (total, joint1, joint2, joint3)
+            { "C", "(CAB)", "(CAC)", "(CAD)" }, // Middle (total, joint1, joint2, joint3)
+            { "D", "(DAB)", "(DAC)", "(DAD)" }, // Ring (total, joint1, joint2, joint3)
+            { "E", "(EAB)", "(EAC)", "(EAD)" }, // Pinky (total, joint1, joint2, joint3)
+          } };
 
         /// Alpha keys for buttons.
         /// <b>MUST</b> be in the same order as the `InputPeripheralData` struct.
@@ -343,8 +366,8 @@ namespace og {
           'I', // Grab
         } };
 
-        auto encode_input(const Input& input, char* buffer, size_t length) const -> size_t override;
+        [[nodiscard]] auto encode_input(const InputData& input, char* buffer, size_t length) const -> size_t override;
 
-        auto parse_output(const char* data, size_t length) const -> Output override;
+        [[nodiscard]] auto parse_output(const char* data, size_t length) const -> Output override;
     };
 } // namespace og
\ No newline at end of file
diff --git a/lib/opengloves/senseshift/opengloves/autoconfig.hpp b/lib/opengloves/senseshift/opengloves/autoconfig.hpp
index 35419d72..4f114d03 100644
--- a/lib/opengloves/senseshift/opengloves/autoconfig.hpp
+++ b/lib/opengloves/senseshift/opengloves/autoconfig.hpp
@@ -1,6 +1,6 @@
 #pragma once
 
-#include <senseshift/opengloves/interface.hpp>
+#include <senseshift/opengloves/opengloves.hpp>
 
 #ifdef ARDUINO
 #include <senseshift/opengloves/transport/stream.hpp>
@@ -29,10 +29,10 @@
 #pragma region Calibration
 
 #ifndef CALIBRATION_CURL
-#define CALIBRATION_CURL ::SenseShift::Calibration::MinMaxCalibrator<uint16_t, 0, ANALOG_MAX>
+#define CALIBRATION_CURL new ::SenseShift::Input::Calibration::MinMaxCalibrator<float>()
 #endif
 #ifndef CALIBRATION_SPLAY
-#define CALIBRATION_SPLAY ::SenseShift::Calibration::MinMaxCalibrator<uint16_t, 0, ANALOG_MAX>
+#define CALIBRATION_SPLAY new ::SenseShift::Input::Calibration::CenterPointDeviationCalibrator<float>(0.66F, 0.005F)
 #endif
 
 #ifndef CALIBRATION_DURATION
@@ -43,61 +43,93 @@
 
 #pragma region Fingers
 
-#define FINGER_THUMB_ENABLED (defined(PIN_FINGER_THUMB) && (PIN_FINGER_THUMB != -1))
-#define FINGER_INDEX_ENABLED (defined(PIN_FINGER_INDEX) && (PIN_FINGER_INDEX != -1))
-#define FINGER_MIDDLE_ENABLED (defined(PIN_FINGER_MIDDLE) && (PIN_FINGER_MIDDLE != -1))
-#define FINGER_RING_ENABLED (defined(PIN_FINGER_RING) && (PIN_FINGER_RING != -1))
-#define FINGER_PINKY_ENABLED (defined(PIN_FINGER_PINKY) && (PIN_FINGER_PINKY != -1))
-#define FINGER_CLASS(type, curl_pin, curl_invert, curl_calib)                   \
-    FingerSensor(                                                               \
-      new ::SenseShift::Input::CalibratedSimpleSensor<uint16_t>(                \
-        new ::SenseShift::Input::StaticMedianSensor<uint16_t, 5>(               \
-          new ::SenseShift::Arduino::Input::AnalogSensor<curl_invert>(curl_pin) \
-        ),                                                                      \
-        new curl_calib()                                                        \
-      ),                                                                        \
-      type                                                                      \
-    )
-#define FINGER_THUMB_SPLAY (FINGER_THUMB_ENABLED && defined(PIN_FINGER_THUMB_SPLAY) && (PIN_FINGER_THUMB_SPLAY != -1))
-#define FINGER_INDEX_SPLAY (FINGER_INDEX_ENABLED && defined(PIN_FINGER_INDEX_SPLAY) && (PIN_FINGER_INDEX_SPLAY != -1))
-#define FINGER_MIDDLE_SPLAY \
-    (FINGER_MIDDLE_ENABLED && defined(PIN_FINGER_MIDDLE_SPLAY) && (PIN_FINGER_MIDDLE_SPLAY != -1))
-#define FINGER_RING_SPLAY (FINGER_RING_ENABLED && defined(PIN_FINGER_RING_SPLAY) && (PIN_FINGER_RING_SPLAY != -1))
-#define FINGER_PINKY_SPLAY (FINGER_PINKY_ENABLED && defined(PIN_FINGER_PINKY_SPLAY) && (PIN_FINGER_PINKY_SPLAY != -1))
-#define FINGER_SPLAY_CLASS(type, curl_pin, curl_invert, curl_calib, splay_pin, splay_invert, splay_calib) \
-    FingerSensor(                                                                                         \
-      new ::SenseShift::Input::CalibratedSimpleSensor<uint16_t>(                                          \
-        new ::SenseShift::Input::StaticMedianSensor<uint16_t, 5>(                                         \
-          new ::SenseShift::Arduino::Input::AnalogSensor<curl_invert>(curl_pin)                           \
-        ),                                                                                                \
-        new curl_calib()                                                                                  \
-      ),                                                                                                  \
-      new ::SenseShift::Input::CalibratedSimpleSensor<uint16_t>(                                          \
-        new ::SenseShift::Input::StaticMedianSensor<uint16_t, 5>(                                         \
-          new ::SenseShift::Arduino::Input::AnalogSensor<splay_invert>(splay_pin)                         \
-        ),                                                                                                \
-        new splay_calib()                                                                                 \
-      ),                                                                                                  \
-      type                                                                                                \
-    )
+#ifdef PIN_FINGER_THUMB
+#define FINGER_THUMB_ENABLED (PIN_FINGER_THUMB != -1)
+#else
+#define FINGER_THUMB_ENABLED false
+#endif
+
+#ifdef PIN_FINGER_INDEX
+#define FINGER_INDEX_ENABLED (PIN_FINGER_INDEX != -1)
+#else
+#define FINGER_INDEX_ENABLED false
+#endif
+
+#ifdef PIN_FINGER_MIDDLE
+#define FINGER_MIDDLE_ENABLED (PIN_FINGER_MIDDLE != -1)
+#else
+#define FINGER_MIDDLE_ENABLED false
+#endif
+
+#ifdef PIN_FINGER_RING
+#define FINGER_RING_ENABLED (PIN_FINGER_RING != -1)
+#else
+#define FINGER_RING_ENABLED false
+#endif
+
+#ifdef PIN_FINGER_PINKY
+#define FINGER_PINKY_ENABLED (PIN_FINGER_PINKY != -1)
+#else
+#define FINGER_PINKY_ENABLED false
+#endif
+
+#define DEFINE_FINGER(NAME, CURL_PIN, CURL_INVERT, CURL_CALIB)                                                   \
+    auto* NAME##_sensor = new ::SenseShift::Input::SimpleSensorDecorator(                                        \
+      new ::SenseShift::Arduino::Input::AnalogSimpleSensor<CURL_INVERT>(CURL_PIN)                                \
+    );                                                                                                           \
+    NAME##_sensor->addFilters({ new ::SenseShift::Input::Filter::ExponentialMovingAverageFilter<float>(0.8F) }); \
+    NAME##_sensor->setCalibrator((CURL_CALIB));
+
+#ifdef PIN_FINGER_THUMB_SPLAY
+#define FINGER_THUMB_SPLAY (FINGER_THUMB_ENABLED && (PIN_FINGER_THUMB_SPLAY != -1))
+#else
+#define FINGER_THUMB_SPLAY false
+#endif
+
+#ifdef PIN_FINGER_INDEX_SPLAY
+#define FINGER_INDEX_SPLAY (FINGER_INDEX_ENABLED && (PIN_FINGER_INDEX_SPLAY != -1))
+#else
+#define FINGER_INDEX_SPLAY false
+#endif
+
+#ifdef PIN_FINGER_MIDDLE_SPLAY
+#define FINGER_MIDDLE_SPLAY (FINGER_MIDDLE_ENABLED && (PIN_FINGER_MIDDLE_SPLAY != -1))
+#else
+#define FINGER_MIDDLE_SPLAY false
+#endif
+
+#ifdef PIN_FINGER_RING_SPLAY
+#define FINGER_RING_SPLAY (FINGER_RING_ENABLED && (PIN_FINGER_RING_SPLAY != -1))
+#else
+#define FINGER_RING_SPLAY false
+#endif
+
+#ifdef PIN_FINGER_PINKY_SPLAY
+#define FINGER_PINKY_SPLAY (FINGER_PINKY_ENABLED && (PIN_FINGER_PINKY_SPLAY != -1))
+#else
+#define FINGER_PINKY_SPLAY false
+#endif
+
+#define DEFINE_FINGER_SPLAY(NAME, CURL_PIN, CURL_INVERT, CURL_CALIB, SPLAY_PIN, SPLAY_INVERT, SPLAY_CALIB) \
+    DEFINE_FINGER(NAME##_curl, CURL_PIN, CURL_INVERT, CURL_CALIB);                                         \
+    DEFINE_FINGER(NAME##_splay, SPLAY_PIN, SPLAY_INVERT, SPLAY_CALIB);
 
 #pragma endregion
 
 #pragma region Joysticks
 
-#define JOYSTICK_ENABLED \
-    (defined(PIN_JOYSTICK_X) && defined(PIN_JOYSTICK_Y) && (PIN_JOYSTICK_X != -1) && (PIN_JOYSTICK_Y != -1))
+#if defined(PIN_JOYSTICK_X) && defined(PIN_JOYSTICK_Y)
+#define JOYSTICK_ENABLED (PIN_JOYSTICK_X != -1) && (PIN_JOYSTICK_Y != -1)
+#else
+#define JOYSTICK_ENABLED false
+#endif
 
-#define JOYSTICK_CLASS(type, pin, invert, deadzone)                   \
-    StringEncodedMemoizedSensor<uint16_t>(                            \
-      new ::SenseShift::Input::JoystickAxisSensor<uint16_t>(          \
-        new ::SenseShift::Input::StaticMedianSensor<uint16_t, 5>(     \
-          new ::SenseShift::Arduino::Input::AnalogSensor<invert>(pin) \
-        ),                                                            \
-        deadzone                                                      \
-      ),                                                              \
-      type                                                            \
-    )
+#define DEFINE_JOYSTICK_AXIS(NAME, PIN, INVERT, DEADZONE)                                                              \
+    auto NAME##_sensor =                                                                                               \
+      new ::SenseShift::Input::SimpleSensorDecorator(new ::SenseShift::Arduino::Input::AnalogSimpleSensor<INVERT>(PIN) \
+      );                                                                                                               \
+    NAME##_sensor->addFilters({ new ::SenseShift::Input::Filter::ExponentialMovingAverageFilter<float>(0.8F),          \
+                                new ::SenseShift::Input::Filter::CenterDeadzoneFilter(DEADZONE) });
 
 #pragma endregion
 
@@ -112,27 +144,24 @@
 #define BUTTON_GRAB_ENABLED (!GESTURE_GRAB_ENABLED && defined(PIN_BUTTON_GRAB) && (PIN_BUTTON_GRAB != -1))
 #define BUTTON_PINCH_ENABLED (!GESTURE_PINCH_ENABLED && defined(PIN_BUTTON_PINCH) && (PIN_BUTTON_PINCH != -1))
 
-#define BUTTON_CLASS(_type, _pin, _invert) \
-    StringEncodedMemoizedSensor<bool>(new ::SenseShift::Arduino::Input::BinarySensor<_invert>(_pin), _type)
+#define BUTTON_CLASS(PIN, INVERT) SimpleSensorDecorator(new DigitalSimpleSensor<INVERT>(PIN));
 
 #pragma endregion
 
 #pragma region Gestures
 
 #ifndef GESTURE_TRIGGER_THRESHOLD
-#define GESTURE_TRIGGER_THRESHOLD (ANALOG_MAX / 2)
+#define GESTURE_TRIGGER_THRESHOLD (0.5F)
 #endif
 
 #ifndef GESTURE_GRAB_THRESHOLD
-#define GESTURE_GRAB_THRESHOLD (ANALOG_MAX / 2)
+#define GESTURE_GRAB_THRESHOLD (0.5F)
 #endif
 
 #ifndef GESTURE_PINCH_THRESHOLD
-#define GESTURE_PINCH_THRESHOLD (ANALOG_MAX / 2)
+#define GESTURE_PINCH_THRESHOLD (0.5F)
 #endif
 
-#define GESTURE_CLASS(_type, _sensor) StringEncodedMemoizedSensor<bool>(_sensor, _type)
-
 #pragma endregion
 
 #define FFB_THUMB_ENABLED (defined(PIN_FFB_THUMB) && (PIN_FFB_THUMB != -1))
diff --git a/lib/opengloves/senseshift/opengloves/opengloves.hpp b/lib/opengloves/senseshift/opengloves/opengloves.hpp
index 267711c3..fa948e4d 100644
--- a/lib/opengloves/senseshift/opengloves/opengloves.hpp
+++ b/lib/opengloves/senseshift/opengloves/opengloves.hpp
@@ -15,4 +15,6 @@ namespace SenseShift::OpenGloves {
         virtual auto hasData() -> bool = 0;
         virtual auto read(char* buffer, size_t length) -> size_t = 0;
     };
+
+    using InputSensors = og::InputPeripheral<::SenseShift::Input::FloatSensor*, ::SenseShift::Input::BinarySensor*>;
 } // namespace SenseShift::OpenGloves
diff --git a/lib/opengloves/sensor/og_finger.hpp b/lib/opengloves/sensor/og_finger.hpp
deleted file mode 100644
index 95760b7a..00000000
--- a/lib/opengloves/sensor/og_finger.hpp
+++ /dev/null
@@ -1,156 +0,0 @@
-#pragma once
-
-#include <optional>
-
-#include "og_sensor.hpp"
-
-namespace OpenGloves {
-    struct FingerSensors {
-        std::vector<SenseShift::Input::CalibratedSimpleSensor<uint16_t>*> curl =
-          std::vector<SenseShift::Input::CalibratedSimpleSensor<uint16_t>*>();
-        std::optional<SenseShift::Input::CalibratedSimpleSensor<uint16_t>*> splay = std::nullopt;
-
-        FingerSensors(
-          std::vector<SenseShift::Input::CalibratedSimpleSensor<uint16_t>*> curl,
-          std::optional<SenseShift::Input::CalibratedSimpleSensor<uint16_t>*> splay = std::nullopt
-        ) :
-          curl(curl), splay(splay){};
-
-        FingerSensors(
-          SenseShift::Input::CalibratedSimpleSensor<uint16_t>* curl1,
-          std::optional<SenseShift::Input::CalibratedSimpleSensor<uint16_t>*> splay = std::nullopt
-        ) :
-          curl({ curl1 }), splay(splay){};
-    };
-
-    class ICurl {
-      public:
-        /**
-         * Get finger curl value.
-         */
-        virtual uint16_t getCurl() = 0;
-    };
-
-    using IFingerSensor = SenseShift::Input::ISimpleSensor<FingerValue>;
-
-    class SimpleFingerSensor : public IFingerSensor, public ICurl {
-      public:
-        SimpleFingerSensor(FingerSensors sensors) : sensors(sensors){};
-
-        SimpleFingerSensor(
-          SenseShift::Input::CalibratedSimpleSensor<uint16_t>* curl1,
-          std::optional<SenseShift::Input::CalibratedSimpleSensor<uint16_t>*> splay = std::nullopt
-        ) :
-          sensors(curl1, splay){};
-
-        void init() override
-        {
-            for (auto sensor : sensors.curl) {
-                sensor->init();
-            }
-            if (sensors.splay.has_value()) {
-                sensors.splay.value()->init();
-            }
-        }
-
-        [[nodiscard]] auto getValue() -> FingerValue override
-        {
-            FingerValue value{
-                .curl = std::vector<uint16_t>(),
-                .splay = std::nullopt,
-            };
-            for (auto sensor : sensors.curl) {
-                value.curl.push_back(sensor->getValue());
-            }
-            if (sensors.splay.has_value()) {
-                value.splay = sensors.splay.value()->getValue();
-            }
-            return value;
-        }
-
-        [[nodiscard]] auto getCurl() -> uint16_t override { return this->getValue().getTotalCurl(); }
-
-      protected:
-        FingerSensors sensors;
-    };
-
-    /**
-     * Simple finger sensor that only provides calibration.
-     */
-    class CalibratedFingerSensor : public SimpleFingerSensor, public SenseShift::Calibration::ICalibrated {
-      public:
-        CalibratedFingerSensor(FingerSensors sensors) : SimpleFingerSensor(sensors){};
-
-        CalibratedFingerSensor(
-          SenseShift::Input::CalibratedSimpleSensor<uint16_t>* curl1,
-          std::optional<SenseShift::Input::CalibratedSimpleSensor<uint16_t>*> splay = std::nullopt
-        ) :
-          SimpleFingerSensor(curl1, splay){};
-
-        void resetCalibration() override
-        {
-            for (auto sensor : this->sensors.curl) {
-                sensor->resetCalibration();
-            }
-            if (this->sensors.splay.has_value()) {
-                this->sensors.splay.value()->resetCalibration();
-            }
-        }
-
-        void enableCalibration() override
-        {
-            for (auto sensor : this->sensors.curl) {
-                sensor->enableCalibration();
-            }
-            if (this->sensors.splay.has_value()) {
-                this->sensors.splay.value()->enableCalibration();
-            }
-        }
-
-        void disableCalibration() override
-        {
-            for (auto sensor : this->sensors.curl) {
-                sensor->disableCalibration();
-            }
-            if (this->sensors.splay.has_value()) {
-                this->sensors.splay.value()->disableCalibration();
-            }
-        }
-    };
-
-    class FingerSensor :
-      public StringEncodedMemoizedSensor<FingerValue>,
-      public SenseShift::Calibration::ICalibrated,
-      public ICurl {
-      public:
-        FingerSensor(CalibratedFingerSensor* sensor, IEncodedInput::Type type) :
-          StringEncodedMemoizedSensor<FingerValue>(sensor, type){};
-
-        FingerSensor(
-          SenseShift::Input::CalibratedSimpleSensor<uint16_t>* curl1,
-          std::optional<SenseShift::Input::CalibratedSimpleSensor<uint16_t>*> splay,
-          IEncodedInput::Type type
-        ) :
-          StringEncodedMemoizedSensor<FingerValue>(new CalibratedFingerSensor(curl1, splay), type){};
-
-        FingerSensor(SenseShift::Input::CalibratedSimpleSensor<uint16_t>* curl1, IEncodedInput::Type type) :
-          StringEncodedMemoizedSensor<FingerValue>(new CalibratedFingerSensor(curl1, std::nullopt), type){};
-
-        void resetCalibration() override { static_cast<CalibratedFingerSensor*>(this->getSensor())->resetCalibration(); }
-
-        void enableCalibration() override { static_cast<CalibratedFingerSensor*>(this->getSensor())->enableCalibration(); }
-
-        void disableCalibration() override { static_cast<CalibratedFingerSensor*>(this->getSensor())->disableCalibration(); }
-
-        uint16_t getCurl() override { return this->getValue().getTotalCurl(); }
-    };
-
-    struct HandSensors {
-        std::optional<FingerSensor> thumb = std::nullopt;
-        std::optional<FingerSensor> index = std::nullopt;
-        std::optional<FingerSensor> middle = std::nullopt;
-        std::optional<FingerSensor> ring = std::nullopt;
-        std::optional<FingerSensor> pinky = std::nullopt;
-    };
-
-} // namespace OpenGloves
diff --git a/lib/opengloves/sensor/og_sensor.hpp b/lib/opengloves/sensor/og_sensor.hpp
deleted file mode 100644
index e43dba74..00000000
--- a/lib/opengloves/sensor/og_sensor.hpp
+++ /dev/null
@@ -1,45 +0,0 @@
-#pragma once
-
-#include <cstdint>
-#include <optional>
-#include <vector>
-
-#include <og_protocol.hpp>
-#include <senseshift/input/sensor.hpp>
-
-namespace OpenGloves {
-    struct FingerValue {
-        std::vector<std::uint16_t> curl = std::vector<std::uint16_t>({ 0 });
-        std::optional<std::uint16_t> splay = std::nullopt;
-
-        [[nodiscard]] auto getTotalCurl() const -> std::uint16_t
-        {
-            if (this->curl.empty()) {
-                return 0;
-            }
-
-            std::uint16_t total = 0;
-            for (auto c_curl : this->curl) {
-                total += c_curl;
-            }
-            return total / this->curl.size();
-        }
-    };
-
-    template<typename _Tp>
-    class StringEncodedMemoizedSensor :
-      public IStringEncodedMemoizedSensor,
-      public SenseShift::Input::MemoizedSensor<_Tp> {
-      public:
-        StringEncodedMemoizedSensor(SenseShift::Input::ISimpleSensor<_Tp>* sensor, IEncodedInput::Type type) :
-          IStringEncodedMemoizedSensor(type), SenseShift::Input::MemoizedSensor<_Tp>(sensor){};
-
-        void init() override { this->getSensor()->init(); }
-
-        void updateValue() override { this->value_ = this->sensor_->getValue(); }
-
-        [[nodiscard]] size_t getEncodedLength() const override;
-
-        size_t encodeString(char* buffer) override;
-    };
-} // namespace OpenGloves
diff --git a/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp b/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp
index f249f954..d3128165 100644
--- a/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp
+++ b/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp
@@ -1,18 +1,17 @@
 #pragma once
 
+#include <senseshift/buffer.hpp>
+
 #include <BLESerial.hpp>
 #include <BluetoothSerial.h>
 #include <HardwareSerial.h>
 #include <Print.h>
 #include <WiFi.h>
 
-#include <og_protocol.hpp>
-#include <senseshift/opengloves/interface.hpp>
+#include <senseshift/opengloves/opengloves.hpp>
 
 namespace SenseShift::OpenGloves {
     class IStreamTransport : public ITransport {
-        using IStringEncodedMemoizedSensor = ::OpenGloves::IStringEncodedMemoizedSensor;
-
       protected:
         Stream* channel;
         char* buffer = new char[256];
@@ -57,7 +56,7 @@ namespace SenseShift::OpenGloves {
         StreamTransport(Stream& channel) : IStreamTransport(&channel){};
         StreamTransport(Stream* channel) : IStreamTransport(channel){};
 
-        void setup() override { this->mReady = true; }
+        void init() override { this->mReady = true; }
 
         bool isReady() override { return this->channel != nullptr && this->mReady; }
 
@@ -101,7 +100,7 @@ namespace SenseShift::OpenGloves {
       public:
         WiFiSerialTransport(WiFiServer& server) : IStreamTransport(nullptr), m_server(server){};
 
-        void setup() override
+        void init() override
         {
             auto* client = static_cast<WiFiClient*>(this->channel);
             if (client != nullptr) {
diff --git a/lib/opengloves_task/opengloves_task.hpp b/lib/opengloves_task/opengloves_task.hpp
index 4fb4f43f..c327890d 100644
--- a/lib/opengloves_task/opengloves_task.hpp
+++ b/lib/opengloves_task/opengloves_task.hpp
@@ -4,15 +4,12 @@
 
 #include <optional>
 
-#include <og_ffb.hpp>
 #include <senseshift/arduino/input/sensor/analog.hpp>
-#include <senseshift/arduino/input/sensor/binary.hpp>
-#include <senseshift/calibration.hpp>
+#include <senseshift/arduino/input/sensor/digital.hpp>
 #include <senseshift/freertos/task.hpp>
 #include <senseshift/input/sensor.hpp>
-#include <senseshift/input/sensor/joystick.hpp>
 #include <senseshift/opengloves/encoding/alpha.hpp>
-#include <senseshift/opengloves/interface.hpp>
+#include <senseshift/opengloves/opengloves.hpp>
 #include <senseshift/utility.hpp>
 #include <sensor/og_finger.hpp>
 #include <sensor/og_gesture.hpp>

From 3b8a90edb35a5ca2c6eca9d8871fe33e093a583a Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Thu, 8 Feb 2024 14:14:17 +0400
Subject: [PATCH 56/82] refactor(FreeRTOS): make SensorUpdateTask universal for
 components

---
 firmware/mode_configs/bhaptics/tactal.cpp     | 12 ++--
 firmware/mode_configs/bhaptics/tactglove.cpp  | 12 ++--
 firmware/mode_configs/bhaptics/tactosy2.cpp   | 12 ++--
 firmware/mode_configs/bhaptics/tactosyf.cpp   | 12 ++--
 firmware/mode_configs/bhaptics/tactosyh.cpp   | 12 ++--
 .../mode_configs/bhaptics/tactsuit_x16.cpp    | 14 ++---
 .../bhaptics/tactsuit_x16_pca9685.cpp         | 14 ++---
 .../mode_configs/bhaptics/tactsuit_x40.cpp    | 12 ++--
 firmware/mode_configs/bhaptics/tactvisor.cpp  | 12 ++--
 lib/battery/senseshift/battery/sensor.hpp     | 18 +++---
 lib/core/senseshift/core/helpers.hpp          | 23 ++++----
 .../senseshift/freertos/input/sensor.hpp      | 32 ----------
 lib/freertos/senseshift/freertos/task.hpp     | 54 ++++++++++++++---
 lib/io/senseshift/input/filter.hpp            | 59 +++++++++----------
 14 files changed, 150 insertions(+), 148 deletions(-)

diff --git a/firmware/mode_configs/bhaptics/tactal.cpp b/firmware/mode_configs/bhaptics/tactal.cpp
index 6be10fed..efab08b5 100644
--- a/firmware/mode_configs/bhaptics/tactal.cpp
+++ b/firmware/mode_configs/bhaptics/tactal.cpp
@@ -58,13 +58,13 @@ void setupMode()
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
     auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
     batteryVoltageSensor->addFilters({
-        new MultiplyFilter(3.3F), // Convert to raw pin voltage
-        new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
+      new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
+      new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
     });
-    auto* batteryTask = new SensorUpdateTask<SimpleSensorDecorator<float>>(
-            batteryVoltageSensor,
-            SENSESHIFT_BATTERY_SAMPLE_RATE,
-            { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask(
+      batteryVoltageSensor,
+      SENSESHIFT_BATTERY_SAMPLE_RATE,
+      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     batteryTask->begin();
 
diff --git a/firmware/mode_configs/bhaptics/tactglove.cpp b/firmware/mode_configs/bhaptics/tactglove.cpp
index 874cd07a..8d3fa066 100644
--- a/firmware/mode_configs/bhaptics/tactglove.cpp
+++ b/firmware/mode_configs/bhaptics/tactglove.cpp
@@ -66,13 +66,13 @@ void setupMode()
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
     auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
     batteryVoltageSensor->addFilters({
-        new MultiplyFilter(3.3F), // Convert to raw pin voltage
-        new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
+      new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
+      new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
     });
-    auto* batteryTask = new SensorUpdateTask<SimpleSensorDecorator<float>>(
-            batteryVoltageSensor,
-            SENSESHIFT_BATTERY_SAMPLE_RATE,
-            { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask(
+      batteryVoltageSensor,
+      SENSESHIFT_BATTERY_SAMPLE_RATE,
+      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     batteryTask->begin();
 
diff --git a/firmware/mode_configs/bhaptics/tactosy2.cpp b/firmware/mode_configs/bhaptics/tactosy2.cpp
index 572e52ad..f18515f9 100644
--- a/firmware/mode_configs/bhaptics/tactosy2.cpp
+++ b/firmware/mode_configs/bhaptics/tactosy2.cpp
@@ -59,13 +59,13 @@ void setupMode()
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
     auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
     batteryVoltageSensor->addFilters({
-        new MultiplyFilter(3.3F), // Convert to raw pin voltage
-        new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
+      new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
+      new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
     });
-    auto* batteryTask = new SensorUpdateTask<SimpleSensorDecorator<float>>(
-            batteryVoltageSensor,
-            SENSESHIFT_BATTERY_SAMPLE_RATE,
-            { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask(
+      batteryVoltageSensor,
+      SENSESHIFT_BATTERY_SAMPLE_RATE,
+      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     batteryTask->begin();
 
diff --git a/firmware/mode_configs/bhaptics/tactosyf.cpp b/firmware/mode_configs/bhaptics/tactosyf.cpp
index 51b67d9f..886857c3 100644
--- a/firmware/mode_configs/bhaptics/tactosyf.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyf.cpp
@@ -60,13 +60,13 @@ void setupMode()
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
     auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
     batteryVoltageSensor->addFilters({
-        new MultiplyFilter(3.3F), // Convert to raw pin voltage
-        new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
+      new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
+      new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
     });
-    auto* batteryTask = new SensorUpdateTask<SimpleSensorDecorator<float>>(
-            batteryVoltageSensor,
-            SENSESHIFT_BATTERY_SAMPLE_RATE,
-            { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask(
+      batteryVoltageSensor,
+      SENSESHIFT_BATTERY_SAMPLE_RATE,
+      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     batteryTask->begin();
 
diff --git a/firmware/mode_configs/bhaptics/tactosyh.cpp b/firmware/mode_configs/bhaptics/tactosyh.cpp
index 277dcb0d..5646060f 100644
--- a/firmware/mode_configs/bhaptics/tactosyh.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyh.cpp
@@ -60,13 +60,13 @@ void setupMode()
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
     auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
     batteryVoltageSensor->addFilters({
-        new MultiplyFilter(3.3F), // Convert to raw pin voltage
-        new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
+      new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
+      new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
     });
-    auto* batteryTask = new SensorUpdateTask<SimpleSensorDecorator<float>>(
-            batteryVoltageSensor,
-            SENSESHIFT_BATTERY_SAMPLE_RATE,
-            { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask(
+      batteryVoltageSensor,
+      SENSESHIFT_BATTERY_SAMPLE_RATE,
+      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     batteryTask->begin();
 
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
index 5ba9322c..7a448b06 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
@@ -30,7 +30,7 @@ Application* app = &App;
 static const std::array<OutputLayout, BH_LAYOUT_TACTSUITX16_SIZE> bhLayout = { BH_LAYOUT_TACTSUITX16 };
 
 // Ouput indices, responsible for x40 => x16 grouping
-static const std::array<std::uint8_t,BH_LAYOUT_TACTSUITX16_GROUPS_SIZE> layoutGroups = BH_LAYOUT_TACTSUITX16_GROUPS;
+static const std::array<std::uint8_t, BH_LAYOUT_TACTSUITX16_GROUPS_SIZE> layoutGroups = BH_LAYOUT_TACTSUITX16_GROUPS;
 
 void setupMode()
 {
@@ -69,13 +69,13 @@ void setupMode()
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
     auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
     batteryVoltageSensor->addFilters({
-        new MultiplyFilter(3.3F), // Convert to raw pin voltage
-        new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
+      new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
+      new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
     });
-    auto* batteryTask = new SensorUpdateTask<SimpleSensorDecorator<float>>(
-            batteryVoltageSensor,
-            SENSESHIFT_BATTERY_SAMPLE_RATE,
-            { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask(
+      batteryVoltageSensor,
+      SENSESHIFT_BATTERY_SAMPLE_RATE,
+      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     batteryTask->begin();
 
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
index 256222a8..38ea195f 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
@@ -30,7 +30,7 @@ Application* app = &App;
 static const std::array<OutputLayout, BH_LAYOUT_TACTSUITX16_SIZE> bhLayout = { BH_LAYOUT_TACTSUITX16 };
 
 // Ouput indices, responsible for x40 => x16 grouping
-static const std::array<std::uint8_t,BH_LAYOUT_TACTSUITX16_GROUPS_SIZE> layoutGroups = BH_LAYOUT_TACTSUITX16_GROUPS;
+static const std::array<std::uint8_t, BH_LAYOUT_TACTSUITX16_GROUPS_SIZE> layoutGroups = BH_LAYOUT_TACTSUITX16_GROUPS;
 
 void setupMode()
 {
@@ -74,13 +74,13 @@ void setupMode()
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
     auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
     batteryVoltageSensor->addFilters({
-        new MultiplyFilter(3.3F), // Convert to raw pin voltage
-        new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
+      new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
+      new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
     });
-    auto* batteryTask = new SensorUpdateTask<SimpleSensorDecorator<float>>(
-            batteryVoltageSensor,
-            SENSESHIFT_BATTERY_SAMPLE_RATE,
-            { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask(
+      batteryVoltageSensor,
+      SENSESHIFT_BATTERY_SAMPLE_RATE,
+      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     batteryTask->begin();
 
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
index 0057140f..6454ed61 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
@@ -83,13 +83,13 @@ void setupMode()
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
     auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
     batteryVoltageSensor->addFilters({
-        new MultiplyFilter(3.3F), // Convert to raw pin voltage
-        new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
+      new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
+      new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
     });
-    auto* batteryTask = new SensorUpdateTask<SimpleSensorDecorator<float>>(
-            batteryVoltageSensor,
-            SENSESHIFT_BATTERY_SAMPLE_RATE,
-            { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask(
+      batteryVoltageSensor,
+      SENSESHIFT_BATTERY_SAMPLE_RATE,
+      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     batteryTask->begin();
 
diff --git a/firmware/mode_configs/bhaptics/tactvisor.cpp b/firmware/mode_configs/bhaptics/tactvisor.cpp
index f47cf4f5..1135d457 100644
--- a/firmware/mode_configs/bhaptics/tactvisor.cpp
+++ b/firmware/mode_configs/bhaptics/tactvisor.cpp
@@ -58,13 +58,13 @@ void setupMode()
 #if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
     auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
     batteryVoltageSensor->addFilters({
-        new MultiplyFilter(3.3F), // Convert to raw pin voltage
-        new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
+      new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
+      new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
     });
-    auto* batteryTask = new SensorUpdateTask<SimpleSensorDecorator<float>>(
-            batteryVoltageSensor,
-            SENSESHIFT_BATTERY_SAMPLE_RATE,
-            { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask(
+      batteryVoltageSensor,
+      SENSESHIFT_BATTERY_SAMPLE_RATE,
+      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     batteryTask->begin();
 
diff --git a/lib/battery/senseshift/battery/sensor.hpp b/lib/battery/senseshift/battery/sensor.hpp
index fdfa525d..19cfc6de 100644
--- a/lib/battery/senseshift/battery/sensor.hpp
+++ b/lib/battery/senseshift/battery/sensor.hpp
@@ -26,12 +26,13 @@ namespace SenseShift::Battery {
         using VoltageType = typename BatteryState::VoltageType;
         using VoltageSource = ::SenseShift::Input::Sensor<VoltageType>;
 
-        LookupTableInterpolateBatterySensor(
-          VoltageSource* voltage_source,
-          Container* lookup_table
-        ) : IBatterySensor(), voltage_source_(voltage_source), lookup_table_(lookup_table) {}
+        LookupTableInterpolateBatterySensor(VoltageSource* voltage_source, Container* lookup_table) :
+          IBatterySensor(), voltage_source_(voltage_source), lookup_table_(lookup_table)
+        {
+        }
 
-        void init() override {
+        void init() override
+        {
             this->voltage_source_->init();
             this->voltage_source_->addValueCallback([this](VoltageType voltage) {
                 // Current level in % (0.0 - 1.0)
@@ -40,7 +41,7 @@ namespace SenseShift::Battery {
                 LOG_D("battery.sensor", "voltage=%f, level=%f", voltage, level);
 
                 const BatteryState value = {
-                  .level = static_cast<typename BatteryState::LevelType>(level * BatteryState::MAX_LEVEL),
+                    .level = static_cast<typename BatteryState::LevelType>(level * BatteryState::MAX_LEVEL),
                 };
 
                 this->publishState(value);
@@ -48,8 +49,9 @@ namespace SenseShift::Battery {
         }
 
       protected:
-        [[nodiscard]] auto lookupInterpolateLevel(VoltageType voltage) -> float {
-            return ::SenseShift::lookup_interpolate<VoltageType, float, Container>(*this->lookup_table_, voltage);
+        [[nodiscard]] auto lookupInterpolateLevel(VoltageType voltage) -> float
+        {
+            return ::SenseShift::lookup_table_interpolate<VoltageType, float, Container>(*this->lookup_table_, voltage);
         }
 
       private:
diff --git a/lib/core/senseshift/core/helpers.hpp b/lib/core/senseshift/core/helpers.hpp
index 14d8e897..1d4fb8b1 100644
--- a/lib/core/senseshift/core/helpers.hpp
+++ b/lib/core/senseshift/core/helpers.hpp
@@ -73,13 +73,13 @@ namespace SenseShift {
     /// \param value
     ///
     /// \return
-    template <typename Tp, typename To, typename Container>
-    auto lookup_interpolate(Container const& lookup_table, Tp value) -> To
+    template<typename Tp, typename To, typename Container>
+    auto lookup_table_interpolate(Container const& lookup_table, Tp value) -> To
     {
         static_assert(std::is_same_v<typename Container::key_type, Tp>);
         static_assert(std::is_same_v<typename Container::mapped_type, To>);
-        static_assert(std::is_arithmetic_v<Tp>, "lookup_interpolate only supports arithmetic types");
-        static_assert(std::is_arithmetic_v<To>, "lookup_interpolate only supports arithmetic types");
+        static_assert(std::is_arithmetic_v<Tp>, "lookup_table_interpolate only supports arithmetic types");
+        static_assert(std::is_arithmetic_v<To>, "lookup_table_interpolate only supports arithmetic types");
 
         // If the value is outside the range of the lookup table, return the closest value
         if (value <= lookup_table.begin()->first) {
@@ -102,20 +102,21 @@ namespace SenseShift {
     template<typename... X>
     class CallbackManager;
 
-     /// Helper class to allow having multiple subscribers to a callback.
-     ///
-     /// \tparam Ts The arguments for the callbacks, wrapped in void().
+    /// Helper class to allow having multiple subscribers to a callback.
+    ///
+    /// \tparam Ts The arguments for the callbacks, wrapped in void().
     template<typename... Ts>
     class CallbackManager<void(Ts...)> {
       public:
         using CallbackType = std::function<void(Ts...)>;
 
         /// Add a callback to the list.
-        void add(std::function<void(Ts...)> &&callback) { this->callbacks_.push_back(std::move(callback)); }
+        void add(std::function<void(Ts...)>&& callback) { this->callbacks_.push_back(std::move(callback)); }
 
         /// Call all callbacks in this manager.
-        void call(Ts... args) {
-            for (auto &callback : this->callbacks_) {
+        void call(Ts... args)
+        {
+            for (auto& callback : this->callbacks_) {
                 callback(args...);
             }
         }
@@ -127,4 +128,4 @@ namespace SenseShift {
       private:
         std::vector<std::function<void(Ts...)>> callbacks_;
     };
-}  // namespace SenseShift
+} // namespace SenseShift
diff --git a/lib/freertos/senseshift/freertos/input/sensor.hpp b/lib/freertos/senseshift/freertos/input/sensor.hpp
index fbd34918..2a31d67d 100644
--- a/lib/freertos/senseshift/freertos/input/sensor.hpp
+++ b/lib/freertos/senseshift/freertos/input/sensor.hpp
@@ -6,37 +6,5 @@
 #include <cstdint>
 
 namespace SenseShift::FreeRTOS::Input {
-    /// Sensor update task
-    template<typename Sensor>
-    class SensorUpdateTask : public Task<SensorUpdateTask<Sensor>> {
-        static_assert(std::is_same_v<decltype(&Sensor::init), void (Sensor::*)()>);
-        static_assert(std::is_same_v<decltype(&Sensor::tick), void (Sensor::*)()>);
 
-      public:
-        SensorUpdateTask(Sensor* sensor, std::uint32_t updateDelay, TaskConfig taskConfig) :
-          Task<SensorUpdateTask>(taskConfig), sensor_(sensor), updateDelay_(updateDelay) {
-            log_i("creating SensorUpdateTask: %s", taskConfig.name);
-        };
-
-        void begin() override {
-            this->sensor_->init();
-            this->Task<SensorUpdateTask>::begin();
-        }
-
-      protected:
-        [[noreturn]] void run()
-        {
-            while (true) {
-                this->sensor_->tick();
-                delay(this->updateDelay_);
-                // log_i("high watermark %d", uxTaskGetStackHighWaterMark(NULL));
-            }
-        }
-
-      private:
-        friend class Task<SensorUpdateTask>;
-
-        Sensor* sensor_;
-        std::uint32_t updateDelay_;
-    };
 } // namespace SenseShift::FreeRTOS::Input
diff --git a/lib/freertos/senseshift/freertos/task.hpp b/lib/freertos/senseshift/freertos/task.hpp
index ea7c942a..9bea8bd8 100644
--- a/lib/freertos/senseshift/freertos/task.hpp
+++ b/lib/freertos/senseshift/freertos/task.hpp
@@ -39,8 +39,9 @@ namespace SenseShift::FreeRTOS {
         friend class Task;
 
       public:
-        explicit Task(TaskConfig& config) : taskConfig(config) {
-            log_i("creating SensorUpdateTask: %s", taskConfig.name);
+        explicit Task(TaskConfig& config) : taskConfig(config)
+        {
+            log_i("creating ComponentUpdateTask: %s", taskConfig.name);
         };
         virtual ~Task()
         {
@@ -83,17 +84,17 @@ namespace SenseShift::FreeRTOS {
             uxArraySize = uxTaskGetSystemState(pxTaskStatusArray, uxArraySize, NULL);
             for (UBaseType_t i = 0; i < uxArraySize; i++) {
                 log_i(
-                        "Task: %s\n\tStack High Watermark: %d\n\tState: %d\n",
-                        pxTaskStatusArray[i].pcTaskName,
-                        pxTaskStatusArray[i].usStackHighWaterMark,
-                        pxTaskStatusArray[i].eCurrentState
+                  "Task: %s\n\tStack High Watermark: %d\n\tState: %d\n",
+                  pxTaskStatusArray[i].pcTaskName,
+                  pxTaskStatusArray[i].usStackHighWaterMark,
+                  pxTaskStatusArray[i].eCurrentState
                 );
 
                 if (pxTaskStatusArray[i].usStackHighWaterMark < 20) {
                     log_w(
-                            "Warning: Task %s has low stack space, only %dB awailable!",
-                            pxTaskStatusArray[i].pcTaskName,
-                            pxTaskStatusArray[i].usStackHighWaterMark * 4
+                      "Warning: Task %s has low stack space, only %dB awailable!",
+                      pxTaskStatusArray[i].pcTaskName,
+                      pxTaskStatusArray[i].usStackHighWaterMark * 4
                     );
                 }
             }
@@ -110,4 +111,39 @@ namespace SenseShift::FreeRTOS {
             task->run();
         }
     };
+
+    template<typename Tp>
+    class ComponentUpdateTask : public Task<ComponentUpdateTask<Tp>> {
+        static_assert(std::is_same_v<decltype(&Tp::init), void (Tp::*)()>);
+        static_assert(std::is_same_v<decltype(&Tp::tick), void (Tp::*)()>);
+
+      public:
+        ComponentUpdateTask(Tp* component, std::uint32_t updateDelay, TaskConfig taskConfig) :
+          Task<ComponentUpdateTask>(taskConfig), component_(component), updateDelay_(updateDelay)
+        {
+            log_i("creating ComponentUpdateTask: %s", taskConfig.name);
+        };
+
+        void begin() override
+        {
+            this->component_->init();
+            this->Task<ComponentUpdateTask>::begin();
+        }
+
+      protected:
+        [[noreturn]] void run()
+        {
+            while (true) {
+                this->component_->tick();
+                delay(this->updateDelay_);
+                // log_i("high watermark %d", uxTaskGetStackHighWaterMark(NULL));
+            }
+        }
+
+      private:
+        friend class Task<ComponentUpdateTask>;
+
+        Tp* component_;
+        std::uint32_t updateDelay_;
+    };
 } // namespace SenseShift::FreeRTOS
diff --git a/lib/io/senseshift/input/filter.hpp b/lib/io/senseshift/input/filter.hpp
index a86ef341..75f65dc2 100644
--- a/lib/io/senseshift/input/filter.hpp
+++ b/lib/io/senseshift/input/filter.hpp
@@ -38,10 +38,7 @@ namespace SenseShift::Input::Filter {
       public:
         explicit AddFilter(Tp offset) : offset_(offset){};
 
-        auto filter(ISimpleSensor<Tp>* /*sensor*/, Tp value) -> Tp override
-        {
-            return value + this->offset_;
-        }
+        auto filter(ISimpleSensor<Tp>* /*sensor*/, Tp value) -> Tp override { return value + this->offset_; }
 
       private:
         Tp offset_;
@@ -66,10 +63,7 @@ namespace SenseShift::Input::Filter {
       public:
         explicit SubtractFilter(Tp offset) : offset_(offset){};
 
-        auto filter(ISimpleSensor<Tp>* /*sensor*/, Tp value) -> Tp override
-        {
-            return value - this->offset_;
-        }
+        auto filter(ISimpleSensor<Tp>* /*sensor*/, Tp value) -> Tp override { return value - this->offset_; }
 
       private:
         Tp offset_;
@@ -80,12 +74,9 @@ namespace SenseShift::Input::Filter {
       public:
         explicit MultiplyFilter(Tp factor) : factor_(factor){};
 
-        auto filter(ISimpleSensor<Tp>* /*sensor*/, Tp value) -> Tp override
-        {
-            return value * this->factor_;
-        }
+        auto filter(ISimpleSensor<Tp>* /*sensor*/, Tp value) -> Tp override { return value * this->factor_; }
 
-    private:
+      private:
         Tp factor_;
     };
 
@@ -131,12 +122,9 @@ namespace SenseShift::Input::Filter {
       public:
         using Lambda = std::function<Tp(Tp)>;
 
-        explicit LambdaFilter(Lambda filter) : filter_(std::move(filter)) {};
+        explicit LambdaFilter(Lambda filter) : filter_(std::move(filter)){};
 
-        auto filter(ISimpleSensor<Tp>* /*sensor*/, Tp value) -> Tp override
-        {
-            return this->filter_(value);
-        }
+        auto filter(ISimpleSensor<Tp>* /*sensor*/, Tp value) -> Tp override { return this->filter_(value); }
 
       private:
         Lambda filter_;
@@ -147,7 +135,10 @@ namespace SenseShift::Input::Filter {
     class SampleAverageFilter : public IFilter<Tp> {
         static_assert(std::is_arithmetic_v<Tp>, "SampleAverageFilter only supports arithmetic types");
         static_assert(std::is_same_v<typename Sensor::ValueType, Tp>, "Sensor type must match filter type");
-        static_assert(std::is_same_v<decltype(&Sensor::readRawValue), void (Sensor::*)()>, "Can only use sensors with readRawValue()");
+        static_assert(
+          std::is_same_v<decltype(&Sensor::readRawValue), void (Sensor::*)()>,
+          "Can only use sensors with readRawValue()"
+        );
 
       public:
         explicit SampleAverageFilter(std::size_t size) : size_(size){};
@@ -173,10 +164,12 @@ namespace SenseShift::Input::Filter {
     template<typename Tp, typename Sensor>
     class SampleMedianFilter : public IFilter<Tp> {
         static_assert(std::is_same_v<typename Sensor::ValueType, Tp>, "Sensor type must match filter type");
-        // static_assert(std::is_same_v<decltype(&Sensor::readRawValue), Tp (Sensor::*)()>, "Can only use sensors with readRawValue()");
+        // static_assert(std::is_same_v<decltype(&Sensor::readRawValue), Tp (Sensor::*)()>, "Can only use sensors with
+        // readRawValue()");
 
       public:
-        explicit SampleMedianFilter(std::size_t size_) : size_(size_) {
+        explicit SampleMedianFilter(std::size_t size_) : size_(size_)
+        {
             // allocate the array
             this->values = new Tp[size_];
         };
@@ -213,7 +206,7 @@ namespace SenseShift::Input::Filter {
         static_assert(std::is_arithmetic_v<Tp>, "SlidingWindowAverageFilter only supports arithmetic types");
 
       public:
-        explicit SlidingWindowMovingAverageFilter(size_t window_size) : window_size_(window_size) { };
+        explicit SlidingWindowMovingAverageFilter(size_t window_size) : window_size_(window_size){};
 
         auto filter(ISimpleSensor<Tp>* /*sensor*/, Tp value) -> Tp override
         {
@@ -244,7 +237,7 @@ namespace SenseShift::Input::Filter {
         static_assert(std::is_arithmetic_v<Tp>, "ExponentialMovingAverageFilter only supports arithmetic types");
 
       public:
-        explicit ExponentialMovingAverageFilter(float alpha) : alpha_(alpha) { };
+        explicit ExponentialMovingAverageFilter(float alpha) : alpha_(alpha){};
 
         auto filter(ISimpleSensor<Tp>* /*sensor*/, Tp value) -> Tp override
         {
@@ -260,8 +253,7 @@ namespace SenseShift::Input::Filter {
 
     /// Deadzone filter. Clamps acc_ to center if it is within the deadzone.
     /// Usually used to filter out noise in the joystick.
-    class CenterDeadzoneFilter : public IFilter<float>
-    {
+    class CenterDeadzoneFilter : public IFilter<float> {
       public:
         explicit CenterDeadzoneFilter(float deadzone) : deadzone_(deadzone){};
 
@@ -271,28 +263,31 @@ namespace SenseShift::Input::Filter {
             return deviation < deadzone_ ? CENTER : value;
         }
 
-    private:
+      private:
         static constexpr float CENTER = 0.5F;
 
         float deadzone_;
     };
 
+    /// Interpolates the value from the lookup table.
+    /// Can be used to determine battery level from the voltage.
+    ///
+    /// \tparam Tp Type of the lookup table values.
     /// \tparam Container Type of the lookup table container.
-    template <typename Tp, typename Container>
-    class LookupTableInterpolationFilter : public IFilter<Tp>
-    {
+    template<typename Tp, typename Container>
+    class LookupTableInterpolationFilter : public IFilter<Tp> {
         static_assert(std::is_same_v<typename Container::value_type, Tp>);
         static_assert(std::is_arithmetic_v<Tp>, "LookupTableInterpolationFilter only supports arithmetic types");
 
       public:
-        explicit LookupTableInterpolationFilter(Container const& lookup_table) : lookup_table_(lookup_table) {};
+        explicit LookupTableInterpolationFilter(Container const& lookup_table) : lookup_table_(lookup_table){};
 
         auto filter(ISimpleSensor<float>* /*sensor*/, Tp value) -> Tp override
         {
-            return SenseShift::lookup_interpolate<Tp, Container>(this->lookup_table_, value);
+            return SenseShift::lookup_table_interpolate<Tp, Container>(this->lookup_table_, value);
         }
 
       private:
         Container const& lookup_table_;
     };
-} // namespace SenseShift::Input
+} // namespace SenseShift::Input::Filter

From 13f3b54ec42c1c54fd5d4598af1166131559e5f3 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Thu, 8 Feb 2024 14:22:20 +0400
Subject: [PATCH 57/82] test(Gestures): refactor old OG tests

---
 test/test_body_gestures/main.cpp      | 104 ++++++++++++++++++++++++++
 test/test_opengloves_gesture/main.cpp | 102 -------------------------
 2 files changed, 104 insertions(+), 102 deletions(-)
 create mode 100644 test/test_body_gestures/main.cpp
 delete mode 100644 test/test_opengloves_gesture/main.cpp

diff --git a/test/test_body_gestures/main.cpp b/test/test_body_gestures/main.cpp
new file mode 100644
index 00000000..57d543ea
--- /dev/null
+++ b/test/test_body_gestures/main.cpp
@@ -0,0 +1,104 @@
+#include <senseshift/body/hands/input/gesture.hpp>
+#include <senseshift/input/sensor.hpp>
+#include <unity.h>
+
+using namespace SenseShift::Input;
+using namespace SenseShift::Body::Hands::Input;
+
+void test_gesture_trigger(void)
+{
+    float threshold = 0.5f;
+    auto* index = new FloatSensor();
+
+    auto* gesture = new TriggerGesture(index, threshold, true);
+
+    index->publishState(0.4f);
+    TEST_ASSERT_FALSE(gesture->getValue());
+
+    index->publishState(0.6f);
+    TEST_ASSERT_TRUE(gesture->getValue());
+}
+
+void test_gesture_grab(void)
+{
+    float threshold = 0.5f;
+
+    auto* index = new FloatSensor();
+    auto* middle = new FloatSensor();
+    auto* ring = new FloatSensor();
+    auto* pinky = new FloatSensor();
+
+    auto* gesture = new GrabGesture(
+      {
+        .index = index,
+        .middle = middle,
+        .ring = ring,
+        .pinky = pinky,
+      },
+      threshold,
+      true
+    );
+
+    TEST_ASSERT_FALSE(gesture->getValue());
+
+    index->publishState(0.6);
+    TEST_ASSERT_FALSE(gesture->getValue());
+
+    middle->publishState(0.6);
+    TEST_ASSERT_FALSE(gesture->getValue());
+
+    ring->publishState(0.6);
+    TEST_ASSERT_FALSE(gesture->getValue());
+
+    pinky->publishState(0.6);
+    TEST_ASSERT_TRUE(gesture->getValue());
+}
+
+void test_gesture_pinch(void)
+{
+    float threshold = 0.5f;
+
+    auto* thumb = new FloatSensor();
+    auto* index = new FloatSensor();
+
+    auto* gesture = new PinchGesture({ .thumb = thumb, .index = index }, threshold, true);
+
+    TEST_ASSERT_FALSE(gesture->getValue());
+
+    thumb->publishState(0.6f);
+    TEST_ASSERT_FALSE(gesture->getValue());
+
+    index->publishState(0.6f);
+    TEST_ASSERT_TRUE(gesture->getValue());
+}
+
+int process(void)
+{
+    UNITY_BEGIN();
+
+    RUN_TEST(test_gesture_grab);
+    RUN_TEST(test_gesture_trigger);
+    RUN_TEST(test_gesture_pinch);
+
+    return UNITY_END();
+}
+
+#ifdef ARDUINO
+
+#include <Arduino.h>
+
+void setup(void)
+{
+    process();
+}
+
+void loop(void) {}
+
+#else
+
+int main(int argc, char** argv)
+{
+    return process();
+}
+
+#endif
diff --git a/test/test_opengloves_gesture/main.cpp b/test/test_opengloves_gesture/main.cpp
deleted file mode 100644
index db4f7057..00000000
--- a/test/test_opengloves_gesture/main.cpp
+++ /dev/null
@@ -1,102 +0,0 @@
-#include <senseshift/body/hands/gestures/grab.hpp>
-#include <unity.h>
-
-using namespace OpenGloves;
-
-class TestCurlFinger : public ICurl {
-  public:
-    uint16_t value;
-
-    TestCurlFinger(uint16_t initialValue = 0) : value(initialValue){};
-
-    uint16_t getCurl() override { return this->value; }
-};
-
-void test_gesture_grab(void)
-{
-    uint16_t threshold = 2047;
-
-    auto index = TestCurlFinger(threshold - 1);
-    auto middle = TestCurlFinger(threshold - 1);
-    auto ring = TestCurlFinger(threshold - 1);
-    auto pinky = TestCurlFinger(threshold - 1);
-
-    auto gesture = GrabGesture(index, middle, ring, pinky, threshold);
-
-    TEST_ASSERT_FALSE(gesture.getValue());
-
-    index.value = threshold + 1;
-    TEST_ASSERT_FALSE(gesture.getValue());
-
-    middle.value = threshold + 1;
-    TEST_ASSERT_FALSE(gesture.getValue());
-
-    ring.value = threshold + 1;
-    TEST_ASSERT_FALSE(gesture.getValue());
-
-    pinky.value = threshold + 1;
-    TEST_ASSERT_TRUE(gesture.getValue());
-}
-
-void test_gesture_trigger(void)
-{
-    uint16_t threshold = 2047;
-
-    auto index = TestCurlFinger(threshold - 1);
-
-    auto gesture = TriggerGesture(index, threshold);
-
-    TEST_ASSERT_FALSE(gesture.getValue());
-
-    index.value = threshold + 1;
-    TEST_ASSERT_TRUE(gesture.getValue());
-}
-
-void test_gesture_pinch(void)
-{
-    uint16_t threshold = 2047;
-
-    auto index = TestCurlFinger(threshold - 1);
-    auto middle = TestCurlFinger(threshold - 1);
-
-    auto gesture = PinchGesture(index, middle, threshold);
-
-    TEST_ASSERT_FALSE(gesture.getValue());
-
-    index.value = threshold + 1;
-    TEST_ASSERT_FALSE(gesture.getValue());
-
-    middle.value = threshold + 1;
-    TEST_ASSERT_TRUE(gesture.getValue());
-}
-
-int process(void)
-{
-    UNITY_BEGIN();
-
-    RUN_TEST(test_gesture_grab);
-    RUN_TEST(test_gesture_trigger);
-    RUN_TEST(test_gesture_pinch);
-
-    return UNITY_END();
-}
-
-#ifdef ARDUINO
-
-#include <Arduino.h>
-
-void setup(void)
-{
-    process();
-}
-
-void loop(void) {}
-
-#else
-
-int main(int argc, char** argv)
-{
-    return process();
-}
-
-#endif

From 809f14e68d27f67c2422e284f27060c6999755c8 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Thu, 8 Feb 2024 18:30:24 +0400
Subject: [PATCH 58/82] fix: sensor template deduction

---
 firmware/mode_configs/bhaptics/tactal.cpp     |  2 +-
 firmware/mode_configs/bhaptics/tactglove.cpp  |  2 +-
 firmware/mode_configs/bhaptics/tactosy2.cpp   |  2 +-
 firmware/mode_configs/bhaptics/tactosyf.cpp   |  2 +-
 firmware/mode_configs/bhaptics/tactosyh.cpp   |  2 +-
 .../mode_configs/bhaptics/tactsuit_x16.cpp    |  2 +-
 .../bhaptics/tactsuit_x16_pca9685.cpp         |  2 +-
 .../mode_configs/bhaptics/tactsuit_x40.cpp    |  2 +-
 firmware/mode_configs/bhaptics/tactvisor.cpp  |  2 +-
 .../mode_configs/opengloves/opengloves.cpp    |  2 +-
 lib/io/senseshift/input/analog_threshold.hpp  | 51 +++++++++++--------
 test/test_body_gestures/main.cpp              | 15 ++++--
 12 files changed, 52 insertions(+), 34 deletions(-)

diff --git a/firmware/mode_configs/bhaptics/tactal.cpp b/firmware/mode_configs/bhaptics/tactal.cpp
index efab08b5..e68ee5a1 100644
--- a/firmware/mode_configs/bhaptics/tactal.cpp
+++ b/firmware/mode_configs/bhaptics/tactal.cpp
@@ -61,7 +61,7 @@ void setupMode()
       new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
       new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
     });
-    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask(
+    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask<SimpleSensorDecorator<float>>(
       batteryVoltageSensor,
       SENSESHIFT_BATTERY_SAMPLE_RATE,
       { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
diff --git a/firmware/mode_configs/bhaptics/tactglove.cpp b/firmware/mode_configs/bhaptics/tactglove.cpp
index 8d3fa066..024bf872 100644
--- a/firmware/mode_configs/bhaptics/tactglove.cpp
+++ b/firmware/mode_configs/bhaptics/tactglove.cpp
@@ -69,7 +69,7 @@ void setupMode()
       new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
       new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
     });
-    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask(
+    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask<SimpleSensorDecorator<float>>(
       batteryVoltageSensor,
       SENSESHIFT_BATTERY_SAMPLE_RATE,
       { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
diff --git a/firmware/mode_configs/bhaptics/tactosy2.cpp b/firmware/mode_configs/bhaptics/tactosy2.cpp
index f18515f9..21dfa3c1 100644
--- a/firmware/mode_configs/bhaptics/tactosy2.cpp
+++ b/firmware/mode_configs/bhaptics/tactosy2.cpp
@@ -62,7 +62,7 @@ void setupMode()
       new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
       new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
     });
-    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask(
+    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask<SimpleSensorDecorator<float>>(
       batteryVoltageSensor,
       SENSESHIFT_BATTERY_SAMPLE_RATE,
       { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
diff --git a/firmware/mode_configs/bhaptics/tactosyf.cpp b/firmware/mode_configs/bhaptics/tactosyf.cpp
index 886857c3..9b9a4a60 100644
--- a/firmware/mode_configs/bhaptics/tactosyf.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyf.cpp
@@ -63,7 +63,7 @@ void setupMode()
       new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
       new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
     });
-    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask(
+    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask<SimpleSensorDecorator<float>>(
       batteryVoltageSensor,
       SENSESHIFT_BATTERY_SAMPLE_RATE,
       { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
diff --git a/firmware/mode_configs/bhaptics/tactosyh.cpp b/firmware/mode_configs/bhaptics/tactosyh.cpp
index 5646060f..11fa6192 100644
--- a/firmware/mode_configs/bhaptics/tactosyh.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyh.cpp
@@ -63,7 +63,7 @@ void setupMode()
       new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
       new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
     });
-    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask(
+    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask<SimpleSensorDecorator<float>>(
       batteryVoltageSensor,
       SENSESHIFT_BATTERY_SAMPLE_RATE,
       { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
index 7a448b06..72d82490 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
@@ -72,7 +72,7 @@ void setupMode()
       new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
       new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
     });
-    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask(
+    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask<SimpleSensorDecorator<float>>(
       batteryVoltageSensor,
       SENSESHIFT_BATTERY_SAMPLE_RATE,
       { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
index 38ea195f..d5826492 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
@@ -77,7 +77,7 @@ void setupMode()
       new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
       new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
     });
-    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask(
+    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask<SimpleSensorDecorator<float>>(
       batteryVoltageSensor,
       SENSESHIFT_BATTERY_SAMPLE_RATE,
       { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
index 6454ed61..ce0b0a8e 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
@@ -86,7 +86,7 @@ void setupMode()
       new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
       new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
     });
-    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask(
+    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask<SimpleSensorDecorator<float>>(
       batteryVoltageSensor,
       SENSESHIFT_BATTERY_SAMPLE_RATE,
       { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
diff --git a/firmware/mode_configs/bhaptics/tactvisor.cpp b/firmware/mode_configs/bhaptics/tactvisor.cpp
index 1135d457..128f2199 100644
--- a/firmware/mode_configs/bhaptics/tactvisor.cpp
+++ b/firmware/mode_configs/bhaptics/tactvisor.cpp
@@ -61,7 +61,7 @@ void setupMode()
       new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
       new VoltageDividerFilter(27000.0F, 100000.0F), // Convert to voltage divider voltage
     });
-    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask(
+    auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask<SimpleSensorDecorator<float>>(
       batteryVoltageSensor,
       SENSESHIFT_BATTERY_SAMPLE_RATE,
       { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp
index 6666dc3f..d25e5733 100644
--- a/firmware/mode_configs/opengloves/opengloves.cpp
+++ b/firmware/mode_configs/opengloves/opengloves.cpp
@@ -122,7 +122,7 @@ void setupMode()
 #endif
 
 #if GESTURE_TRIGGER_ENABLED
-    auto* trigger = new AnalogThresholdSensor(index_curl_sensor, GESTURE_TRIGGER_THRESHOLD);
+    auto* trigger = new TriggerGesture(index_curl_sensor, GESTURE_TRIGGER_THRESHOLD);
     input_sensors.trigger.press = trigger;
 #elif BUTTON_TRIGGER_ENABLED
     auto trigger = new BUTTON_CLASS(PIN_BUTTON_TRIGGER, BUTTON_TRIGGER_INVERT);
diff --git a/lib/io/senseshift/input/analog_threshold.hpp b/lib/io/senseshift/input/analog_threshold.hpp
index eb03eec5..46eb53d4 100644
--- a/lib/io/senseshift/input/analog_threshold.hpp
+++ b/lib/io/senseshift/input/analog_threshold.hpp
@@ -1,55 +1,66 @@
 #pragma once
 
 #include "senseshift/input/sensor.hpp"
+#include <type_traits>
 
 namespace SenseShift::Input {
-    template<typename Tp>
+    template<typename Tp = float>
     class AnalogThresholdSensor : public BinarySensor, ITickable {
-    public:
+      public:
         /// Analog threshold sensor with hysteresis.
         ///
         /// \param source The source sensor.
         /// \param threshold_upper Upper threshold, that needs to be crossed to transition from `low` to `high` states.
         /// \param threshold_lower Lower threshold, that needs to be crossed to transition from `high` to `low` states.
         explicit AnalogThresholdSensor(
-                ::SenseShift::Input::Sensor<Tp>* source,
-                Tp threshold_upper,
-                float threshold_lower,
-                bool attach_callbacks = false
-        ) : source_(source), threshold_upper_(threshold_upper), threshold_lower_(threshold_lower), attach_callbacks_(attach_callbacks) {}
+          ::SenseShift::Input::Sensor<Tp>* source, Tp threshold_upper, Tp threshold_lower, bool attach_callbacks = false
+        ) :
+          source_(source),
+          threshold_upper_(threshold_upper),
+          threshold_lower_(threshold_lower),
+          attach_callbacks_(attach_callbacks)
+        {
+        }
 
         /// \param source The source sensor.
         /// \param threshold Threshold, that will be used for both upper and lower thresholds.
-        template <typename U = Tp, std::enable_if_t<std::is_same_v<U, float>, int> = 0>
+        template<typename U = Tp, std::enable_if_t<std::is_same_v<U, float>, int> = 0>
         explicit AnalogThresholdSensor(
-                ::SenseShift::Input::Sensor<Tp>* source,
-                float threshold = 0.5f,
-                bool attach_callbacks = false
-        ) : AnalogThresholdSensor(source, threshold, threshold, attach_callbacks) { }
+          ::SenseShift::Input::Sensor<Tp>* source, float threshold = 0.5f, bool attach_callbacks = false
+        ) :
+          AnalogThresholdSensor(source, threshold, threshold, attach_callbacks)
+        {
+        }
 
-        void init() override {
+        void init() override
+        {
             SS_SUBSENSOR_INIT(this->source_, this->attach_callbacks_, [this](Tp /*value*/) {
                 this->recalculateState();
             });
         }
 
-        void tick() override {
+        void tick() override
+        {
             if (this->attach_callbacks_) {
-                LOG_E("sensor.analog_threshold", "tick() called when attach_callbacks_ is true, infinite loop go wroom-wroom!");
+                LOG_E(
+                  "sensor.analog_threshold",
+                  "tick() called when attach_callbacks_ is true, infinite loop go wroom-wroom!"
+                );
             }
             this->recalculateState();
         }
 
-        void recalculateState() {
-            const auto sensor_value  = this->source_->getValue();
+        void recalculateState()
+        {
+            const auto sensor_value = this->source_->getValue();
             this->publishState(sensor_value >= (this->getValue() ? this->threshold_lower_ : this->threshold_upper_));
         }
 
-    private:
+      private:
         ::SenseShift::Input::Sensor<Tp>* source_;
 
-        float threshold_lower_, threshold_upper_;
+        Tp threshold_lower_, threshold_upper_;
 
         bool attach_callbacks_ = false;
     };
-}
\ No newline at end of file
+} // namespace SenseShift::Input
diff --git a/test/test_body_gestures/main.cpp b/test/test_body_gestures/main.cpp
index 57d543ea..5f9e800d 100644
--- a/test/test_body_gestures/main.cpp
+++ b/test/test_body_gestures/main.cpp
@@ -10,12 +10,14 @@ void test_gesture_trigger(void)
     float threshold = 0.5f;
     auto* index = new FloatSensor();
 
-    auto* gesture = new TriggerGesture(index, threshold, true);
+    auto* gesture = new TriggerGesture(index, threshold);
 
     index->publishState(0.4f);
+    gesture->tick();
     TEST_ASSERT_FALSE(gesture->getValue());
 
     index->publishState(0.6f);
+    gesture->tick();
     TEST_ASSERT_TRUE(gesture->getValue());
 }
 
@@ -35,22 +37,25 @@ void test_gesture_grab(void)
         .ring = ring,
         .pinky = pinky,
       },
-      threshold,
-      true
+      threshold
     );
 
     TEST_ASSERT_FALSE(gesture->getValue());
 
     index->publishState(0.6);
+    gesture->tick();
     TEST_ASSERT_FALSE(gesture->getValue());
 
     middle->publishState(0.6);
+    gesture->tick();
     TEST_ASSERT_FALSE(gesture->getValue());
 
     ring->publishState(0.6);
+    gesture->tick();
     TEST_ASSERT_FALSE(gesture->getValue());
 
     pinky->publishState(0.6);
+    gesture->tick();
     TEST_ASSERT_TRUE(gesture->getValue());
 }
 
@@ -61,14 +66,16 @@ void test_gesture_pinch(void)
     auto* thumb = new FloatSensor();
     auto* index = new FloatSensor();
 
-    auto* gesture = new PinchGesture({ .thumb = thumb, .index = index }, threshold, true);
+    auto* gesture = new PinchGesture({ .thumb = thumb, .index = index }, threshold);
 
     TEST_ASSERT_FALSE(gesture->getValue());
 
     thumb->publishState(0.6f);
+    gesture->tick();
     TEST_ASSERT_FALSE(gesture->getValue());
 
     index->publishState(0.6f);
+    gesture->tick();
     TEST_ASSERT_TRUE(gesture->getValue());
 }
 

From f43319a60840e6341e254c811518ea4152bdc861 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Thu, 8 Feb 2024 20:27:27 +0400
Subject: [PATCH 59/82] test(OpenGloves): update old alpha-encoding unit test

---
 .github/workflows/ci.yml                     |  20 +-
 lib/io/senseshift/input/calibration.hpp      | 105 +++++++---
 lib/opengloves/opengloves/opengloves.cpp     | 103 ++++++---
 lib/opengloves/opengloves/opengloves.hpp     |   2 +-
 test/test_opengloves/main.cpp                | 159 --------------
 test/test_opengloves_alpha_encoding/main.cpp | 210 +++++++++++--------
 test/test_opengloves_finger/main.cpp         | 177 ----------------
 7 files changed, 281 insertions(+), 495 deletions(-)
 delete mode 100644 test/test_opengloves/main.cpp
 delete mode 100644 test/test_opengloves_finger/main.cpp

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index bade01aa..6a12df0c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -21,7 +21,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        os: [ubuntu-latest]
+        os: [ ubuntu-latest ]
         target:
           - bhaptics_tactsuit_x16
           - bhaptics_tactsuit_x16_pca9685
@@ -32,10 +32,10 @@ jobs:
           - bhaptics_tactal
           - bhaptics_tactvisor
           - bhaptics_tactglove_right
-        battery_flag: [SENSESHIFT_BATTERY_ENABLED=true]
-        serial_plotter_flag: [SENSESHIFT_SERIAL_PLOTTER=false]
-        nimble_flag: [SENSESHIFT_BLE_USE_NIMBLE=false]
-        coverage: [false]
+        battery_flag: [ SENSESHIFT_BATTERY_ENABLED=true ]
+        serial_plotter_flag: [ SENSESHIFT_SERIAL_PLOTTER=false ]
+        nimble_flag: [ SENSESHIFT_BLE_USE_NIMBLE=false ]
+        coverage: [ false ]
 
         include:
           # Extra tests for x40, as it uses the most features
@@ -165,7 +165,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        os: [ubuntu-latest]
+        os: [ ubuntu-latest ]
         target:
           - lucidgloves-prototype3
           - lucidgloves-prototype4
@@ -176,20 +176,20 @@ jobs:
         comm_flag:
           - COMMUNICATION_PROTOCOL=OPENGLOVES_COMM_SERIAL
           - COMMUNICATION_PROTOCOL=OPENGLOVES_COMM_BTSERIAL
-        coverage: [false]
+        coverage: [ false ]
 
         include:
           - os: ubuntu-latest
             target: lucidgloves-prototype4-ffb
-            curl_calibration_flag: CALIBRATION_CURL="::SenseShift::Calibration::MinMaxCalibrator<uint16_t, 0, ANALOG_MAX>"
+            curl_calibration_flag: CALIBRATION_CURL="new ::SenseShift::Input::Calibration::MinMaxCalibrator<float>()"
             coverage: true
           - os: ubuntu-latest
             target: lucidgloves-prototype4-ffb
-            curl_calibration_flag: CALIBRATION_CURL="::SenseShift::Calibration::CenterPointDeviationCalibrator<uint16_t, 20, 270, 0, ANALOG_MAX>"
+            curl_calibration_flag: CALIBRATION_CURL="new ::SenseShift::Input::Calibration::CenterPointDeviationCalibrator<float>(0.66F, 0.005F)"
             coverage: true
           - os: ubuntu-latest
             target: lucidgloves-prototype4-ffb
-            curl_calibration_flag: CALIBRATION_CURL="::SenseShift::Calibration::FixedCenterPointDeviationCalibrator<uint16_t, 20, 270, 0, ANALOG_MAX>"
+            curl_calibration_flag: CALIBRATION_CURL="new ::SenseShift::Input::Calibration::FixedCenterPointDeviationCalibrator<float>(0.66F, 0.005F)"
             coverage: true
           - os: ubuntu-latest
             target: indexer-csf
diff --git a/lib/io/senseshift/input/calibration.hpp b/lib/io/senseshift/input/calibration.hpp
index 51412389..b5a271a5 100644
--- a/lib/io/senseshift/input/calibration.hpp
+++ b/lib/io/senseshift/input/calibration.hpp
@@ -33,21 +33,25 @@ namespace SenseShift::Input::Calibration {
       public:
         using ValueType = Tp;
 
-        explicit MinMaxCalibrator(Tp output_min, Tp output_max)
-            : output_min_(output_min), output_max_(output_max),
-              value_min_(output_max), value_max_(output_min) {}
+        explicit MinMaxCalibrator(Tp output_min, Tp output_max) :
+          output_min_(output_min), output_max_(output_max), value_min_(output_max), value_max_(output_min)
+        {
+        }
 
         template<typename U = Tp, std::enable_if_t<std::is_same_v<U, float>, int> = 0>
-        explicit MinMaxCalibrator(Tp output_min = 0.0F, Tp output_max = 1.0F)
-            : output_min_(output_min), output_max_(output_max),
-              value_min_(output_max), value_max_(output_min) {}
+        explicit MinMaxCalibrator(Tp output_min = 0.0F, Tp output_max = 1.0F) :
+          output_min_(output_min), output_max_(output_max), value_min_(output_max), value_max_(output_min)
+        {
+        }
 
-        void reset() override {
+        void reset() override
+        {
             value_min_ = output_max_;
             value_max_ = output_min_;
         }
 
-        void update(ValueType input) override {
+        void update(ValueType input) override
+        {
             // Update the min and the max.
             if (input < value_min_) {
                 value_min_ = input;
@@ -57,7 +61,8 @@ namespace SenseShift::Input::Calibration {
             }
         }
 
-        auto calibrate(ValueType input) const -> ValueType override {
+        auto calibrate(ValueType input) const -> ValueType override
+        {
             // This means we haven't had any calibration data yet.
             // Return a neutral value right in the middle of the output range.
             if (value_min_ > value_max_) {
@@ -73,7 +78,8 @@ namespace SenseShift::Input::Calibration {
             }
 
             // Map the input range to the output range.
-            ValueType output = ::SenseShift::remap<ValueType, ValueType>(input, value_min_, value_max_, output_min_, output_max_);
+            ValueType output =
+              ::SenseShift::remap<ValueType, ValueType>(input, value_min_, value_max_, output_min_, output_max_);
 
             // Lock the range to the output.
             return std::clamp(output, output_min_, output_max_);
@@ -89,43 +95,63 @@ namespace SenseShift::Input::Calibration {
 
     template<typename Tp>
     class CenterPointDeviationCalibrator : public ICalibrator<Tp> {
-        static_assert(std::is_arithmetic_v<Tp>, "CenterPointDeviationCalibrator only can be used with arithmetic types");
+        static_assert(
+          std::is_arithmetic_v<Tp>, "CenterPointDeviationCalibrator only can be used with arithmetic types"
+        );
 
       public:
         using ValueType = Tp;
 
-        CenterPointDeviationCalibrator(
-          Tp sensor_max, Tp driver_max_deviation, Tp output_min, Tp output_max
-        ) : sensor_max_(sensor_max), driver_max_deviation_(driver_max_deviation), output_min_(output_min),
-            output_max_(output_max), range_min_(sensor_max), range_max_(0) {}
+        CenterPointDeviationCalibrator(Tp sensor_max, Tp driver_max_deviation, Tp output_min, Tp output_max) :
+          sensor_max_(sensor_max),
+          driver_max_deviation_(driver_max_deviation),
+          output_min_(output_min),
+          output_max_(output_max),
+          range_min_(sensor_max),
+          range_max_(0)
+        {
+        }
 
         template<typename U = Tp, std::enable_if_t<std::is_same_v<U, float>, int> = 0>
         CenterPointDeviationCalibrator(
-            Tp sensor_max, Tp driver_max_deviation, Tp output_min = 0.0F, Tp output_max = 1.0F
-        ) : sensor_max_(sensor_max), driver_max_deviation_(driver_max_deviation), output_min_(output_min),
-            output_max_(output_max), range_min_(sensor_max), range_max_(0) {}
+          Tp sensor_max, Tp driver_max_deviation, Tp output_min = 0.0F, Tp output_max = 1.0F
+        ) :
+          sensor_max_(sensor_max),
+          driver_max_deviation_(driver_max_deviation),
+          output_min_(output_min),
+          output_max_(output_max),
+          range_min_(sensor_max),
+          range_max_(0)
+        {
+        }
 
-        void reset() override {
+        void reset() override
+        {
             this->range_min_ = this->sensor_max_;
             this->range_max_ = 0;
         }
 
-        void update(Tp input) override {
+        void update(Tp input) override
+        {
             // Update the min and the max.
             if (input < this->range_min_) {
-                this->range_min_ = ::SenseShift::remap<ValueType>(input, this->output_min_, this->output_max_, 0, this->sensor_max_);
+                this->range_min_ =
+                  ::SenseShift::remap<ValueType>(input, this->output_min_, this->output_max_, 0, this->sensor_max_);
             }
             if (input > this->range_max_) {
-                this->range_max_ = ::SenseShift::remap<ValueType>(input, this->output_min_, this->output_max_, 0, this->sensor_max_);
+                this->range_max_ =
+                  ::SenseShift::remap<ValueType>(input, this->output_min_, this->output_max_, 0, this->sensor_max_);
             }
         }
 
-        auto calibrate(ValueType input) const -> ValueType override {
+        auto calibrate(ValueType input) const -> ValueType override
+        {
             // Find the center point of the sensor, so we know how much we have deviated from it.
             Tp center = (this->range_min_ + this->range_max_) / 2.0F;
 
             // Map the input to the sensor range of motion.
-            int output = ::SenseShift::accurateMap<Tp>(input, this->output_min_, this->output_max_, 0, this->sensor_max_);
+            int output =
+              ::SenseShift::accurateMap<Tp>(input, this->output_min_, this->output_max_, 0, this->sensor_max_);
 
             // Find the deviation from the center and clamp it to the maximum that the driver supports.
             output = std::clamp<int>(output - center, -(this->driver_max_deviation_), this->driver_max_deviation_);
@@ -152,20 +178,39 @@ namespace SenseShift::Input::Calibration {
 
     template<typename Tp>
     class FixedCenterPointDeviationCalibrator : public ICalibrator<Tp> {
-        static_assert(std::is_arithmetic_v<Tp>, "FixedCenterPointDeviationCalibrator only can be used with arithmetic types");
+        static_assert(
+          std::is_arithmetic_v<Tp>, "FixedCenterPointDeviationCalibrator only can be used with arithmetic types"
+        );
 
       public:
         using ValueType = Tp;
 
         explicit FixedCenterPointDeviationCalibrator(
           Tp sensor_max, Tp driver_max_deviation, Tp output_min, Tp output_max
-        ) : sensor_max_(sensor_max), driver_max_deviation_(driver_max_deviation),
-            output_min_(output_min), output_max_(output_max) {}
+        ) :
+          sensor_max_(sensor_max),
+          driver_max_deviation_(driver_max_deviation),
+          output_min_(output_min),
+          output_max_(output_max)
+        {
+        }
+
+        template<typename U = Tp, std::enable_if_t<std::is_same_v<U, float>, int> = 0>
+        explicit FixedCenterPointDeviationCalibrator(
+          Tp sensor_max, Tp driver_max_deviation, Tp output_min = 0.0F, Tp output_max = 1.0F
+        ) :
+          sensor_max_(sensor_max),
+          driver_max_deviation_(driver_max_deviation),
+          output_min_(output_min),
+          output_max_(output_max)
+        {
+        }
 
         void reset() override {}
         void update(ValueType input) override {}
 
-        auto calibrate(ValueType input) const -> ValueType override {
+        auto calibrate(ValueType input) const -> ValueType override
+        {
             // Find the center point of the sensor, so we know how much we have deviated from it.
             Tp center = this->sensor_max_ / 2.0F;
 
@@ -185,10 +230,10 @@ namespace SenseShift::Input::Calibration {
             );
         }
 
-    private:
+      private:
         const Tp sensor_max_;
         const Tp driver_max_deviation_;
         const Tp output_min_;
         const Tp output_max_;
     };
-}  // namespace SenseShift::Input::Calibration
+} // namespace SenseShift::Input::Calibration
diff --git a/lib/opengloves/opengloves/opengloves.cpp b/lib/opengloves/opengloves/opengloves.cpp
index 951391b6..0411c6d9 100644
--- a/lib/opengloves/opengloves/opengloves.cpp
+++ b/lib/opengloves/opengloves/opengloves.cpp
@@ -1,9 +1,24 @@
 #include "opengloves/opengloves.hpp"
 
+#include <cmath>
 #include <cstdint>
 #include <cstdio>
 #include <variant>
 
+#ifdef OG_ENCODE_FAST
+template<typename Tp = int, typename Tu = float>
+auto ifloor(Tu x) -> Tp
+{
+    return (Tp) x - (x < (Tp) x);
+}
+
+template<typename Tp = int, typename Tu = float>
+auto iceil(Tu x) -> Tp
+{
+    return (Tp) x + (x > (Tp) x);
+}
+#endif
+
 namespace og {
 
     auto AlphaEncoder::encode_input(const InputData& input, char* buffer, size_t length) const -> size_t
@@ -32,52 +47,84 @@ namespace og {
             const auto peripheral = std::get<InputPeripheralData>(input);
             auto written = 0;
 
-            const auto& curl = peripheral.curl.fingers;
+            const auto& curls = peripheral.curl.fingers;
+            const auto& splays = peripheral.splay.fingers;
 #ifdef OG_ENCODE_FAST
-            for (auto i = 0; i < curl.size(); i++) {
-                const auto& finger = curl[i];
+            for (auto i = 0; i < curls.size(); i++) {
+                const auto& finger = curls[i];
                 const auto& finger_curl = finger.curl_total;
 
-                written +=
-                  snprintf(buffer + written, length - written, "%c%.0f", 'A' + i, finger_curl * MAX_ANALOG_VALUE);
+                written += snprintf(
+                  buffer + written,
+                  length - written,
+                  "%c%u",
+                  'A' + i,
+                  ifloor<std::uint8_t, float>(finger_curl * MAX_ANALOG_VALUE)
+                );
             }
 #else
-            for (auto i = 0; i < curl.size(); i++) {
-                const auto& finger = curl[i];
-                const auto& joints = finger.curl;
+            for (auto i = 0; i < curls.size(); i++) {
+                const auto& finger_curl = curls[i];
+                const auto& finger_splay = splays[i];
+                const auto finger_alpha_key = 'A' + i;
+
+                written += snprintf(
+                  buffer + written,
+                  length - written,
+                  "%c%.0f",
+                  finger_alpha_key,
+                  std::floor(finger_curl.curl_total * MAX_ANALOG_VALUE)
+                );
+
+                if (finger_splay > 0.0F) {
+                    written += snprintf(
+                      buffer + written,
+                      length - written,
+                      "(%cB)%.0f",
+                      finger_alpha_key,
+                      std::floor(finger_splay * MAX_ANALOG_VALUE)
+                    );
+                }
 
-                for (auto j = 0; j < joints.size(); j++) {
+                const auto& joints = finger_curl.curl;
+                for (auto j = 1; j < joints.size(); j++) {
                     const auto& joint = joints[j];
+                    const auto joint_alpha_key = 'A' + j;
 
-                    // skip if joint is 0.0, except total_curl (it is probably not enabled)
-                    if (joint == 0.0F && j != 0) {
+                    if (joint == 0.0F) {
                         continue;
                     }
 
-                    const auto& jointKey = AlphaEncoder::FINGER_CURL_JOINT_ALPHA_KEY[i][j];
-
-                    written +=
-                      snprintf(buffer + written, length - written, "%s%.0f", jointKey.data(), joint * MAX_ANALOG_VALUE);
+                    written += snprintf(
+                      buffer + written,
+                      length - written,
+                      "(%cA%c)%.0f",
+                      finger_alpha_key,
+                      joint_alpha_key,
+                      std::floor(joint * MAX_ANALOG_VALUE)
+                    );
                 }
             }
-
-            const auto& splay = peripheral.splay.fingers;
-            for (auto i = 0; i < splay.size(); i++) {
-                const auto& finger = splay[i];
-
-                if (finger == 0.0F) {
-                    continue;
-                }
-
+#endif
+            if (peripheral.joystick.x != 0.0F) {
                 written += snprintf(
                   buffer + written,
                   length - written,
-                  "(%cB)%.0f",
-                  AlphaEncoder::FINGER_ALPHA_KEY[i],
-                  finger * MAX_ANALOG_VALUE
+                  "F%.0f",
+                  std::floor(peripheral.joystick.x * MAX_ANALOG_VALUE)
                 );
             }
-#endif
+            if (peripheral.joystick.y != 0.0F) {
+                written += snprintf(
+                  buffer + written,
+                  length - written,
+                  "G%.0f",
+                  std::floor(peripheral.joystick.y * MAX_ANALOG_VALUE)
+                );
+            }
+            if (peripheral.joystick.press) {
+                written += snprintf(buffer + written, length - written, "H");
+            }
 
             const auto& buttons = peripheral.buttons;
             for (auto i = 0; i < buttons.size(); i++) {
diff --git a/lib/opengloves/opengloves/opengloves.hpp b/lib/opengloves/opengloves/opengloves.hpp
index d01bbfeb..7cfbda4f 100644
--- a/lib/opengloves/opengloves/opengloves.hpp
+++ b/lib/opengloves/opengloves/opengloves.hpp
@@ -231,7 +231,7 @@ namespace og {
         inline static constexpr const char* INFO_DEVICE_TYPE_KEY = "(ZG)";
         inline static constexpr const char* INFO_HAND_KEY = "(ZH)";
 
-        inline static constexpr const uint16_t MAX_ANALOG_VALUE = 4096;
+        inline static constexpr const uint16_t MAX_ANALOG_VALUE = 4095;
 
 #ifdef OG_USE_FROZEN
         inline static constexpr const auto ALPHA_KEYS_TO_COMMAND = frozen::make_map<frozen::string, Command>
diff --git a/test/test_opengloves/main.cpp b/test/test_opengloves/main.cpp
deleted file mode 100644
index 7f607d6f..00000000
--- a/test/test_opengloves/main.cpp
+++ /dev/null
@@ -1,159 +0,0 @@
-#include <sensor/og_sensor.hpp>
-#include <unity.h>
-
-using namespace OpenGloves;
-
-class TestAnalogSensor : public SenseShift::Input::ISimpleSensor<uint16_t> {
-  private:
-    uint16_t count = 0;
-
-  public:
-    int setupCounter = 0;
-
-    void init() override { this->setupCounter++; };
-
-    uint16_t getValue() override { return ++this->count; };
-};
-
-class TestBinarySensor : public SenseShift::Input::ISimpleSensor<bool> {
-  public:
-    bool value = false;
-    int setupCounter = 0;
-
-    void init() override { this->setupCounter++; };
-
-    bool getValue() override { return this->value; };
-};
-
-class TestFingerSensor : public SenseShift::Input::ISimpleSensor<FingerValue> {
-  public:
-    FingerValue value;
-    int setupCounter = 0;
-
-    void init() override { this->setupCounter++; };
-
-    FingerValue getValue() override { return this->value; };
-};
-
-void test_string_encoded_sensor_uint16(void)
-{
-    auto inner = new TestAnalogSensor();
-    auto sensor = new StringEncodedMemoizedSensor<uint16_t>(inner, IEncodedInput::Type::INDEX);
-
-    TEST_ASSERT_EQUAL_INT(0, inner->setupCounter);
-    sensor->init();
-    TEST_ASSERT_EQUAL_INT(1, inner->setupCounter);
-
-    TEST_ASSERT_EQUAL_INT(0, sensor->getValue());
-    TEST_ASSERT_EQUAL_INT(0, sensor->getValue());
-
-    sensor->updateValue();
-
-    TEST_ASSERT_EQUAL_INT(1, sensor->getValue());
-    TEST_ASSERT_EQUAL_INT(1, sensor->getValue());
-
-    char buffer[6];
-    TEST_ASSERT_EQUAL_INT(2, sensor->encodeString(buffer));
-    TEST_ASSERT_EQUAL_STRING("B1", buffer);
-}
-
-void test_string_encoded_sensor_bool(void)
-{
-    auto inner = new TestBinarySensor();
-    auto sensor = new StringEncodedMemoizedSensor<bool>(inner, IEncodedInput::Type::A_BTN);
-
-    TEST_ASSERT_EQUAL_INT(0, inner->setupCounter);
-    sensor->init();
-    TEST_ASSERT_EQUAL_INT(1, inner->setupCounter);
-
-    TEST_ASSERT_FALSE(sensor->getValue());
-
-    char buffer[1];
-    TEST_ASSERT_EQUAL_INT(0, sensor->encodeString(buffer));
-    TEST_ASSERT_EQUAL_STRING("", buffer);
-
-    sensor->updateValue();
-
-    TEST_ASSERT_FALSE(sensor->getValue());
-
-    inner->value = true;
-
-    TEST_ASSERT_FALSE(sensor->getValue());
-
-    sensor->updateValue();
-
-    TEST_ASSERT_TRUE(sensor->getValue());
-
-    TEST_ASSERT_EQUAL_INT(1, sensor->encodeString(buffer));
-    TEST_ASSERT_EQUAL_STRING("J", buffer);
-}
-
-void test_string_encoded_sensor_fingervalue(void)
-{
-    auto inner = new TestFingerSensor();
-    auto sensor = new StringEncodedMemoizedSensor<FingerValue>(inner, IEncodedInput::Type::THUMB);
-
-    TEST_ASSERT_EQUAL_INT(0, inner->setupCounter);
-    sensor->init();
-    TEST_ASSERT_EQUAL_INT(1, inner->setupCounter);
-
-    // curl-only
-    char buffer[sensor->getEncodedLength()];
-    sensor->encodeString(buffer);
-    TEST_ASSERT_EQUAL_STRING("A0", buffer);
-
-    inner->value.curl = { 256 };
-    sensor->updateValue();
-    sensor->encodeString(buffer);
-    TEST_ASSERT_EQUAL_STRING("A256", buffer);
-
-    // curl + splay
-    inner->value.splay = 420;
-    sensor->updateValue();
-    sensor->encodeString(buffer);
-    TEST_ASSERT_EQUAL_STRING("A256(AB)420", buffer);
-
-    // multi-curl
-    inner->value.curl = { 128, 256, 512 };
-    inner->value.splay = std::nullopt;
-    sensor->updateValue();
-    sensor->encodeString(buffer);
-    TEST_ASSERT_EQUAL_STRING("A298(AAA)128(AAB)256(AAC)512", buffer); // 298 = (128 + 256 + 512) / 3
-
-    // multi-curl + splay
-    inner->value.splay = 69;
-    sensor->updateValue();
-    sensor->encodeString(buffer);
-    TEST_ASSERT_EQUAL_STRING("A298(AAA)128(AAB)256(AAC)512(AB)69", buffer); // 298 = (128 + 256 + 512) / 3
-}
-
-int process(void)
-{
-    UNITY_BEGIN();
-
-    RUN_TEST(test_string_encoded_sensor_uint16);
-    RUN_TEST(test_string_encoded_sensor_bool);
-    RUN_TEST(test_string_encoded_sensor_fingervalue);
-
-    return UNITY_END();
-}
-
-#ifdef ARDUINO
-
-#include <Arduino.h>
-
-void setup(void)
-{
-    process();
-}
-
-void loop(void) {}
-
-#else
-
-int main(int argc, char** argv)
-{
-    return process();
-}
-
-#endif
diff --git a/test/test_opengloves_alpha_encoding/main.cpp b/test/test_opengloves_alpha_encoding/main.cpp
index 68be53cd..2d599457 100644
--- a/test/test_opengloves_alpha_encoding/main.cpp
+++ b/test/test_opengloves_alpha_encoding/main.cpp
@@ -1,111 +1,141 @@
-#include <senseshift/opengloves/encoding/alpha.hpp>
+#include <opengloves/opengloves.hpp>
 #include <unity.h>
 
-using namespace OpenGloves;
-using namespace SenseShift::OpenGloves;
+using namespace og;
 
-void testSplitCommands(void)
+void test_encode_input_peripherals(void)
 {
-    std::map<std::string, std::map<Command, int>> input_strings = {
-        // curl only
+    const IEncoder* encoder = new AlphaEncoder();
+
+    const std::vector<std::tuple<InputPeripheralData, std::string>> cases = {
         {
-          "A2048\n\0",
-          {
-            { Command::ThumbCurl, 2048 },
-          },
+            InputPeripheralData({
+                .curl = {
+                    .thumb = {
+                        .curl_total = 0.5,
+                    },
+                    .index = {
+                        .curl_total = 0.5,
+                    },
+                    .middle = {
+                        .curl_total = 0.5,
+                    },
+                    .ring = {
+                        .curl_total = 0.5,
+                    },
+                    .pinky = {
+                        .curl_total = 0.5,
+                    },
+                },
+            }),
+            "A2047B2047C2047D2047E2047\n",
         },
-        // curl only
         {
-          "A100B200C300D400E500",
-          {
-            { Command::ThumbCurl, 100 },
-            { Command::IndexCurl, 200 },
-            { Command::MiddleCurl, 300 },
-            { Command::RingCurl, 400 },
-            { Command::PinkyCurl, 500 },
-          },
+            InputPeripheralData({
+                .curl = {
+                    .thumb = {
+                        .curl_total = 0.0,
+                    },
+                    .index = {
+                        .curl_total = 0.0,
+                    },
+                    .middle = {
+                        .curl_total = 0.0,
+                    },
+                    .ring = {
+                        .curl_total = 0.0,
+                    },
+                    .pinky = {
+                        .curl_total = 0.0,
+                    },
+                },
+            }),
+            "A0B0C0D0E0\n",
         },
-        // curl (unordered)
         {
-          "E500A100B200D400C300",
-          {
-            { Command::ThumbCurl, 100 },
-            { Command::IndexCurl, 200 },
-            { Command::MiddleCurl, 300 },
-            { Command::RingCurl, 400 },
-            { Command::PinkyCurl, 500 },
-          },
+            InputPeripheralData({
+                .curl = {
+                    .thumb = {
+                        .curl_total = 1.0,
+                    },
+                    .index = {
+                        .curl_total = 1.0,
+                    },
+                    .middle = {
+                        .curl_total = 1.0,
+                    },
+                    .ring = {
+                        .curl_total = 1.0,
+                    },
+                    .pinky = {
+                        .curl_total = 1.0,
+                    },
+                },
+            }),
+            "A4095B4095C4095D4095E4095\n",
         },
-        // curl (with invalid data)
         {
-          "A100B200C300D400E",
-          {
-            { Command::ThumbCurl, 100 },
-            { Command::IndexCurl, 200 },
-            { Command::MiddleCurl, 300 },
-            { Command::RingCurl, 400 },
-          },
+          InputPeripheralData({
+                .splay = {
+                    .thumb = 0.5,
+                    .index = 0.5,
+                    .middle = 0.5,
+                    .ring = 0.5,
+                    .pinky = 0.5,
+                },
+            }),
+            "A0(AB)2047B0(BB)2047C0(CB)2047D0(DB)2047E0(EB)2047\n",
         },
-        // curl (with unknown prefix)
         {
-          "X100A100B200C300D400E500",
-          {
-            { Command::ThumbCurl, 100 },
-            { Command::IndexCurl, 200 },
-            { Command::MiddleCurl, 300 },
-            { Command::RingCurl, 400 },
-            { Command::PinkyCurl, 500 },
-          },
+          InputPeripheralData({
+            .joystick = {
+              .x = 0.5,
+              .y = 0.5,
+              .press = true,
+            },
+          }),
+          "A0B0C0D0E0F2047G2047H\n"
         },
-        // curl + splay
         {
-          "A1(AB)2B3(BB)4C5(CB)6D7(DB)8E9(EB)10",
-          {
-            { Command::ThumbCurl, 1 },
-            { Command::ThumbSplay, 2 },
-            { Command::IndexCurl, 3 },
-            { Command::IndexSplay, 4 },
-            { Command::MiddleCurl, 5 },
-            { Command::MiddleSplay, 6 },
-            { Command::RingCurl, 7 },
-            { Command::RingSplay, 8 },
-            { Command::PinkyCurl, 9 },
-            { Command::PinkySplay, 10 },
-          },
+          InputPeripheralData({
+            .buttons = { true, false, false, true },
+          }),
+          "A0B0C0D0E0JO\n"
         },
-        // curl + splay (unordered)
         {
-          "E9A1B3D7C5(BB)4(AB)2(EB)10(CB)6(DB)8",
-          {
-            { Command::ThumbCurl, 1 },
-            { Command::ThumbSplay, 2 },
-            { Command::IndexCurl, 3 },
-            { Command::IndexSplay, 4 },
-            { Command::MiddleCurl, 5 },
-            { Command::MiddleSplay, 6 },
-            { Command::RingCurl, 7 },
-            { Command::RingSplay, 8 },
-            { Command::PinkyCurl, 9 },
-            { Command::PinkySplay, 10 },
-          },
-        },
+          InputPeripheralData({
+                .curl = {
+                    .thumb = {
+                        .curl_total = 0.5,
+                    },
+                    .index = {
+                        .curl_total = 0.5,
+                    },
+                    .middle = {
+                        .curl_total = 0.5,
+                    },
+                    .ring = {
+                        .curl_total = 0.5,
+                    },
+                    .pinky = {
+                        .curl_total = 0.5,
+                    },
+                },
+                .splay = {
+                    .thumb = 0.5,
+                    .index = 0.5,
+                    .middle = 0.5,
+                    .ring = 0.5,
+                    .pinky = 0.5,
+                },
+          }),
+          "A2047(AB)2047B2047(BB)2047C2047(CB)2047D2047(DB)2047E2047(EB)2047\n",
+        }
     };
 
-    auto encoding_service = AlphaEncodingService();
-
-    for (auto& [input_string, expected_commands] : input_strings) {
-        std::map<Command, uint16_t> commands = {};
-        encoding_service.deserialize(input_string.c_str(), input_string.length(), commands);
-
-        TEST_ASSERT_EQUAL_size_t_MESSAGE(
-          expected_commands.size(),
-          commands.size(),
-          "Expected commands size does not match actual commands size"
-        );
-
-        for (auto& [command, value] : expected_commands) {
-            TEST_ASSERT_EQUAL_INT(value, commands[command]);
-        }
+    for (const auto& [data, expected] : cases) {
+        const auto encoded = encoder->encode_input(data);
+        TEST_ASSERT_EQUAL_STRING(expected.c_str(), encoded.c_str());
     }
 }
 
@@ -113,7 +143,7 @@ int process(void)
 {
     UNITY_BEGIN();
 
-    RUN_TEST(testSplitCommands);
+    RUN_TEST(test_encode_input_peripherals);
 
     return UNITY_END();
 }
diff --git a/test/test_opengloves_finger/main.cpp b/test/test_opengloves_finger/main.cpp
deleted file mode 100644
index 569bcda8..00000000
--- a/test/test_opengloves_finger/main.cpp
+++ /dev/null
@@ -1,177 +0,0 @@
-#include <sensor/og_finger.hpp>
-#include <unity.h>
-
-using namespace OpenGloves;
-using namespace SenseShift::Calibration;
-
-class TestAnalogSensor : public SenseShift::Input::ISimpleSensor<uint16_t> {
-  private:
-    uint16_t count = 0;
-
-  public:
-    int setupCounter = 0;
-
-    void init() override { this->setupCounter++; };
-
-    uint16_t getValue() override { return this->count++; };
-};
-
-class DummyCalibrator : public ICalibrator<uint16_t> {
-  public:
-    uint8_t resetCounter = 0;
-    std::optional<uint16_t> calibrated = std::nullopt;
-
-    void reset() override
-    {
-        this->resetCounter++;
-        this->calibrated = std::nullopt;
-    };
-    void update(uint16_t input) override { this->calibrated = input; };
-    uint16_t calibrate(uint16_t input) const override { return this->calibrated.value_or(input); };
-};
-
-void test_simple_finger_sensor_curl(void)
-{
-    auto* inner = new TestAnalogSensor();
-    auto* calibrator = new DummyCalibrator();
-    auto* calibrated = new SenseShift::Input::CalibratedSimpleSensor<uint16_t>(inner, calibrator);
-    auto* sensor = new SimpleFingerSensor(calibrated);
-
-    TEST_ASSERT_EQUAL_INT(0, inner->setupCounter);
-    sensor->init();
-    TEST_ASSERT_EQUAL_INT(1, inner->setupCounter);
-
-    // since the sensor is not memoized, the value is updated on every call
-    TEST_ASSERT_EQUAL_INT(0, sensor->getValue().curl[0]);
-    TEST_ASSERT_EQUAL_INT(1, sensor->getCurl());
-
-    calibrator->calibrated = 100;
-
-    TEST_ASSERT_EQUAL_INT(100, sensor->getValue().curl[0]); // 2 is inside
-    TEST_ASSERT_EQUAL_INT(100, sensor->getCurl());          // 3 is inside
-
-    calibrator->calibrated = std::nullopt;
-
-    TEST_ASSERT_EQUAL_INT(4, sensor->getValue().curl[0]);
-    TEST_ASSERT_EQUAL_INT(5, sensor->getCurl());
-}
-
-void test_simple_finger_sensor_curl_flex(void)
-{
-    auto* inner_curl = new TestAnalogSensor();
-    auto* calibrator_curl = new DummyCalibrator();
-    auto* calibrated_curl = new SenseShift::Input::CalibratedSimpleSensor<uint16_t>(inner_curl, calibrator_curl);
-
-    auto* inner_flex = new TestAnalogSensor();
-    auto* calibrator_flex = new DummyCalibrator();
-    auto* calibrated_flex = new SenseShift::Input::CalibratedSimpleSensor<uint16_t>(inner_flex, calibrator_flex);
-
-    auto* sensor = new SimpleFingerSensor(calibrated_curl, calibrated_flex);
-
-    TEST_ASSERT_EQUAL_INT(0, inner_curl->setupCounter);
-    TEST_ASSERT_EQUAL_INT(0, inner_flex->setupCounter);
-    sensor->init();
-    TEST_ASSERT_EQUAL_INT(1, inner_curl->setupCounter);
-    TEST_ASSERT_EQUAL_INT(1, inner_flex->setupCounter);
-
-    // since the sensor is not memoized, the value is updated on every call
-    TEST_ASSERT_EQUAL_INT(0, sensor->getValue().curl[0]);
-    TEST_ASSERT_EQUAL_INT(1, sensor->getValue().splay.value());
-    TEST_ASSERT_EQUAL_INT(2, sensor->getCurl());
-
-    calibrator_curl->calibrated = 100;
-    calibrator_flex->calibrated = 200;
-
-    TEST_ASSERT_EQUAL_INT(100, sensor->getValue().curl[0]);       // 3 is inside
-    TEST_ASSERT_EQUAL_INT(200, sensor->getValue().splay.value()); // 4 is inside
-    TEST_ASSERT_EQUAL_INT(100, sensor->getCurl());                // 5 is inside
-
-    calibrator_curl->calibrated = std::nullopt;
-    calibrator_flex->calibrated = std::nullopt;
-
-    TEST_ASSERT_EQUAL_INT(6, sensor->getValue().curl[0]);
-    TEST_ASSERT_EQUAL_INT(7, sensor->getValue().splay.value());
-    TEST_ASSERT_EQUAL_INT(8, sensor->getCurl());
-}
-
-// todo: tests for multi-curl
-
-void test_finger_sensor_curl(void)
-{
-    auto* inner = new TestAnalogSensor();
-    auto* calibrator = new DummyCalibrator();
-    auto* calibrated = new SenseShift::Input::CalibratedSimpleSensor<uint16_t>(inner, calibrator);
-    auto* sensor = new FingerSensor(calibrated, IEncodedInput::Type::INDEX);
-
-    TEST_ASSERT_EQUAL_INT(0, inner->setupCounter);
-    sensor->init();
-    TEST_ASSERT_EQUAL_INT(1, inner->setupCounter);
-
-    TEST_ASSERT_EQUAL_INT(0, sensor->getValue().curl[0]);
-    TEST_ASSERT_EQUAL_INT(0, sensor->getCurl());
-
-    sensor->updateValue();
-
-    TEST_ASSERT_EQUAL_INT(0, sensor->getValue().curl[0]);
-    TEST_ASSERT_EQUAL_INT(0, sensor->getCurl());
-
-    calibrator->calibrated = 100;
-
-    TEST_ASSERT_EQUAL_INT(0, sensor->getValue().curl[0]);
-    TEST_ASSERT_EQUAL_INT(0, sensor->getCurl());
-
-    sensor->updateValue(); // +1 is inside
-
-    TEST_ASSERT_EQUAL_INT(100, sensor->getValue().curl[0]);
-    TEST_ASSERT_EQUAL_INT(100, sensor->getCurl());
-
-    TEST_ASSERT_EQUAL_INT(0, calibrator->resetCounter);
-    sensor->resetCalibration();
-    TEST_ASSERT_EQUAL_INT(1, calibrator->resetCounter);
-
-    sensor->enableCalibration();
-    sensor->updateValue(); // +1 is inside
-    TEST_ASSERT_EQUAL_INT(2, sensor->getValue().curl[0]);
-    TEST_ASSERT_EQUAL_INT(2, sensor->getCurl());
-
-    sensor->updateValue(); // +1 is inside
-    TEST_ASSERT_EQUAL_INT(3, sensor->getValue().curl[0]);
-    TEST_ASSERT_EQUAL_INT(3, sensor->getCurl());
-
-    sensor->disableCalibration();
-    sensor->updateValue(); // +1 is inside, but calibrated to 3
-    TEST_ASSERT_EQUAL_INT(3, sensor->getValue().curl[0]);
-    TEST_ASSERT_EQUAL_INT(3, sensor->getCurl());
-}
-
-int process(void)
-{
-    UNITY_BEGIN();
-
-    RUN_TEST(test_simple_finger_sensor_curl);
-    RUN_TEST(test_simple_finger_sensor_curl_flex);
-
-    RUN_TEST(test_finger_sensor_curl);
-
-    return UNITY_END();
-}
-
-#ifdef ARDUINO
-
-#include <Arduino.h>
-
-void setup(void)
-{
-    process();
-}
-
-void loop(void) {}
-
-#else
-
-int main(int argc, char** argv)
-{
-    return process();
-}
-
-#endif

From 48d827701088683b0f0a6816a3733e5021b027f8 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Fri, 9 Feb 2024 15:35:46 +0400
Subject: [PATCH 60/82] test: cover edge-cases

---
 firmware/mode_configs/bhaptics/tactal.cpp     |   3 +-
 firmware/mode_configs/bhaptics/tactglove.cpp  |   3 +-
 firmware/mode_configs/bhaptics/tactosy2.cpp   |   3 +-
 firmware/mode_configs/bhaptics/tactosyf.cpp   |   3 +-
 firmware/mode_configs/bhaptics/tactosyh.cpp   |   3 +-
 .../mode_configs/bhaptics/tactsuit_x16.cpp    |   3 +-
 .../bhaptics/tactsuit_x16_pca9685.cpp         |   3 +-
 .../mode_configs/bhaptics/tactsuit_x40.cpp    |   3 +-
 firmware/mode_configs/bhaptics/tactvisor.cpp  |   3 +-
 include/senseshift.h                          |   4 +-
 .../senseshift/{ => battery}/battery.hpp      |  32 +----
 .../{sensor.hpp => input/battery_sensor.hpp}  |  15 +-
 .../senseshift/bh/ble/connection.hpp          |   7 +-
 lib/io/senseshift/input/calibration.hpp       |   3 +-
 lib/opengloves/opengloves/opengloves.hpp      |  16 ++-
 lib/util/senseshift/range.hpp                 |  21 ---
 lib/util/senseshift/utility.hpp               |   2 +-
 test/test_battery/main.cpp                    |  53 +++++++
 test/test_body_gestures/main.cpp              |  18 +--
 test/test_core_helpers/main.cpp               | 132 ++++++++++++++++++
 .../main.cpp                                  |   0
 test/test_opengloves_alpha_encoding/main.cpp  |  36 ++++-
 test/test_util_buffer/main.cpp                |   1 -
 .../main.cpp                                  |  29 +---
 24 files changed, 274 insertions(+), 122 deletions(-)
 rename lib/battery/senseshift/{ => battery}/battery.hpp (50%)
 rename lib/battery/senseshift/battery/{sensor.hpp => input/battery_sensor.hpp} (85%)
 delete mode 100644 lib/util/senseshift/range.hpp
 create mode 100644 test/test_battery/main.cpp
 create mode 100644 test/test_core_helpers/main.cpp
 rename test/{test_util_calibration => test_io_calibration}/main.cpp (100%)
 rename test/{test_util => test_util_container}/main.cpp (63%)

diff --git a/firmware/mode_configs/bhaptics/tactal.cpp b/firmware/mode_configs/bhaptics/tactal.cpp
index e68ee5a1..8a452ab5 100644
--- a/firmware/mode_configs/bhaptics/tactal.cpp
+++ b/firmware/mode_configs/bhaptics/tactal.cpp
@@ -6,9 +6,9 @@
 
 #include "senseshift.h"
 
+#include "senseshift/battery/input/battery_sensor.hpp"
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/ledc.hpp>
-#include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
@@ -21,6 +21,7 @@ using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
+using namespace SenseShift::Battery::Input;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
diff --git a/firmware/mode_configs/bhaptics/tactglove.cpp b/firmware/mode_configs/bhaptics/tactglove.cpp
index 024bf872..1fa2aa4e 100644
--- a/firmware/mode_configs/bhaptics/tactglove.cpp
+++ b/firmware/mode_configs/bhaptics/tactglove.cpp
@@ -6,9 +6,9 @@
 
 #include "senseshift.h"
 
+#include "senseshift/battery/input/battery_sensor.hpp"
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/ledc.hpp>
-#include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
@@ -22,6 +22,7 @@ using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
+using namespace SenseShift::Battery::Input;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
diff --git a/firmware/mode_configs/bhaptics/tactosy2.cpp b/firmware/mode_configs/bhaptics/tactosy2.cpp
index 21dfa3c1..e630072b 100644
--- a/firmware/mode_configs/bhaptics/tactosy2.cpp
+++ b/firmware/mode_configs/bhaptics/tactosy2.cpp
@@ -6,9 +6,9 @@
 
 #include "senseshift.h"
 
+#include "senseshift/battery/input/battery_sensor.hpp"
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/ledc.hpp>
-#include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
@@ -21,6 +21,7 @@ using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
+using namespace SenseShift::Battery::Input;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
diff --git a/firmware/mode_configs/bhaptics/tactosyf.cpp b/firmware/mode_configs/bhaptics/tactosyf.cpp
index 9b9a4a60..d35a6427 100644
--- a/firmware/mode_configs/bhaptics/tactosyf.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyf.cpp
@@ -6,9 +6,9 @@
 
 #include "senseshift.h"
 
+#include "senseshift/battery/input/battery_sensor.hpp"
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/ledc.hpp>
-#include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
@@ -21,6 +21,7 @@ using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
+using namespace SenseShift::Battery::Input;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
diff --git a/firmware/mode_configs/bhaptics/tactosyh.cpp b/firmware/mode_configs/bhaptics/tactosyh.cpp
index 11fa6192..3fd258cc 100644
--- a/firmware/mode_configs/bhaptics/tactosyh.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyh.cpp
@@ -6,9 +6,9 @@
 
 #include "senseshift.h"
 
+#include "senseshift/battery/input/battery_sensor.hpp"
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/ledc.hpp>
-#include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
@@ -21,6 +21,7 @@ using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
+using namespace SenseShift::Battery::Input;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
index 72d82490..1f8bd345 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
@@ -6,9 +6,9 @@
 
 #include "senseshift.h"
 
+#include "senseshift/battery/input/battery_sensor.hpp"
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/ledc.hpp>
-#include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
@@ -21,6 +21,7 @@ using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
+using namespace SenseShift::Battery::Input;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
index d5826492..e3ac3769 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
@@ -6,9 +6,9 @@
 
 #include "senseshift.h"
 
+#include "senseshift/battery/input/battery_sensor.hpp"
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/pca9685.hpp>
-#include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
@@ -21,6 +21,7 @@ using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
+using namespace SenseShift::Battery::Input;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
index ce0b0a8e..3a88f119 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
@@ -6,10 +6,10 @@
 
 #include "senseshift.h"
 
+#include "senseshift/battery/input/battery_sensor.hpp"
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/ledc.hpp>
 #include <senseshift/arduino/output/pca9685.hpp>
-#include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
@@ -22,6 +22,7 @@ using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
+using namespace SenseShift::Battery::Input;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
diff --git a/firmware/mode_configs/bhaptics/tactvisor.cpp b/firmware/mode_configs/bhaptics/tactvisor.cpp
index 128f2199..83b44903 100644
--- a/firmware/mode_configs/bhaptics/tactvisor.cpp
+++ b/firmware/mode_configs/bhaptics/tactvisor.cpp
@@ -6,9 +6,9 @@
 
 #include "senseshift.h"
 
+#include "senseshift/battery/input/battery_sensor.hpp"
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/ledc.hpp>
-#include <senseshift/battery/sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
@@ -21,6 +21,7 @@ using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
 using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
+using namespace SenseShift::Battery::Input;
 using namespace SenseShift::BH;
 using namespace SenseShift::Body::Haptics;
 
diff --git a/include/senseshift.h b/include/senseshift.h
index ec98103d..d3d8629d 100644
--- a/include/senseshift.h
+++ b/include/senseshift.h
@@ -2,7 +2,7 @@
 
 #include "config/all.h"
 
-#include <senseshift/battery/sensor.hpp>
+#include "senseshift/battery/input/battery_sensor.hpp"
 #include <senseshift/body/haptics/body.hpp>
 #include <senseshift/events.hpp>
 
@@ -21,6 +21,6 @@ namespace SenseShift {
       private:
         std::vector<const IEventListener*> event_listeners_{};
         Body::Haptics::FloatBody* vibro_body_;
-        Battery::IBatterySensor* battery_ = nullptr;
+        Battery::Input::IBatterySensor* battery_ = nullptr;
     };
 } // namespace SenseShift
diff --git a/lib/battery/senseshift/battery.hpp b/lib/battery/senseshift/battery/battery.hpp
similarity index 50%
rename from lib/battery/senseshift/battery.hpp
rename to lib/battery/senseshift/battery/battery.hpp
index ed784445..0f4e587c 100644
--- a/lib/battery/senseshift/battery.hpp
+++ b/lib/battery/senseshift/battery/battery.hpp
@@ -2,39 +2,21 @@
 
 #include <cstdint>
 #include <limits>
-#include <algorithm>
 
-#include <frozen/map.h>
+#include "frozen/map.h"
 
-#include <senseshift/events.hpp>
+#include "senseshift/events.hpp"
 
 namespace SenseShift::Battery {
 
     namespace VoltageMap {
         /// Lookup table for LiPO 1S 4.2V batteries
         /// \see <a href="https://blog.ampow.com/lipo-voltage-chart/">Source</a>
-        [[maybe_unused]] inline static const auto LiPO_1S_42= frozen::make_map<float, float>({
-            { 4.2, 1.0 },
-            { 4.15, 0.95 },
-            { 4.11, 0.9 },
-            { 4.08, 0.85 },
-            { 4.02, 0.8 },
-            { 3.98, 0.75 },
-            { 3.95, 0.7 },
-            { 3.91, 0.65 },
-            { 3.87, 0.6 },
-            { 3.85, 0.55 },
-            { 3.84, 0.5 },
-            { 3.82, 0.45 },
-            { 3.8, 0.4 },
-            { 3.79, 0.35 },
-            { 3.77, 0.3 },
-            { 3.75, 0.25 },
-            { 3.73, 0.2 },
-            { 3.71, 0.15 },
-            { 3.69, 0.1 },
-            { 3.61, 0.05 },
-            { 3.27, 0.0 },
+        [[maybe_unused]] inline static const auto LiPO_1S_42 = frozen::make_map<float, float>({
+          { 4.2, 1.0 },  { 4.15, 0.95 }, { 4.11, 0.9 }, { 4.08, 0.85 }, { 4.02, 0.8 }, { 3.98, 0.75 },
+          { 3.95, 0.7 }, { 3.91, 0.65 }, { 3.87, 0.6 }, { 3.85, 0.55 }, { 3.84, 0.5 }, { 3.82, 0.45 },
+          { 3.8, 0.4 },  { 3.79, 0.35 }, { 3.77, 0.3 }, { 3.75, 0.25 }, { 3.73, 0.2 }, { 3.71, 0.15 },
+          { 3.69, 0.1 }, { 3.61, 0.05 }, { 3.27, 0.0 },
         });
     } // namespace VoltageMap
 
diff --git a/lib/battery/senseshift/battery/sensor.hpp b/lib/battery/senseshift/battery/input/battery_sensor.hpp
similarity index 85%
rename from lib/battery/senseshift/battery/sensor.hpp
rename to lib/battery/senseshift/battery/input/battery_sensor.hpp
index 19cfc6de..d0314a03 100644
--- a/lib/battery/senseshift/battery/sensor.hpp
+++ b/lib/battery/senseshift/battery/input/battery_sensor.hpp
@@ -2,17 +2,12 @@
 
 #include <cstdint>
 
-#include "senseshift/battery.hpp"
+#include "senseshift/battery/battery.hpp"
 
-#include <senseshift/freertos/task.hpp>
-#include <senseshift/input/sensor.hpp>
-#include <senseshift/utility.hpp>
+#include "senseshift/core/helpers.hpp"
+#include "senseshift/input/sensor.hpp"
 
-#ifndef SENSESHIFT_BATTERY_TASK_PRIORITY
-#define SENSESHIFT_BATTERY_TASK_PRIORITY 1
-#endif
-
-namespace SenseShift::Battery {
+namespace SenseShift::Battery::Input {
     /// Abstract battery sensor
     using IBatterySensor = ::SenseShift::Input::Sensor<BatteryState>;
 
@@ -58,4 +53,4 @@ namespace SenseShift::Battery {
         VoltageSource* voltage_source_;
         Container* lookup_table_;
     };
-} // namespace SenseShift::Battery
+} // namespace SenseShift::Battery::Input
diff --git a/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp b/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
index a0265f74..e5033fb1 100644
--- a/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
+++ b/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
@@ -1,6 +1,7 @@
 #pragma once
 
-#include <senseshift/battery.hpp>
+#include "senseshift/battery/battery.hpp"
+#include "senseshift/core/helpers.hpp"
 #include <senseshift/bh/ble/constants.hpp>
 #include <senseshift/bh/constants.hpp>
 #include <senseshift/events.hpp>
@@ -44,9 +45,9 @@ namespace SenseShift::BH::BLE {
         void handleEvent(const IEvent* event) const override
         {
             if (event->eventName == OH_EVENT_BATTERY_LEVEL) {
-                uint16_t level = simpleMap<uint8_t>(
+                uint16_t level = remap_simple<std::uint8_t, std::uint8_t>(
                   static_cast<const ::SenseShift::Battery::BatteryLevelEvent*>(event)->state.level,
-                  255,
+                  ::SenseShift::Battery::BatteryState::MAX_LEVEL,
                   100
                 );
 
diff --git a/lib/io/senseshift/input/calibration.hpp b/lib/io/senseshift/input/calibration.hpp
index b5a271a5..fd6ec99e 100644
--- a/lib/io/senseshift/input/calibration.hpp
+++ b/lib/io/senseshift/input/calibration.hpp
@@ -150,8 +150,7 @@ namespace SenseShift::Input::Calibration {
             Tp center = (this->range_min_ + this->range_max_) / 2.0F;
 
             // Map the input to the sensor range of motion.
-            int output =
-              ::SenseShift::accurateMap<Tp>(input, this->output_min_, this->output_max_, 0, this->sensor_max_);
+            int output = ::SenseShift::remap<Tp>(input, this->output_min_, this->output_max_, 0, this->sensor_max_);
 
             // Find the deviation from the center and clamp it to the maximum that the driver supports.
             output = std::clamp<int>(output - center, -(this->driver_max_deviation_), this->driver_max_deviation_);
diff --git a/lib/opengloves/opengloves/opengloves.hpp b/lib/opengloves/opengloves/opengloves.hpp
index 7cfbda4f..fed4d11c 100644
--- a/lib/opengloves/opengloves/opengloves.hpp
+++ b/lib/opengloves/opengloves/opengloves.hpp
@@ -36,11 +36,15 @@ namespace og {
     template<typename Tf = float>
     union InputFingerCurl {
         std::array<Tf, 4> curl; // NOLINT(*-magic-numbers): I'm sure our finger aren't changing anytime soon
-        struct {
+        union {
             Tf curl_total;
-            Tf curl_joint1;
-            Tf curl_joint2;
-            Tf curl_joint3;
+            
+            struct {
+                Tf curl_joint0;
+                Tf curl_joint1;
+                Tf curl_joint2;
+                Tf curl_joint3;
+            };
         };
     };
     using InputFingerCurlData = InputFingerCurl<float>;
@@ -77,7 +81,7 @@ namespace og {
     using InputButtonData = InputButton<bool>;
 
     template<typename Tf = float, typename Tb = bool>
-    struct InputAnalogButton : InputButton<Tb> {
+    struct InputAnalogButton : public InputButton<Tb> {
         Tf value;
     };
     using InputAnalogButtonData = InputAnalogButton<float, bool>;
@@ -93,7 +97,7 @@ namespace og {
         InputJoystick<Tf, Tb> joystick;
 
         union {
-            std::array<InputButton<Tb>, 4> buttons;
+            std::array<InputButton<Tb>, 5> buttons;
             struct {
                 InputButton<Tb> button_a;
                 InputButton<Tb> button_b;
diff --git a/lib/util/senseshift/range.hpp b/lib/util/senseshift/range.hpp
deleted file mode 100644
index ea690108..00000000
--- a/lib/util/senseshift/range.hpp
+++ /dev/null
@@ -1,21 +0,0 @@
-#include <cstdint>
-#include <type_traits>
-
-#include <senseshift/core/logging.hpp>
-#include <senseshift/core/helpers.hpp>
-
-namespace SenseShift {
-    /// Remap \p value from the range (\p min, \p max) to (\p min_out, \p max_out).
-    template<typename Tp>
-    constexpr auto accurateMap(Tp value, Tp min, Tp max, Tp min_out, Tp max_out) noexcept -> Tp
-    {
-        return remap<Tp, Tp>(value, min, max, min_out, max_out);
-    }
-
-    // Same as the above, but both mins are 0.
-    template<typename Tp>
-    constexpr auto simpleMap(Tp value, Tp in_max, Tp out_max) noexcept -> Tp
-    {
-        return remap_simple<Tp, Tp>(value, in_max, out_max);
-    }
-} // namespace SenseShift
diff --git a/lib/util/senseshift/utility.hpp b/lib/util/senseshift/utility.hpp
index 5c95fd01..595944a9 100644
--- a/lib/util/senseshift/utility.hpp
+++ b/lib/util/senseshift/utility.hpp
@@ -5,4 +5,4 @@
 #include <type_traits>
 
 #include "senseshift/container.hpp"
-#include "senseshift/range.hpp"
+#include "senseshift/core/helpers.hpp"
diff --git a/test/test_battery/main.cpp b/test/test_battery/main.cpp
new file mode 100644
index 00000000..c563d37c
--- /dev/null
+++ b/test/test_battery/main.cpp
@@ -0,0 +1,53 @@
+#include <senseshift/battery/input/battery_sensor.hpp>
+#include <senseshift/input/sensor.hpp>
+#include <unity.h>
+
+using namespace SenseShift::Input;
+using namespace SenseShift::Battery;
+using namespace SenseShift::Battery::Input;
+
+void test_battery_sensor(void)
+{
+    auto* source = new FloatSensor();
+
+    auto* battery = new LookupTableInterpolateBatterySensor(source, &VoltageMap::LiPO_1S_42);
+    battery->init();
+
+    source->publishState(0.f);
+    TEST_ASSERT_EQUAL_INT(0, battery->getValue().level);
+
+    source->publishState(4.2f);
+    TEST_ASSERT_EQUAL_INT(255, battery->getValue().level);
+
+    source->publishState(3.7f);
+    TEST_ASSERT_EQUAL_INT(31, battery->getValue().level);
+}
+
+int process(void)
+{
+    UNITY_BEGIN();
+
+    RUN_TEST(test_battery_sensor);
+
+    return UNITY_END();
+}
+
+#ifdef ARDUINO
+
+#include <Arduino.h>
+
+void setup(void)
+{
+    process();
+}
+
+void loop(void) {}
+
+#else
+
+int main(int argc, char** argv)
+{
+    return process();
+}
+
+#endif
\ No newline at end of file
diff --git a/test/test_body_gestures/main.cpp b/test/test_body_gestures/main.cpp
index 5f9e800d..e63fb03f 100644
--- a/test/test_body_gestures/main.cpp
+++ b/test/test_body_gestures/main.cpp
@@ -10,14 +10,13 @@ void test_gesture_trigger(void)
     float threshold = 0.5f;
     auto* index = new FloatSensor();
 
-    auto* gesture = new TriggerGesture(index, threshold);
+    auto* gesture = new TriggerGesture(index, threshold, true);
+    gesture->init();
 
     index->publishState(0.4f);
-    gesture->tick();
     TEST_ASSERT_FALSE(gesture->getValue());
 
     index->publishState(0.6f);
-    gesture->tick();
     TEST_ASSERT_TRUE(gesture->getValue());
 }
 
@@ -37,25 +36,23 @@ void test_gesture_grab(void)
         .ring = ring,
         .pinky = pinky,
       },
-      threshold
+      threshold,
+      true
     );
+    gesture->init();
 
     TEST_ASSERT_FALSE(gesture->getValue());
 
     index->publishState(0.6);
-    gesture->tick();
     TEST_ASSERT_FALSE(gesture->getValue());
 
     middle->publishState(0.6);
-    gesture->tick();
     TEST_ASSERT_FALSE(gesture->getValue());
 
     ring->publishState(0.6);
-    gesture->tick();
     TEST_ASSERT_FALSE(gesture->getValue());
 
     pinky->publishState(0.6);
-    gesture->tick();
     TEST_ASSERT_TRUE(gesture->getValue());
 }
 
@@ -66,16 +63,15 @@ void test_gesture_pinch(void)
     auto* thumb = new FloatSensor();
     auto* index = new FloatSensor();
 
-    auto* gesture = new PinchGesture({ .thumb = thumb, .index = index }, threshold);
+    auto* gesture = new PinchGesture({ .thumb = thumb, .index = index }, threshold, true);
+    gesture->init();
 
     TEST_ASSERT_FALSE(gesture->getValue());
 
     thumb->publishState(0.6f);
-    gesture->tick();
     TEST_ASSERT_FALSE(gesture->getValue());
 
     index->publishState(0.6f);
-    gesture->tick();
     TEST_ASSERT_TRUE(gesture->getValue());
 }
 
diff --git a/test/test_core_helpers/main.cpp b/test/test_core_helpers/main.cpp
new file mode 100644
index 00000000..d13815bd
--- /dev/null
+++ b/test/test_core_helpers/main.cpp
@@ -0,0 +1,132 @@
+#include <senseshift/core/helpers.hpp>
+#include <unity.h>
+
+#include <map>
+#include <cstdint>
+
+using namespace SenseShift;
+
+void test_lerp_uint8(void)
+{
+    TEST_ASSERT_EQUAL_UINT16(0, lerp<std::uint8_t>(0.0f, 0, 255));
+    TEST_ASSERT_EQUAL_UINT16(127, lerp<std::uint8_t>(0.5f, 0, 255));
+    TEST_ASSERT_EQUAL_UINT16(255, lerp<std::uint8_t>(1.0f, 0, 255));
+
+    TEST_ASSERT_EQUAL_UINT16(0, lerp<std::uint8_t>(0.0f, 0, 0));
+}
+
+void test_lerp_float(void)
+{
+    TEST_ASSERT_EQUAL_FLOAT(-1.0f, lerp<float>(0.0f, -1.0f, 1.0f));
+    TEST_ASSERT_EQUAL_FLOAT(0.0f, lerp<float>(0.5f, -1.0f, 1.0f));
+    TEST_ASSERT_EQUAL_FLOAT(1.0f, lerp<float>(1.0f, -1.0f, 1.0f));
+
+    TEST_ASSERT_EQUAL_FLOAT(0.0f, lerp<float>(0.0f, 0.0f, 0.0f));
+}
+
+void test_remap_uint16(void)
+{
+    TEST_ASSERT_EQUAL_UINT16(0, remap(0, 0, 4095, 0, 255));
+    TEST_ASSERT_EQUAL_UINT16(127, remap(2047, 0, 4095, 0, 255));
+    TEST_ASSERT_EQUAL_UINT16(255, remap(4095, 0, 4095, 0, 255));
+
+    TEST_ASSERT_EQUAL_UINT16(127, remap(0, 0, 4095, 127, 255));
+    TEST_ASSERT_EQUAL_UINT16(190, remap(2047, 0, 4095, 127, 255));
+    TEST_ASSERT_EQUAL_UINT16(255, remap(4095, 0, 4095, 127, 255));
+
+    TEST_ASSERT_EQUAL_UINT16(0, remap(2048, 2048, 4095, 0, 255));
+    TEST_ASSERT_EQUAL_UINT16(127, remap(3071, 2048, 4095, 0, 255));
+    TEST_ASSERT_EQUAL_UINT16(255, remap(4095, 2048, 4095, 0, 255));
+
+    TEST_ASSERT_EQUAL_UINT16(2047, remap(343, 343, 343, 0, 4095));
+}
+
+void test_remap_float(void)
+{
+    TEST_ASSERT_EQUAL_FLOAT(0.0f, remap(0.0f, 0.0f, 1.0f, 0.0f, 255.0f));
+    TEST_ASSERT_EQUAL_FLOAT(127.5f, remap(0.5f, 0.0f, 1.0f, 0.0f, 255.0f));
+    TEST_ASSERT_EQUAL_FLOAT(255.0f, remap(1.0f, 0.0f, 1.0f, 0.0f, 255.0f));
+
+    TEST_ASSERT_EQUAL_FLOAT(0.5f, remap(0.0f, -1.0f, 1.0f, 0.0f, 1.0f));
+
+    TEST_ASSERT_EQUAL_FLOAT(0.125f, remap(0.2f, 0.1f, 0.9f, 0.f, 1.f));
+}
+
+void test_remap_simple_uint16(void)
+{
+    TEST_ASSERT_EQUAL_UINT16(0, remap_simple(0, 4095, 255));
+    TEST_ASSERT_EQUAL_UINT16(127, remap_simple(2047, 4095, 255));
+    TEST_ASSERT_EQUAL_UINT16(255, remap_simple(4095, 4095, 255));
+}
+
+void test_remap_simple_float(void)
+{
+    TEST_ASSERT_EQUAL_FLOAT(0.0f, remap_simple(0.0f, 1.0f, 255.0f));
+    TEST_ASSERT_EQUAL_FLOAT(127.5f, remap_simple(0.5f, 1.0f, 255.0f));
+    TEST_ASSERT_EQUAL_FLOAT(255.0f, remap_simple(1.0f, 1.0f, 255.0f));
+}
+
+// Wtf? https://stackoverflow.com/questions/4295890
+#define COMMAE ,
+
+void test_lookup_table_interpolate_float(void)
+{
+    const std::map<float, float> table = {
+        { 0.0f, 13.0f },
+        { 0.5f, 16.0f },
+        { 0.6f, 17.0f },
+        { 1.0f, 18.0f },
+    };
+
+    TEST_ASSERT_EQUAL_FLOAT(13.0f, lookup_table_interpolate<float COMMAE float>(table, 0.0f));
+    TEST_ASSERT_EQUAL_FLOAT(13.6f, lookup_table_interpolate<float COMMAE float>(table, 0.1f));
+    TEST_ASSERT_EQUAL_FLOAT(14.2f, lookup_table_interpolate<float COMMAE float>(table, 0.2f));
+    TEST_ASSERT_EQUAL_FLOAT(14.5f, lookup_table_interpolate<float COMMAE float>(table, 0.25f));
+    TEST_ASSERT_EQUAL_FLOAT(14.8f, lookup_table_interpolate<float COMMAE float>(table, 0.3f));
+    TEST_ASSERT_EQUAL_FLOAT(15.4f, lookup_table_interpolate<float COMMAE float>(table, 0.4f));
+    TEST_ASSERT_EQUAL_FLOAT(16.0f, lookup_table_interpolate<float COMMAE float>(table, 0.5f));
+    TEST_ASSERT_EQUAL_FLOAT(16.5f, lookup_table_interpolate<float COMMAE float>(table, 0.55f));
+    TEST_ASSERT_EQUAL_FLOAT(17.0f, lookup_table_interpolate<float COMMAE float>(table, 0.6f));
+    TEST_ASSERT_EQUAL_FLOAT(17.25f, lookup_table_interpolate<float COMMAE float>(table, 0.7f));
+    TEST_ASSERT_EQUAL_FLOAT(17.5f, lookup_table_interpolate<float COMMAE float>(table, 0.8f));
+    TEST_ASSERT_EQUAL_FLOAT(17.75f, lookup_table_interpolate<float COMMAE float>(table, 0.9f));
+    TEST_ASSERT_EQUAL_FLOAT(18.0f, lookup_table_interpolate<float COMMAE float>(table, 1.0f));
+}
+
+int process(void)
+{
+    UNITY_BEGIN();
+
+    RUN_TEST(test_lerp_uint8);
+    RUN_TEST(test_lerp_float);
+
+    RUN_TEST(test_remap_uint16);
+    RUN_TEST(test_remap_float);
+
+    RUN_TEST(test_remap_simple_uint16);
+    RUN_TEST(test_remap_simple_float);
+
+    RUN_TEST(test_lookup_table_interpolate_float);
+
+    return UNITY_END();
+}
+
+#ifdef ARDUINO
+
+#include <Arduino.h>
+
+void setup(void)
+{
+    process();
+}
+
+void loop(void) {}
+
+#else
+
+int main(int argc, char** argv)
+{
+    return process();
+}
+
+#endif
diff --git a/test/test_util_calibration/main.cpp b/test/test_io_calibration/main.cpp
similarity index 100%
rename from test/test_util_calibration/main.cpp
rename to test/test_io_calibration/main.cpp
diff --git a/test/test_opengloves_alpha_encoding/main.cpp b/test/test_opengloves_alpha_encoding/main.cpp
index 2d599457..c0162e99 100644
--- a/test/test_opengloves_alpha_encoding/main.cpp
+++ b/test/test_opengloves_alpha_encoding/main.cpp
@@ -86,6 +86,28 @@ void test_encode_input_peripherals(void)
             }),
             "A0(AB)2047B0(BB)2047C0(CB)2047D0(DB)2047E0(EB)2047\n",
         },
+        {
+          InputPeripheralData({
+            .curl = {
+              .thumb = {
+                .curl = { 0.25f, 0.5f, 0.75f, 1.0f },
+              },
+              .index = {
+                .curl = { 0.25f, 0.5f, 0.75f, 1.0f },
+              },
+              .middle = {
+                .curl = { 0.25f, 0.5f, 0.75f, 1.0f },
+              },
+              .ring = {
+                .curl = { 0.25f, 0.5f, 0.75f, 1.0f },
+              },
+              .pinky = {
+                .curl = { 0.25f, 0.5f, 0.75f, 1.0f },
+              },
+            },
+          }),
+          "A1023(AAB)2047(AAC)3071(AAD)4095B1023(BAB)2047(BAC)3071(BAD)4095C1023(CAB)2047(CAC)3071(CAD)4095D1023(DAB)2047(DAC)3071(DAD)4095E1023(EAB)2047(EAC)3071(EAD)4095\n",
+        },
         {
           InputPeripheralData({
             .joystick = {
@@ -97,11 +119,19 @@ void test_encode_input_peripherals(void)
           "A0B0C0D0E0F2047G2047H\n"
         },
         {
-          InputPeripheralData({
-            .buttons = { true, false, false, true },
-          }),
+          InputPeripheralData{
+            .button_a = { .press = true },
+            .button_calibrate = { .press = true },
+          },
           "A0B0C0D0E0JO\n"
         },
+        {
+          InputPeripheralData{
+            .pinch = { true },
+            .grab = { true },
+          },
+          "A0B0C0D0E0MI\n"
+        },
         {
           InputPeripheralData({
                 .curl = {
diff --git a/test/test_util_buffer/main.cpp b/test/test_util_buffer/main.cpp
index 40f1221c..14eec519 100644
--- a/test/test_util_buffer/main.cpp
+++ b/test/test_util_buffer/main.cpp
@@ -1,4 +1,3 @@
-#include <senseshift/utility.hpp>
 #include <unity.h>
 
 #include <senseshift/buffer.hpp>
diff --git a/test/test_util/main.cpp b/test/test_util_container/main.cpp
similarity index 63%
rename from test/test_util/main.cpp
rename to test/test_util_container/main.cpp
index 4be719ba..ed0279ca 100644
--- a/test/test_util/main.cpp
+++ b/test/test_util_container/main.cpp
@@ -45,30 +45,6 @@ void test_contains_string(void)
     TEST_ASSERT_FALSE(contains(s, 'z'));
 }
 
-void test_accurate_map(void)
-{
-    TEST_ASSERT_EQUAL_UINT16(0, accurateMap(0, 0, 4095, 0, 255));
-    TEST_ASSERT_EQUAL_UINT16(127, accurateMap(2047, 0, 4095, 0, 255));
-    TEST_ASSERT_EQUAL_UINT16(255, accurateMap(4095, 0, 4095, 0, 255));
-
-    TEST_ASSERT_EQUAL_UINT16(127, accurateMap(0, 0, 4095, 127, 255));
-    TEST_ASSERT_EQUAL_UINT16(190, accurateMap(2047, 0, 4095, 127, 255));
-    TEST_ASSERT_EQUAL_UINT16(255, accurateMap(4095, 0, 4095, 127, 255));
-
-    TEST_ASSERT_EQUAL_UINT16(0, accurateMap(2048, 2048, 4095, 0, 255));
-    TEST_ASSERT_EQUAL_UINT16(127, accurateMap(3071, 2048, 4095, 0, 255));
-    TEST_ASSERT_EQUAL_UINT16(255, accurateMap(4095, 2048, 4095, 0, 255));
-
-    TEST_ASSERT_EQUAL_UINT16(2047, accurateMap(343, 343, 343, 0, 4095));
-}
-
-void test_simple_map(void)
-{
-    TEST_ASSERT_EQUAL_UINT16(0, simpleMap(0, 4095, 255));
-    TEST_ASSERT_EQUAL_UINT16(127, simpleMap(2047, 4095, 255));
-    TEST_ASSERT_EQUAL_UINT16(255, simpleMap(4095, 4095, 255));
-}
-
 int process(void)
 {
     UNITY_BEGIN();
@@ -77,9 +53,6 @@ int process(void)
     RUN_TEST(test_contains_iterator);
     RUN_TEST(test_contains_string);
 
-    RUN_TEST(test_accurate_map);
-    RUN_TEST(test_simple_map);
-
     return UNITY_END();
 }
 
@@ -101,4 +74,4 @@ int main(int argc, char** argv)
     return process();
 }
 
-#endif
+#endif
\ No newline at end of file

From 01c9a063f87f406d58ddbd948e5704f7fbbc61f8 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Fri, 9 Feb 2024 18:15:03 +0400
Subject: [PATCH 61/82] feat(OpenGloves) re-introduce simple data sending

---
 .../mode_configs/opengloves/opengloves.cpp    |  17 +
 lib/core/senseshift/core/component.hpp        |  22 +-
 lib/freertos/senseshift/freertos/task.hpp     |   2 +-
 .../senseshift/body/hands/input/gesture.hpp   |  45 +--
 .../body/hands/input/total_curl.hpp           |   2 +-
 lib/io/senseshift/input/analog_threshold.hpp  |   2 +-
 lib/io/senseshift/input/sensor.hpp            |   6 +-
 lib/opengloves/opengloves/opengloves.hpp      |   8 +-
 .../senseshift/opengloves/opengloves.hpp      | 110 +++++-
 .../senseshift/opengloves/opengloves_task.hpp | 183 +++++++++
 lib/opengloves_task/opengloves_task.hpp       | 348 ------------------
 11 files changed, 360 insertions(+), 385 deletions(-)
 create mode 100644 lib/opengloves_freertos/senseshift/opengloves/opengloves_task.hpp
 delete mode 100644 lib/opengloves_task/opengloves_task.hpp

diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp
index d25e5733..a3e97875 100644
--- a/firmware/mode_configs/opengloves/opengloves.cpp
+++ b/firmware/mode_configs/opengloves/opengloves.cpp
@@ -7,6 +7,8 @@
 #include <senseshift/input/filter.hpp>
 #include <senseshift/input/sensor.hpp>
 #include <senseshift/opengloves/autoconfig.hpp>
+#include <senseshift/opengloves/opengloves.hpp>
+#include <senseshift/opengloves/opengloves_task.hpp>
 #include <senseshift/utility.hpp>
 
 using namespace ::SenseShift::Input;
@@ -150,6 +152,21 @@ void setupMode()
 #elif BUTTON_PINCH_ENABLED
     auto* pinch = new BUTTON_CLASS(PIN_BUTTON_PINCH, BUTTON_PINCH_INVERT);
 #endif
+
+    OpenGlovesTrackingComponent::Config const tracking_config(2000, false);
+    auto* opengloves_tracking =
+      new OpenGlovesTrackingComponent(tracking_config, input_sensors, new StreamTransport(Serial));
+
+    auto* opengloves_tracking_task = new ::SenseShift::FreeRTOS::ComponentUpdateTask<OpenGlovesTrackingComponent>(
+      opengloves_tracking,
+      1000 / 60,
+      {
+        .name = "OpenGlovesSensorTask",
+        .stackDepth = 8192,
+        .priority = 1,
+      }
+    );
+    opengloves_tracking_task->begin();
 }
 
 void loopMode()
diff --git a/lib/core/senseshift/core/component.hpp b/lib/core/senseshift/core/component.hpp
index 8fae06dd..8ead9f04 100644
--- a/lib/core/senseshift/core/component.hpp
+++ b/lib/core/senseshift/core/component.hpp
@@ -1,18 +1,31 @@
 #pragma once
 
+#define SS_INIT_NOT_NULL(ptr) \
+    if ((ptr) != nullptr) {   \
+        (ptr)->init();        \
+    }
+#define SS_TICK_NOT_NULL(ptr) \
+    if ((ptr) != nullptr) {   \
+        (ptr)->tick();        \
+    }
+
 namespace SenseShift {
     class IInitializable {
-    public:
+      public:
+        virtual ~IInitializable() = default;
+
         virtual void init() = 0;
     };
 
     class ITickable {
-    public:
+      public:
+        virtual ~ITickable() = default;
+
         virtual void tick() = 0;
     };
 
-    class Component : public IInitializable, public ITickable {
-    public:
+    class Component : public virtual IInitializable, public virtual ITickable {
+      public:
         /// @brief Initialize the component.
         ///
         /// Where the component's initialization should happen.
@@ -30,4 +43,3 @@ namespace SenseShift {
         void tick() override {}
     };
 } // namespace SenseShift
-
diff --git a/lib/freertos/senseshift/freertos/task.hpp b/lib/freertos/senseshift/freertos/task.hpp
index 9bea8bd8..aab82a70 100644
--- a/lib/freertos/senseshift/freertos/task.hpp
+++ b/lib/freertos/senseshift/freertos/task.hpp
@@ -112,7 +112,7 @@ namespace SenseShift::FreeRTOS {
         }
     };
 
-    template<typename Tp>
+    template<typename Tp = Component>
     class ComponentUpdateTask : public Task<ComponentUpdateTask<Tp>> {
         static_assert(std::is_same_v<decltype(&Tp::init), void (Tp::*)()>);
         static_assert(std::is_same_v<decltype(&Tp::tick), void (Tp::*)()>);
diff --git a/lib/hands_input/senseshift/body/hands/input/gesture.hpp b/lib/hands_input/senseshift/body/hands/input/gesture.hpp
index ad462051..eccb911f 100644
--- a/lib/hands_input/senseshift/body/hands/input/gesture.hpp
+++ b/lib/hands_input/senseshift/body/hands/input/gesture.hpp
@@ -1,8 +1,8 @@
 #pragma once
 
 #include <senseshift/core/component.hpp>
-#include <senseshift/input/sensor.hpp>
 #include <senseshift/input/analog_threshold.hpp>
+#include <senseshift/input/sensor.hpp>
 
 namespace SenseShift::Body::Hands::Input {
     using Gesture = ::SenseShift::Input::BinarySensor;
@@ -10,7 +10,7 @@ namespace SenseShift::Body::Hands::Input {
     /// An alias for semantic consistency.
     using TriggerGesture = ::SenseShift::Input::AnalogThresholdSensor<float>;
 
-    class GrabGesture : public Gesture, ITickable {
+    class GrabGesture : public Gesture {
       public:
         struct Fingers {
             ::SenseShift::Input::FloatSensor* index;
@@ -19,13 +19,13 @@ namespace SenseShift::Body::Hands::Input {
             ::SenseShift::Input::FloatSensor* pinky;
         };
 
-        explicit GrabGesture(
-          Fingers fingers,
-          float threshold = 0.5F,
-          bool attach_callbacks = false
-        ) : fingers_(fingers), threshold_(threshold), attach_callbacks_(attach_callbacks) {}
+        explicit GrabGesture(Fingers fingers, float threshold = 0.5F, bool attach_callbacks = false) :
+          fingers_(fingers), threshold_(threshold), attach_callbacks_(attach_callbacks)
+        {
+        }
 
-        void init() override {
+        void init() override
+        {
             SS_SUBSENSOR_INIT(this->fingers_.index, this->attach_callbacks_, [this](float /*value*/) {
                 this->recalculateState();
             });
@@ -40,14 +40,16 @@ namespace SenseShift::Body::Hands::Input {
             });
         }
 
-        void tick() override {
+        void tick() override
+        {
             if (this->attach_callbacks_) {
                 LOG_E("gesture.grab", "tick() called when attach_callbacks_ is true, infinite loop go wroom-wroom!");
             }
             this->recalculateState();
         }
 
-        void recalculateState() {
+        void recalculateState()
+        {
             return this->publishState(
               this->fingers_.index->getValue() > this->threshold_
               && this->fingers_.middle->getValue() > this->threshold_
@@ -62,20 +64,20 @@ namespace SenseShift::Body::Hands::Input {
         bool attach_callbacks_ = false;
     };
 
-    class PinchGesture : public Gesture, ITickable {
+    class PinchGesture : public Gesture {
       public:
         struct Fingers {
             ::SenseShift::Input::FloatSensor* thumb;
             ::SenseShift::Input::FloatSensor* index;
         };
 
-        explicit PinchGesture(
-          Fingers fingers,
-          float threshold = 0.5F,
-          bool attach_callbacks = false
-        ) : fingers_(fingers), threshold_(threshold), attach_callbacks_(attach_callbacks) {}
+        explicit PinchGesture(Fingers fingers, float threshold = 0.5F, bool attach_callbacks = false) :
+          fingers_(fingers), threshold_(threshold), attach_callbacks_(attach_callbacks)
+        {
+        }
 
-        void init() override {
+        void init() override
+        {
             SS_SUBSENSOR_INIT(this->fingers_.thumb, this->attach_callbacks_, [this](float /*value*/) {
                 this->recalculateState();
             });
@@ -84,17 +86,18 @@ namespace SenseShift::Body::Hands::Input {
             });
         }
 
-        void tick() override {
+        void tick() override
+        {
             if (this->attach_callbacks_) {
                 LOG_E("gesture.pinch", "tick() called when attach_callbacks_ is true, infinite loop go wroom-wroom!");
             }
             this->recalculateState();
         }
 
-        void recalculateState() {
+        void recalculateState()
+        {
             return this->publishState(
-              this->fingers_.thumb->getValue() > this->threshold_
-              && this->fingers_.index->getValue() > this->threshold_
+              this->fingers_.thumb->getValue() > this->threshold_ && this->fingers_.index->getValue() > this->threshold_
             );
         }
 
diff --git a/lib/hands_input/senseshift/body/hands/input/total_curl.hpp b/lib/hands_input/senseshift/body/hands/input/total_curl.hpp
index 26538afc..e086b894 100644
--- a/lib/hands_input/senseshift/body/hands/input/total_curl.hpp
+++ b/lib/hands_input/senseshift/body/hands/input/total_curl.hpp
@@ -8,7 +8,7 @@
 #include <senseshift/input/sensor.hpp>
 
 namespace SenseShift::Body::Hands::Input {
-    class TotalCurl : public ::SenseShift::Input::FloatSensor, public ::SenseShift::ITickable {
+    class TotalCurl : public ::SenseShift::Input::FloatSensor {
       public:
         /// \param joints The joints to calculate the total curl from.
         /// \param attach_callbacks Whether to attach callbacks to the joints to update the total curl when they update.
diff --git a/lib/io/senseshift/input/analog_threshold.hpp b/lib/io/senseshift/input/analog_threshold.hpp
index 46eb53d4..48397ef4 100644
--- a/lib/io/senseshift/input/analog_threshold.hpp
+++ b/lib/io/senseshift/input/analog_threshold.hpp
@@ -5,7 +5,7 @@
 
 namespace SenseShift::Input {
     template<typename Tp = float>
-    class AnalogThresholdSensor : public BinarySensor, ITickable {
+    class AnalogThresholdSensor : public BinarySensor {
       public:
         /// Analog threshold sensor with hysteresis.
         ///
diff --git a/lib/io/senseshift/input/sensor.hpp b/lib/io/senseshift/input/sensor.hpp
index 6d2046ea..e76b53b8 100644
--- a/lib/io/senseshift/input/sensor.hpp
+++ b/lib/io/senseshift/input/sensor.hpp
@@ -20,7 +20,7 @@ namespace SenseShift::Input {
     /// Abstract hardware sensor (e.g. potentiometer, flex sensor, etc.)
     /// \tparam Tp Type of the sensor value
     template<typename Tp>
-    class ISimpleSensor : virtual public IInitializable {
+    class ISimpleSensor : public virtual IInitializable {
       public:
         using ValueType = Tp;
 
@@ -37,7 +37,7 @@ namespace SenseShift::Input {
     class ISensor : public virtual ISimpleSensor<Tp>, public Calibration::ICalibrated {};
 
     template<typename Tp>
-    class Sensor : public ISensor<Tp> {
+    class Sensor : public ISensor<Tp>, public Component {
       public:
         using ValueType = Tp;
         using CallbackManagerType = CallbackManager<void(ValueType)>;
@@ -173,7 +173,7 @@ namespace SenseShift::Input {
     using BinarySensor = Sensor<bool>;
 
     template<typename Tp>
-    class SimpleSensorDecorator : public Sensor<Tp>, public ITickable {
+    class SimpleSensorDecorator : public Sensor<Tp> {
       public:
         using ValueType = Tp;
         using SourceType = ISimpleSensor<ValueType>;
diff --git a/lib/opengloves/opengloves/opengloves.hpp b/lib/opengloves/opengloves/opengloves.hpp
index fed4d11c..4d5b470f 100644
--- a/lib/opengloves/opengloves/opengloves.hpp
+++ b/lib/opengloves/opengloves/opengloves.hpp
@@ -38,7 +38,7 @@ namespace og {
         std::array<Tf, 4> curl; // NOLINT(*-magic-numbers): I'm sure our finger aren't changing anytime soon
         union {
             Tf curl_total;
-            
+
             struct {
                 Tf curl_joint0;
                 Tf curl_joint1;
@@ -146,18 +146,18 @@ namespace og {
 
         [[nodiscard]] virtual auto parse_output(const char* data, size_t length) const -> Output = 0;
 
-        [[nodiscard]] auto parse_output(const std::vector<char>& data) const -> Output
+        [[nodiscard]] inline auto parse_output(const std::vector<char>& data) const -> Output
         {
             return this->parse_output(data.data(), data.size());
         }
 
-        [[nodiscard]] auto parse_output(const std::string& data) const -> Output
+        [[nodiscard]] inline auto parse_output(const std::string& data) const -> Output
         {
             return this->parse_output(data.data(), data.length());
         }
 
 #ifdef ARDUINO
-        [[nodiscard]] auto parse_output(const String& data) const -> Output
+        [[nodiscard]] inline auto parse_output(const String& data) const -> Output
         {
             return this->parse_output(data.c_str(), data.length());
         }
diff --git a/lib/opengloves/senseshift/opengloves/opengloves.hpp b/lib/opengloves/senseshift/opengloves/opengloves.hpp
index fa948e4d..14354bcd 100644
--- a/lib/opengloves/senseshift/opengloves/opengloves.hpp
+++ b/lib/opengloves/senseshift/opengloves/opengloves.hpp
@@ -16,5 +16,113 @@ namespace SenseShift::OpenGloves {
         virtual auto read(char* buffer, size_t length) -> size_t = 0;
     };
 
-    using InputSensors = og::InputPeripheral<::SenseShift::Input::FloatSensor*, ::SenseShift::Input::BinarySensor*>;
+    using FloatSensor = ::SenseShift::Input::FloatSensor;
+    using BinarySensor = ::SenseShift::Input::BinarySensor;
+
+    class InputSensors : public og::InputPeripheral<FloatSensor*, BinarySensor*>, public Component {
+      public:
+        void init() override
+        {
+            for (auto& finger_curl : this->curl.fingers) {
+                for (auto& joint_sensor : finger_curl.curl) {
+                    SS_INIT_NOT_NULL(joint_sensor);
+                }
+            }
+
+            for (auto& finger_splay : this->splay.fingers) {
+                SS_INIT_NOT_NULL(finger_splay);
+            }
+
+            SS_INIT_NOT_NULL(this->joystick.x);
+            SS_INIT_NOT_NULL(this->joystick.y);
+            SS_INIT_NOT_NULL(this->joystick.press);
+
+            for (auto& button : this->buttons) {
+                SS_INIT_NOT_NULL(button.press);
+            }
+
+            for (auto& analog_button : this->analog_buttons) {
+                SS_INIT_NOT_NULL(analog_button.press);
+                SS_INIT_NOT_NULL(analog_button.value);
+            }
+        }
+
+        void tick() override
+        {
+            for (auto& finger_curl : this->curl.fingers) {
+                for (auto& joint_sensor : finger_curl.curl) {
+                    SS_TICK_NOT_NULL(joint_sensor);
+                }
+            }
+
+            for (auto& finger_splay : this->splay.fingers) {
+                SS_TICK_NOT_NULL(finger_splay);
+            }
+
+            SS_TICK_NOT_NULL(this->joystick.x);
+            SS_TICK_NOT_NULL(this->joystick.y);
+            SS_TICK_NOT_NULL(this->joystick.press);
+
+            for (auto& button : this->buttons) {
+                SS_TICK_NOT_NULL(button.press);
+            }
+
+            for (auto& analog_button : this->analog_buttons) {
+                SS_TICK_NOT_NULL(analog_button.press);
+                SS_TICK_NOT_NULL(analog_button.value);
+            }
+        }
+
+        auto collectData() -> og::InputPeripheralData
+        {
+            og::InputPeripheralData data;
+
+            const auto& curls = this->curl.fingers;
+            const auto& splays = this->splay.fingers;
+            for (auto i = 0; i < curls.size(); i++) {
+                const auto& finger_curl = curls[i].curl;
+                for (auto j = 0; j < finger_curl.size(); j++) {
+                    auto* joint_sensor = finger_curl[j];
+                    if (joint_sensor != nullptr) {
+                        data.curl.fingers[i].curl[j] = joint_sensor->getValue();
+                    }
+                }
+
+                auto* finger_splay = splays[i];
+                if (finger_splay != nullptr) {
+                    data.splay.fingers[i] = finger_splay->getValue();
+                }
+            }
+
+            if (this->joystick.x != nullptr) {
+                data.joystick.x = this->joystick.x->getValue();
+            }
+            if (this->joystick.y != nullptr) {
+                data.joystick.y = this->joystick.y->getValue();
+            }
+            if (this->joystick.press != nullptr) {
+                data.joystick.press = this->joystick.press->getValue();
+            }
+
+            for (auto i = 0; i < this->buttons.size(); i++) {
+                auto* button = this->buttons[i].press;
+                if (button != nullptr) {
+                    data.buttons[i].press = button->getValue();
+                }
+            }
+
+            for (auto i = 0; i < this->analog_buttons.size(); i++) {
+                auto* button = this->analog_buttons[i].press;
+                if (button != nullptr) {
+                    data.analog_buttons[i].press = button->getValue();
+                }
+                auto* value = this->analog_buttons[i].value;
+                if (value != nullptr) {
+                    data.analog_buttons[i].value = value->getValue();
+                }
+            }
+
+            return data;
+        }
+    };
 } // namespace SenseShift::OpenGloves
diff --git a/lib/opengloves_freertos/senseshift/opengloves/opengloves_task.hpp b/lib/opengloves_freertos/senseshift/opengloves/opengloves_task.hpp
new file mode 100644
index 00000000..bed2ed48
--- /dev/null
+++ b/lib/opengloves_freertos/senseshift/opengloves/opengloves_task.hpp
@@ -0,0 +1,183 @@
+#pragma once
+
+#include <Arduino.h>
+
+#include <cstddef>
+#include <optional>
+
+#include "senseshift/arduino/input/sensor/analog.hpp"
+#include "senseshift/arduino/input/sensor/digital.hpp"
+#include "senseshift/core/component.hpp"
+#include "senseshift/freertos/task.hpp"
+#include "senseshift/input/sensor.hpp"
+#include "senseshift/opengloves/opengloves.hpp"
+#include "senseshift/utility.hpp"
+#include <opengloves/opengloves.hpp>
+#include <utility>
+
+namespace SenseShift::OpenGloves {
+    class OpenGlovesTrackingComponent : public SenseShift::Component {
+      public:
+        class Config {
+            friend class OpenGlovesTrackingComponent;
+            size_t calibration_duration_ms_;
+            bool always_calibrate_;
+
+          public:
+            /// \param updateRate The rate at which the sensors should be updated in Hz.
+            /// \param calibrationDuration The duration in milliseconds that the calibration button should be held for.
+            Config(size_t calibration_duration_ms, bool always_calibrate) :
+              calibration_duration_ms_(calibration_duration_ms), always_calibrate_(always_calibrate)
+            {
+            }
+        };
+
+        OpenGlovesTrackingComponent(Config config, InputSensors& input_sensors, ITransport* communication) :
+          config(config), input_sensors_(std::move(input_sensors)), communication_(communication)
+        {
+            this->encoder_ = new og::AlphaEncoder();
+        }
+
+        void init() override
+        {
+            this->communication_->init();
+            this->input_sensors_.init();
+        }
+
+        void tick() override
+        {
+            const auto data = this->input_sensors_.collectData();
+
+            char buffer[256];
+            this->encoder_->encode_input(data, buffer, sizeof(buffer));
+            this->communication_->send(buffer, sizeof(buffer));
+        }
+
+      private:
+        Config& config;
+        InputSensors input_sensors_;
+        ITransport* communication_;
+        og::IEncoder* encoder_;
+    };
+
+    //    class OpenGlovesForceFeedbackTask : public SenseShift::FreeRTOS::Task<OpenGlovesForceFeedbackTask> {
+    //        friend class SenseShift::FreeRTOS::Task<OpenGlovesForceFeedbackTask>;
+    //
+    //      public:
+    //        OpenGlovesForceFeedbackTask(
+    //          ::SenseShift::OpenGloves::ITransport& communication,
+    //          HandActuators& actuators,
+    //          size_t updateRate,
+    //          SenseShift::FreeRTOS::TaskConfig taskConfig
+    //        ) :
+    //          communication(communication),
+    //          actuators(actuators),
+    //          SenseShift::FreeRTOS::Task<OpenGlovesForceFeedbackTask>(taskConfig)
+    //        {
+    //            this->updateIntervalMs = 1000 / updateRate;
+    //        };
+    //
+    //        void begin() override
+    //        {
+    //            log_d("Starting OpenGloves force feedback task: %p", this);
+    //            this->setup();
+    //            this->SenseShift::FreeRTOS::Task<OpenGlovesForceFeedbackTask>::begin();
+    //        };
+    //
+    //      private:
+    //        ::SenseShift::OpenGloves::ITransport& communication;
+    //        HandActuators& actuators;
+    //        size_t updateIntervalMs;
+    //        ::SenseShift::OpenGloves::AlphaEncodingService encodingService =
+    //          ::SenseShift::OpenGloves::AlphaEncodingService();
+    //
+    //        void setup()
+    //        {
+    //            log_d("Setting up OpenGloves force feedback task: %p", this);
+    //            this->communication.setup();
+    //
+    //            if (this->actuators.thumb.has_value()) {
+    //                this->actuators.thumb.value()->setup();
+    //            }
+    //
+    //            if (this->actuators.index.has_value()) {
+    //                this->actuators.index.value()->setup();
+    //            }
+    //
+    //            if (this->actuators.middle.has_value()) {
+    //                this->actuators.middle.value()->setup();
+    //            }
+    //
+    //            if (this->actuators.ring.has_value()) {
+    //                this->actuators.ring.value()->setup();
+    //            }
+    //
+    //            if (this->actuators.pinky.has_value()) {
+    //                this->actuators.pinky.value()->setup();
+    //            }
+    //        }
+    //
+    //        void run()
+    //        {
+    //            char commandBuffer[256];
+    //            std::string command;
+    //            while (true) {
+    //                auto now = millis();
+    //
+    //                if (this->communication.hasData()) {
+    //                    auto bytesRead = this->communication.read(commandBuffer, sizeof(commandBuffer));
+    //                    if (bytesRead == 0) {
+    //                        continue;
+    //                    }
+    //
+    //                    std::map<::OpenGloves::Command, uint16_t> commands = {};
+    //                    this->encodingService.deserialize(commandBuffer, bytesRead, commands);
+    //
+    //                    for (auto& [command, value] : commands) {
+    //                        this->handleCommand(command, value);
+    //                    }
+    //                }
+    //
+    //                // Delay until the next update.
+    //                auto elapsed = millis() - now;
+    //                if (elapsed < this->updateIntervalMs) {
+    //                    delay(this->updateIntervalMs - elapsed);
+    //                }
+    //            }
+    //        }
+    //
+    //        void handleCommand(Command command, uint16_t value)
+    //        {
+    //            switch (command) {
+    //                case Command::ThumbCurl:
+    //                    if (this->actuators.thumb.has_value()) {
+    //                        this->actuators.thumb.value()->writeOutput(value);
+    //                    }
+    //                    break;
+    //                case Command::IndexCurl:
+    //                    if (this->actuators.index.has_value()) {
+    //                        this->actuators.index.value()->writeOutput(value);
+    //                    }
+    //                    break;
+    //                case Command::MiddleCurl:
+    //                    if (this->actuators.middle.has_value()) {
+    //                        this->actuators.middle.value()->writeOutput(value);
+    //                    }
+    //                    break;
+    //                case Command::RingCurl:
+    //                    if (this->actuators.ring.has_value()) {
+    //                        this->actuators.ring.value()->writeOutput(value);
+    //                    }
+    //                    break;
+    //                case Command::PinkyCurl:
+    //                    if (this->actuators.pinky.has_value()) {
+    //                        this->actuators.pinky.value()->writeOutput(value);
+    //                    }
+    //                    break;
+    //                default:
+    //                    log_w("Unhandled command: %d", command);
+    //                    break;
+    //            }
+    //        }
+    //    };
+} // namespace SenseShift::OpenGloves
diff --git a/lib/opengloves_task/opengloves_task.hpp b/lib/opengloves_task/opengloves_task.hpp
deleted file mode 100644
index c327890d..00000000
--- a/lib/opengloves_task/opengloves_task.hpp
+++ /dev/null
@@ -1,348 +0,0 @@
-#pragma once
-
-#include <Arduino.h>
-
-#include <optional>
-
-#include <senseshift/arduino/input/sensor/analog.hpp>
-#include <senseshift/arduino/input/sensor/digital.hpp>
-#include <senseshift/freertos/task.hpp>
-#include <senseshift/input/sensor.hpp>
-#include <senseshift/opengloves/encoding/alpha.hpp>
-#include <senseshift/opengloves/opengloves.hpp>
-#include <senseshift/utility.hpp>
-#include <sensor/og_finger.hpp>
-#include <sensor/og_gesture.hpp>
-
-namespace OpenGloves {
-    struct OpenGlovesTrackingTaskConfig {
-        size_t updateIntervalMs;
-        size_t calibrationDuration;
-        bool alwaysCalibrate;
-
-        /**
-         * @param updateRate The rate at which the sensors should be updated in Hz.
-         * @param calibrationDuration The duration in milliseconds that the
-         * calibration button should be held for.
-         */
-        OpenGlovesTrackingTaskConfig(size_t updateRate, size_t calibrationDuration, bool alwaysCalibrate) :
-          calibrationDuration(calibrationDuration), alwaysCalibrate(alwaysCalibrate)
-        {
-            // Convert the update rate to an interval in milliseconds.
-            this->updateIntervalMs = 1000 / updateRate;
-        }
-    };
-
-    class OpenGlovesTrackingTask : public SenseShift::FreeRTOS::Task<OpenGlovesTrackingTask> {
-        friend class SenseShift::FreeRTOS::Task<OpenGlovesTrackingTask>;
-
-      public:
-        /**
-         * @param config The configuration for the OpenGloves tracking task.
-         * @param communication The communication interface.
-         * @param fingers The finger sensors.
-         * @param buttons The button sensors.
-         * @param joysticks The joystick sensors.
-         * @param otherSensors Other sensors that should be updated.
-         * @param calibrationButton The calibration button sensor, optional.
-         * @param taskConfig The task configuration.
-         */
-        OpenGlovesTrackingTask(
-          OpenGlovesTrackingTaskConfig& config,
-          ::SenseShift::OpenGloves::ITransport& communication,
-          HandSensors& fingers,
-          std::vector<StringEncodedMemoizedSensor<bool>*>& buttons,
-          std::vector<StringEncodedMemoizedSensor<uint16_t>*>& joysticks,
-          std::vector<IStringEncodedMemoizedSensor*>& otherSensors,
-          std::optional<StringEncodedMemoizedSensor<bool>>& calibrationButton,
-          SenseShift::FreeRTOS::TaskConfig taskConfig
-        ) :
-          config(config),
-          communication(communication),
-          fingers(fingers),
-          buttons(buttons),
-          joysticks(joysticks),
-          otherSensors(otherSensors),
-          calibrationButton(calibrationButton),
-          allSensors(std::vector<IStringEncodedMemoizedSensor*>()),
-          SenseShift::FreeRTOS::Task<OpenGlovesTrackingTask>(taskConfig)
-        {
-            if (fingers.thumb.has_value()) {
-                auto* thumb = &fingers.thumb.value();
-                this->calibrated.push_back(thumb);
-                this->allSensors.push_back(thumb);
-            }
-            if (fingers.index.has_value()) {
-                auto* index = &fingers.index.value();
-                this->calibrated.push_back(index);
-                this->allSensors.push_back(index);
-            }
-            if (fingers.middle.has_value()) {
-                auto* middle = &fingers.middle.value();
-                this->calibrated.push_back(middle);
-                this->allSensors.push_back(middle);
-            }
-            if (fingers.ring.has_value()) {
-                auto* ring = &fingers.ring.value();
-                this->calibrated.push_back(ring);
-                this->allSensors.push_back(ring);
-            }
-            if (fingers.pinky.has_value()) {
-                auto* pinky = &fingers.pinky.value();
-                this->calibrated.push_back(pinky);
-                this->allSensors.push_back(pinky);
-            }
-
-            for (auto* button : buttons) {
-                this->allSensors.push_back(button);
-            }
-
-            if (calibrationButton.has_value()) {
-                this->allSensors.push_back(&calibrationButton.value());
-            }
-
-            for (auto* joystick : joysticks) {
-                this->allSensors.push_back(joystick);
-            }
-
-            for (auto* otherSensor : otherSensors) {
-                this->allSensors.push_back(otherSensor);
-            }
-
-            // sort all sensors by type for easier debugging
-            std::sort(
-              this->allSensors.begin(),
-              this->allSensors.end(),
-              [](IStringEncodedMemoizedSensor* a, IStringEncodedMemoizedSensor* b) {
-                  return a->getType() < b->getType();
-              }
-            );
-        }
-
-        void begin() override
-        {
-            log_d("Starting OpenGloves tracking task: %p", this);
-            this->setup();
-            this->SenseShift::FreeRTOS::Task<OpenGlovesTrackingTask>::begin();
-        };
-
-      private:
-        OpenGlovesTrackingTaskConfig& config;
-
-        HandSensors& fingers;
-        ::SenseShift::OpenGloves::ITransport& communication;
-        ::SenseShift::OpenGloves::AlphaEncodingService encodingService =
-          ::SenseShift::OpenGloves::AlphaEncodingService();
-
-        std::vector<StringEncodedMemoizedSensor<bool>*>& buttons;
-        std::vector<StringEncodedMemoizedSensor<uint16_t>*>& joysticks;
-        std::vector<IStringEncodedMemoizedSensor*>& otherSensors;
-
-        std::vector<IStringEncodedMemoizedSensor*> allSensors;
-
-        std::optional<StringEncodedMemoizedSensor<bool>>& calibrationButton;
-        std::vector<SenseShift::Calibration::ICalibrated*> calibrated =
-          std::vector<SenseShift::Calibration::ICalibrated*>();
-        unsigned long long calibrationStarted = 0;
-
-        void startCalibration()
-        {
-            for (auto* input : this->calibrated) {
-                input->resetCalibration();
-                input->enableCalibration();
-            }
-            this->calibrationStarted = millis();
-        }
-
-        void setup()
-        {
-            log_d("Setting up OpenGloves tracking task: %p", this);
-            log_d("There is a total of %d sensors", this->allSensors.size());
-            for (auto* input : this->allSensors) {
-                log_d("Setting up sensor: %c", input->getType());
-                input->init();
-            }
-
-            // Start calibration if no calibration button is present or if configured to always calibrate.
-            if (!this->calibrationButton.has_value() || this->config.alwaysCalibrate) {
-                log_d("Starting calibration on startup");
-                this->startCalibration();
-            }
-
-            this->communication.setup();
-        }
-
-        void run()
-        {
-            while (true) {
-                auto now = millis();
-
-                // Update the sensors.
-                for (auto* input : this->allSensors) {
-                    input->updateValue();
-                }
-
-                // Update the calibration if calibration has not started, calibration is not configured to always run,
-                // and the calibration button is present and pressed.
-                if (calibrationStarted == 0 &&
-                    !this->config.alwaysCalibrate &&
-                    this->calibrationButton.has_value() &&
-                    this->calibrationButton.value().getValue()
-                ) {
-                    log_d("Calibration started");
-                    this->startCalibration();
-                }
-
-                // Send the sensor values.
-                char command[256];
-                size_t length = this->encodingService.serialize(this->allSensors, command);
-                this->communication.send(command, length);
-
-                // Check if the calibration has finished.
-                if (!(this->config.alwaysCalibrate) && calibrationStarted > 0 && (now - calibrationStarted) > CALIBRATION_DURATION) {
-                    log_d("Calibration finished");
-                    for (size_t i = 0; i < calibrated.size(); i++) {
-                        auto* input = calibrated[i];
-                        input->disableCalibration();
-                    }
-                    calibrationStarted = 0;
-                }
-
-                // Delay until the next update.
-                auto elapsed = millis() - now;
-
-                log_d(
-                  "Update took %d ms, theoretical max rate is %dHz (target is %dHz)",
-                  elapsed,
-                  1000 / elapsed,
-                  1000 / this->config.updateIntervalMs
-                );
-
-                if (elapsed < this->config.updateIntervalMs) {
-                    delay(this->config.updateIntervalMs - elapsed);
-                }
-            }
-        };
-    };
-
-    class OpenGlovesForceFeedbackTask : public SenseShift::FreeRTOS::Task<OpenGlovesForceFeedbackTask> {
-        friend class SenseShift::FreeRTOS::Task<OpenGlovesForceFeedbackTask>;
-
-      public:
-        OpenGlovesForceFeedbackTask(
-          ::SenseShift::OpenGloves::ITransport& communication,
-          HandActuators& actuators,
-          size_t updateRate,
-          SenseShift::FreeRTOS::TaskConfig taskConfig
-        ) :
-          communication(communication),
-          actuators(actuators),
-          SenseShift::FreeRTOS::Task<OpenGlovesForceFeedbackTask>(taskConfig)
-        {
-            this->updateIntervalMs = 1000 / updateRate;
-        };
-
-        void begin() override
-        {
-            log_d("Starting OpenGloves force feedback task: %p", this);
-            this->setup();
-            this->SenseShift::FreeRTOS::Task<OpenGlovesForceFeedbackTask>::begin();
-        };
-
-      private:
-        ::SenseShift::OpenGloves::ITransport& communication;
-        HandActuators& actuators;
-        size_t updateIntervalMs;
-        ::SenseShift::OpenGloves::AlphaEncodingService encodingService =
-          ::SenseShift::OpenGloves::AlphaEncodingService();
-
-        void setup()
-        {
-            log_d("Setting up OpenGloves force feedback task: %p", this);
-            this->communication.setup();
-
-            if (this->actuators.thumb.has_value()) {
-                this->actuators.thumb.value()->setup();
-            }
-
-            if (this->actuators.index.has_value()) {
-                this->actuators.index.value()->setup();
-            }
-
-            if (this->actuators.middle.has_value()) {
-                this->actuators.middle.value()->setup();
-            }
-
-            if (this->actuators.ring.has_value()) {
-                this->actuators.ring.value()->setup();
-            }
-
-            if (this->actuators.pinky.has_value()) {
-                this->actuators.pinky.value()->setup();
-            }
-        }
-
-        void run()
-        {
-            char commandBuffer[256];
-            std::string command;
-            while (true) {
-                auto now = millis();
-
-                if (this->communication.hasData()) {
-                    auto bytesRead = this->communication.read(commandBuffer, sizeof(commandBuffer));
-                    if (bytesRead == 0) {
-                        continue;
-                    }
-
-                    std::map<::OpenGloves::Command, uint16_t> commands = {};
-                    this->encodingService.deserialize(commandBuffer, bytesRead, commands);
-
-                    for (auto& [command, value] : commands) {
-                        this->handleCommand(command, value);
-                    }
-                }
-
-                // Delay until the next update.
-                auto elapsed = millis() - now;
-                if (elapsed < this->updateIntervalMs) {
-                    delay(this->updateIntervalMs - elapsed);
-                }
-            }
-        }
-
-        void handleCommand(Command command, uint16_t value)
-        {
-            switch (command) {
-                case Command::ThumbCurl:
-                    if (this->actuators.thumb.has_value()) {
-                        this->actuators.thumb.value()->writeOutput(value);
-                    }
-                    break;
-                case Command::IndexCurl:
-                    if (this->actuators.index.has_value()) {
-                        this->actuators.index.value()->writeOutput(value);
-                    }
-                    break;
-                case Command::MiddleCurl:
-                    if (this->actuators.middle.has_value()) {
-                        this->actuators.middle.value()->writeOutput(value);
-                    }
-                    break;
-                case Command::RingCurl:
-                    if (this->actuators.ring.has_value()) {
-                        this->actuators.ring.value()->writeOutput(value);
-                    }
-                    break;
-                case Command::PinkyCurl:
-                    if (this->actuators.pinky.has_value()) {
-                        this->actuators.pinky.value()->writeOutput(value);
-                    }
-                    break;
-                default:
-                    log_w("Unhandled command: %d", command);
-                    break;
-            }
-        }
-    };
-} // namespace OpenGloves

From 825e6f0a0d20125f8040ac38cf92a1063d771e10 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Sat, 10 Feb 2024 22:20:47 +0400
Subject: [PATCH 62/82] test(Input): cover filters

---
 lib/io/senseshift/input/filter.hpp           |  49 ++++--
 test/test_io_filter/main.cpp                 | 172 +++++++++++++++++++
 test/test_opengloves_alpha_encoding/main.cpp |   4 +
 3 files changed, 207 insertions(+), 18 deletions(-)
 create mode 100644 test/test_io_filter/main.cpp

diff --git a/lib/io/senseshift/input/filter.hpp b/lib/io/senseshift/input/filter.hpp
index 75f65dc2..6601d5ad 100644
--- a/lib/io/senseshift/input/filter.hpp
+++ b/lib/io/senseshift/input/filter.hpp
@@ -2,6 +2,7 @@
 
 #include <algorithm>
 #include <array>
+#include <cmath>
 #include <cstdint>
 #include <cstdlib>
 #include <deque>
@@ -33,17 +34,6 @@ namespace SenseShift::Input::Filter {
         virtual auto filter(ISimpleSensor<ValueType>* sensor, ValueType value) -> ValueType = 0;
     };
 
-    template<typename Tp>
-    class AddFilter : public IFilter<Tp> {
-      public:
-        explicit AddFilter(Tp offset) : offset_(offset){};
-
-        auto filter(ISimpleSensor<Tp>* /*sensor*/, Tp value) -> Tp override { return value + this->offset_; }
-
-      private:
-        Tp offset_;
-    };
-
     template<typename Tp>
     class IFiltered {
       public:
@@ -58,6 +48,17 @@ namespace SenseShift::Input::Filter {
         void clearFilters() = 0;
     };
 
+    template<typename Tp>
+    class AddFilter : public IFilter<Tp> {
+      public:
+        explicit AddFilter(Tp offset) : offset_(offset){};
+
+        auto filter(ISimpleSensor<Tp>* /*sensor*/, Tp value) -> Tp override { return value + this->offset_; }
+
+      private:
+        Tp offset_;
+    };
+
     template<typename Tp>
     class SubtractFilter : public IFilter<Tp> {
       public:
@@ -82,6 +83,8 @@ namespace SenseShift::Input::Filter {
 
     class VoltageDividerFilter : public MultiplyFilter<float> {
       public:
+        /// Calculates the original voltage from the voltage divider.
+        ///
         /// \param r1 The resistance in Ohms of the first resistor in the voltage divider.
         /// Example: 27000.0F.
         /// \param r2 The resistance in Ohms of the second resistor in the voltage divider.
@@ -91,7 +94,7 @@ namespace SenseShift::Input::Filter {
         /// \code
         /// new VoltageDividerFilter(27000.0F, 100000.0F);
         /// \endcode
-        explicit VoltageDividerFilter(float r1, float r2) : MultiplyFilter<float>(r2 / (r1 + r2)){};
+        explicit VoltageDividerFilter(float r1, float r2) : MultiplyFilter<float>((r1 + r2) / r2){};
     };
 
     template<typename Tp>
@@ -220,7 +223,7 @@ namespace SenseShift::Input::Filter {
 
       private:
         std::size_t window_size_;
-        std::deque<Tp> values_;
+        std::deque<Tp> queue_;
 
         [[nodiscard]] auto getAverage() const -> Tp
         {
@@ -239,14 +242,24 @@ namespace SenseShift::Input::Filter {
       public:
         explicit ExponentialMovingAverageFilter(float alpha) : alpha_(alpha){};
 
+        template<typename U = Tp, std::enable_if_t<std::is_same_v<U, float>, int> = 0>
+        explicit ExponentialMovingAverageFilter(float alpha) : alpha_(alpha), acc_(std::nanf){};
+
         auto filter(ISimpleSensor<Tp>* /*sensor*/, Tp value) -> Tp override
         {
-            this->acc_ = this->alpha_ * value + (1 - this->alpha_) * this->acc_;
+            if (this->is_first_) {
+                this->is_first_ = false;
+
+                this->acc_ = value;
+                return this->acc_;
+            }
 
+            this->acc_ = (this->alpha_ * value) + ((1 - this->alpha_) * this->acc_);
             return this->acc_;
         }
 
       private:
+        bool is_first_ = true;
         float alpha_;
         Tp acc_;
     };
@@ -255,16 +268,16 @@ namespace SenseShift::Input::Filter {
     /// Usually used to filter out noise in the joystick.
     class CenterDeadzoneFilter : public IFilter<float> {
       public:
-        explicit CenterDeadzoneFilter(float deadzone) : deadzone_(deadzone){};
+        explicit CenterDeadzoneFilter(float deadzone, float center = 0.5f) : deadzone_(deadzone), center_(center){};
 
         auto filter(ISimpleSensor<float>* /*sensor*/, float value) -> float override
         {
-            float const deviation = std::abs(CENTER - value);
-            return deviation < deadzone_ ? CENTER : value;
+            float const deviation = std::abs(value - this->center_);
+            return deviation < deadzone_ ? this->center_ : value;
         }
 
       private:
-        static constexpr float CENTER = 0.5F;
+        const float center_;
 
         float deadzone_;
     };
diff --git a/test/test_io_filter/main.cpp b/test/test_io_filter/main.cpp
new file mode 100644
index 00000000..f5002158
--- /dev/null
+++ b/test/test_io_filter/main.cpp
@@ -0,0 +1,172 @@
+#include <senseshift/input/filter.hpp>
+#include <senseshift/input/sensor.hpp>
+#include <unity.h>
+
+#define ASSERT_EQUAL_FLOAT_ROUNDED(expected, actual, precision)                \
+    TEST_ASSERT_EQUAL_FLOAT(                                                   \
+      std::round(expected* std::pow(10, precision)) / std::pow(10, precision), \
+      std::round(actual * std::pow(10, precision)) / std::pow(10, precision)   \
+    )
+
+using namespace SenseShift::Input;
+using namespace SenseShift::Input::Filter;
+
+void test_add_filter(void)
+{
+    IFilter<float>* filter = new AddFilter<float>(5.0f);
+
+    TEST_ASSERT_EQUAL_FLOAT(10.0f, filter->filter(nullptr, 5.0f));
+    TEST_ASSERT_EQUAL_FLOAT(11.0f, filter->filter(nullptr, 6.0f));
+    TEST_ASSERT_EQUAL_FLOAT(12.0f, filter->filter(nullptr, 7.0f));
+}
+
+void test_subtract_filter(void)
+{
+    IFilter<float>* filter = new SubtractFilter<float>(5.0f);
+
+    TEST_ASSERT_EQUAL_FLOAT(0.0f, filter->filter(nullptr, 5.0f));
+    TEST_ASSERT_EQUAL_FLOAT(-1.0f, filter->filter(nullptr, 4.0f));
+    TEST_ASSERT_EQUAL_FLOAT(-2.0f, filter->filter(nullptr, 3.0f));
+}
+
+void test_multiply_filter(void)
+{
+    IFilter<float>* filter = new MultiplyFilter<float>(5.0f);
+
+    TEST_ASSERT_EQUAL_FLOAT(25.0f, filter->filter(nullptr, 5.0f));
+    TEST_ASSERT_EQUAL_FLOAT(30.0f, filter->filter(nullptr, 6.0f));
+    TEST_ASSERT_EQUAL_FLOAT(35.0f, filter->filter(nullptr, 7.0f));
+}
+
+void test_voltage_divider_filter(void)
+{
+    IFilter<float>* filter = new VoltageDividerFilter(27000.0F, 100000.0F);
+
+    TEST_ASSERT_EQUAL_FLOAT(0.0F, filter->filter(nullptr, 0.0F));
+    TEST_ASSERT_EQUAL_FLOAT(3.429F, filter->filter(nullptr, 2.7F));
+    TEST_ASSERT_EQUAL_FLOAT(3.81F, filter->filter(nullptr, 3.0F));
+    TEST_ASSERT_EQUAL_FLOAT(4.191F, filter->filter(nullptr, 3.3F));
+}
+
+void test_clamp_filter(void)
+{
+    IFilter<float>* filter = new ClampFilter<float>(0.0f, 1.0f);
+
+    TEST_ASSERT_EQUAL_FLOAT(0.0f, filter->filter(nullptr, -1.0f));
+    TEST_ASSERT_EQUAL_FLOAT(0.0f, filter->filter(nullptr, 0.0f));
+    TEST_ASSERT_EQUAL_FLOAT(0.5f, filter->filter(nullptr, 0.5f));
+    TEST_ASSERT_EQUAL_FLOAT(1.0f, filter->filter(nullptr, 1.0f));
+    TEST_ASSERT_EQUAL_FLOAT(1.0f, filter->filter(nullptr, 2.0f));
+}
+
+void test_lambda_filter(void)
+{
+    IFilter<float>* filter = new LambdaFilter<float>([](float value) {
+        return value * 42.F; // Cause after all, 42 is the answer to everything.
+    });
+
+    TEST_ASSERT_EQUAL_FLOAT(42.0f, filter->filter(nullptr, 1.0f));
+    TEST_ASSERT_EQUAL_FLOAT(84.0f, filter->filter(nullptr, 2.0f));
+    TEST_ASSERT_EQUAL_FLOAT(126.0f, filter->filter(nullptr, 3.0f));
+}
+
+void test_sliding_window_moving_average_filter(void)
+{
+    IFilter<float>* filter = new SlidingWindowMovingAverageFilter<float>(3);
+
+    TEST_ASSERT_EQUAL_FLOAT(1.0f, filter->filter(nullptr, 1.0f));   // 1 / 1 = 1
+    TEST_ASSERT_EQUAL_FLOAT(1.5f, filter->filter(nullptr, 2.0f));   // (1 + 2) / 2 = 1.5
+    TEST_ASSERT_EQUAL_FLOAT(2.0f, filter->filter(nullptr, 3.0f));   // (1 + 2 + 3) / 3 = 2
+    TEST_ASSERT_EQUAL_FLOAT(3.0f, filter->filter(nullptr, 4.0f));   // (2 + 3 + 4) / 3 = 3
+    TEST_ASSERT_EQUAL_FLOAT(4.0f, filter->filter(nullptr, 5.0f));   // (3 + 4 + 5) / 3 = 4
+    TEST_ASSERT_EQUAL_FLOAT(5.0f, filter->filter(nullptr, 6.0f));   // (4 + 5 + 6) / 3 = 5
+    TEST_ASSERT_EQUAL_FLOAT(6.0f, filter->filter(nullptr, 7.0f));   // (5 + 6 + 7) / 3 = 6
+    TEST_ASSERT_EQUAL_FLOAT(7.0f, filter->filter(nullptr, 8.0f));   // (6 + 7 + 8) / 3 = 7
+    TEST_ASSERT_EQUAL_FLOAT(8.0f, filter->filter(nullptr, 9.0f));   // (7 + 8 + 9) / 3 = 8
+    TEST_ASSERT_EQUAL_FLOAT(9.0f, filter->filter(nullptr, 10.0f));  // (8 + 9 + 10) / 3 = 9
+    TEST_ASSERT_EQUAL_FLOAT(10.0f, filter->filter(nullptr, 11.0f)); // (9 + 10 + 11) / 3 = 10
+
+    ASSERT_EQUAL_FLOAT_ROUNDED(10.67f, filter->filter(nullptr, 11.0f), 2); // (10 + 11 + 11) / 3 = 10.67
+    TEST_ASSERT_EQUAL_FLOAT(11.0f, filter->filter(nullptr, 11.0f));        // (11 + 11 + 11) / 3 = 11
+    ASSERT_EQUAL_FLOAT_ROUNDED(11.33f, filter->filter(nullptr, 12.0f), 2); // (11 + 11 + 12) / 3 = 11.33
+}
+
+void test_exponential_moving_average_filter(void)
+{
+    IFilter<float>* filter = new ExponentialMovingAverageFilter<float>(0.5f);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1.0f, filter->filter(nullptr, 1.0f), 2);    // 1.0
+    ASSERT_EQUAL_FLOAT_ROUNDED(1.5f, filter->filter(nullptr, 2.0f), 2);    // (0.5 * 1.0) + (0.5 * 2.0) = 1.5
+    ASSERT_EQUAL_FLOAT_ROUNDED(2.25f, filter->filter(nullptr, 3.0f), 2);   // (0.5 * 1.5) + (0.5 * 3.0) = 2.25
+    ASSERT_EQUAL_FLOAT_ROUNDED(3.125f, filter->filter(nullptr, 4.0f), 2);  // (0.5 * 2.25) + (0.5 * 4.0) = 3.125
+    ASSERT_EQUAL_FLOAT_ROUNDED(4.0625f, filter->filter(nullptr, 5.0f), 2); // (0.5 * 3.125) + (0.5 * 5.0) = 4.0625
+
+    filter = new ExponentialMovingAverageFilter<float>(0.1f);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1.0f, filter->filter(nullptr, 1.0f), 2);    // 1.0
+    ASSERT_EQUAL_FLOAT_ROUNDED(1.1f, filter->filter(nullptr, 2.0f), 2);    // (0.1 * 2.0) + (0.9 * 1.0) = 1.1
+    ASSERT_EQUAL_FLOAT_ROUNDED(1.29f, filter->filter(nullptr, 3.0f), 2);   // (0.1 * 3.0) + (0.9 * 1.1) = 1.29
+    ASSERT_EQUAL_FLOAT_ROUNDED(1.561f, filter->filter(nullptr, 4.0f), 2);  // (0.1 * 4.0) + (0.9 * 1.29) = 1.561
+    ASSERT_EQUAL_FLOAT_ROUNDED(1.9049f, filter->filter(nullptr, 5.0f), 2); // (0.1 * 5.0) + (0.9 * 1.561) = 1.9049
+
+    filter = new ExponentialMovingAverageFilter<float>(0.9f);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1.0f, filter->filter(nullptr, 1.0f), 2);     // 1.0
+    ASSERT_EQUAL_FLOAT_ROUNDED(1.9f, filter->filter(nullptr, 2.0f), 2);     // (0.9 * 2.0) + (0.1 * 1.0) = 1.9
+    ASSERT_EQUAL_FLOAT_ROUNDED(2.89f, filter->filter(nullptr, 3.0f), 2);    // (0.9 * 3.0) + (0.1 * 1.9) = 2.89
+    ASSERT_EQUAL_FLOAT_ROUNDED(3.889f, filter->filter(nullptr, 4.0f), 2);   // (0.9 * 4.0) + (0.1 * 2.89) = 3.889
+    ASSERT_EQUAL_FLOAT_ROUNDED(4.8889f, filter->filter(nullptr, 5.0f), 2);  // (0.9 * 5.0) + (0.1 * 3.889) = 4.8889
+    ASSERT_EQUAL_FLOAT_ROUNDED(4.98889, filter->filter(nullptr, 5.0f), 2);  // (0.9 * 5.0) + (0.1 * 4.8889) = 4.98889
+    ASSERT_EQUAL_FLOAT_ROUNDED(4.99889, filter->filter(nullptr, 5.0f), 2);  // (0.9 * 5.0) + (0.1 * 4.98889) = 4.99889
+    ASSERT_EQUAL_FLOAT_ROUNDED(4.999889, filter->filter(nullptr, 5.0f), 2); // (0.9 * 5.0) + (0.1 * 4.99889) = 4.999889
+}
+
+void test_center_deadzone_filter(void)
+{
+    IFilter<float>* filter = new CenterDeadzoneFilter(0.1f);
+
+    TEST_ASSERT_EQUAL_FLOAT(0.0f, filter->filter(nullptr, 0.0f));
+    TEST_ASSERT_EQUAL_FLOAT(0.1f, filter->filter(nullptr, 0.1f));
+
+    // Inside the deadzone
+    TEST_ASSERT_EQUAL_FLOAT(0.5f, filter->filter(nullptr, 0.43f));
+    TEST_ASSERT_EQUAL_FLOAT(0.5f, filter->filter(nullptr, 0.5f));
+    TEST_ASSERT_EQUAL_FLOAT(0.5f, filter->filter(nullptr, 0.57f));
+
+    TEST_ASSERT_EQUAL_FLOAT(0.9f, filter->filter(nullptr, 0.9f));
+    TEST_ASSERT_EQUAL_FLOAT(1.0f, filter->filter(nullptr, 1.0f));
+}
+
+int process(void)
+{
+    UNITY_BEGIN();
+
+    RUN_TEST(test_add_filter);
+    RUN_TEST(test_subtract_filter);
+    RUN_TEST(test_multiply_filter);
+    RUN_TEST(test_voltage_divider_filter);
+    RUN_TEST(test_clamp_filter);
+    RUN_TEST(test_lambda_filter);
+    RUN_TEST(test_sliding_window_moving_average_filter);
+    RUN_TEST(test_exponential_moving_average_filter);
+    RUN_TEST(test_center_deadzone_filter);
+
+    return UNITY_END();
+}
+
+#ifdef ARDUINO
+
+#include <Arduino.h>
+
+void setup(void)
+{
+    process();
+}
+
+void loop(void) {}
+
+#else
+
+int main(int argc, char** argv)
+{
+    return process();
+}
+
+#endif
\ No newline at end of file
diff --git a/test/test_opengloves_alpha_encoding/main.cpp b/test/test_opengloves_alpha_encoding/main.cpp
index c0162e99..4786fa0a 100644
--- a/test/test_opengloves_alpha_encoding/main.cpp
+++ b/test/test_opengloves_alpha_encoding/main.cpp
@@ -8,6 +8,10 @@ void test_encode_input_peripherals(void)
     const IEncoder* encoder = new AlphaEncoder();
 
     const std::vector<std::tuple<InputPeripheralData, std::string>> cases = {
+        {
+          InputPeripheralData{},
+          "A0B0C0D0E0\n",
+        },
         {
             InputPeripheralData({
                 .curl = {

From 728bd795448dfe797d26ec0d722fe19bf9291718 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Sat, 10 Feb 2024 22:58:14 +0400
Subject: [PATCH 63/82] fix(OpenGloves): tick input sensors

---
 .../mode_configs/opengloves/opengloves.cpp    |  3 +-
 ini/opengloves-lucidgloves.ini                |  2 -
 .../arduino/input/sensor/digital.hpp          |  6 ++-
 lib/io/senseshift/input/filter.hpp            |  3 --
 lib/io/senseshift/input/sensor.hpp            |  6 +++
 lib/opengloves/opengloves/opengloves.cpp      |  2 +-
 lib/opengloves/opengloves/opengloves.hpp      |  5 ++
 .../senseshift/opengloves/opengloves.hpp      |  2 +-
 .../senseshift/opengloves/opengloves_task.hpp | 50 +++++++++++++++---
 test/test_io_sensor/main.cpp                  | 51 +++++++++----------
 10 files changed, 88 insertions(+), 42 deletions(-)

diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp
index a3e97875..1a603125 100644
--- a/firmware/mode_configs/opengloves/opengloves.cpp
+++ b/firmware/mode_configs/opengloves/opengloves.cpp
@@ -153,7 +153,8 @@ void setupMode()
     auto* pinch = new BUTTON_CLASS(PIN_BUTTON_PINCH, BUTTON_PINCH_INVERT);
 #endif
 
-    OpenGlovesTrackingComponent::Config const tracking_config(2000, false);
+    Serial.begin(115200);
+    OpenGlovesTrackingComponent::Config tracking_config(2000, true);
     auto* opengloves_tracking =
       new OpenGlovesTrackingComponent(tracking_config, input_sensors, new StreamTransport(Serial));
 
diff --git a/ini/opengloves-lucidgloves.ini b/ini/opengloves-lucidgloves.ini
index f535f768..f150cfc4 100644
--- a/ini/opengloves-lucidgloves.ini
+++ b/ini/opengloves-lucidgloves.ini
@@ -65,8 +65,6 @@ build_flags = ${opengloves.build_flags} -D OG_ENCODE_FAST
 ; -D PIN_BUTTON_GRAB=13 ; unused if GESTURE_GRAB is true
 ; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
 
-; todo: add servo pins
-
 build_unflags = ${opengloves.build_unflags}
 build_src_filter = ${opengloves.build_src_filter}
                    +<mode_configs/opengloves/opengloves.cpp>
diff --git a/lib/arduino/senseshift/arduino/input/sensor/digital.hpp b/lib/arduino/senseshift/arduino/input/sensor/digital.hpp
index 9a3d101c..cc3e7573 100644
--- a/lib/arduino/senseshift/arduino/input/sensor/digital.hpp
+++ b/lib/arduino/senseshift/arduino/input/sensor/digital.hpp
@@ -18,12 +18,14 @@ namespace SenseShift::Arduino::Input {
     };
 
     template<>
-    [[nodiscard]] inline auto DigitalSimpleSensor<false>::getValue() -> bool {
+    [[nodiscard]] inline auto DigitalSimpleSensor<false>::getValue() -> bool
+    {
         return digitalRead(this->pin_) == LOW;
     }
 
     template<>
-    [[nodiscard]] inline auto DigitalSimpleSensor<true>::getValue() -> bool {
+    [[nodiscard]] inline auto DigitalSimpleSensor<true>::getValue() -> bool
+    {
         return digitalRead(this->pin_) == HIGH;
     }
 } // namespace SenseShift::Arduino::Input
diff --git a/lib/io/senseshift/input/filter.hpp b/lib/io/senseshift/input/filter.hpp
index 6601d5ad..b13bc857 100644
--- a/lib/io/senseshift/input/filter.hpp
+++ b/lib/io/senseshift/input/filter.hpp
@@ -242,9 +242,6 @@ namespace SenseShift::Input::Filter {
       public:
         explicit ExponentialMovingAverageFilter(float alpha) : alpha_(alpha){};
 
-        template<typename U = Tp, std::enable_if_t<std::is_same_v<U, float>, int> = 0>
-        explicit ExponentialMovingAverageFilter(float alpha) : alpha_(alpha), acc_(std::nanf){};
-
         auto filter(ISimpleSensor<Tp>* /*sensor*/, Tp value) -> Tp override
         {
             if (this->is_first_) {
diff --git a/lib/io/senseshift/input/sensor.hpp b/lib/io/senseshift/input/sensor.hpp
index e76b53b8..3d6e1b1f 100644
--- a/lib/io/senseshift/input/sensor.hpp
+++ b/lib/io/senseshift/input/sensor.hpp
@@ -45,6 +45,12 @@ namespace SenseShift::Input {
 
         explicit Sensor() = default;
 
+        template<typename U = Tp, std::enable_if_t<std::is_same_v<U, float>, int> = 0>
+        explicit Sensor(float value = 0.0f) : raw_value_(value)
+        {
+            this->value_ = this->applyFilters(value);
+        }
+
         /// Appends a filter to the sensor's filter chain.
         ///
         /// \param filter The filter to add.
diff --git a/lib/opengloves/opengloves/opengloves.cpp b/lib/opengloves/opengloves/opengloves.cpp
index 0411c6d9..924f8914 100644
--- a/lib/opengloves/opengloves/opengloves.cpp
+++ b/lib/opengloves/opengloves/opengloves.cpp
@@ -59,7 +59,7 @@ namespace og {
                   length - written,
                   "%c%u",
                   'A' + i,
-                  ifloor<std::uint8_t, float>(finger_curl * MAX_ANALOG_VALUE)
+                  ifloor<std::uint16_t, float>(finger_curl * MAX_ANALOG_VALUE)
                 );
             }
 #else
diff --git a/lib/opengloves/opengloves/opengloves.hpp b/lib/opengloves/opengloves/opengloves.hpp
index 4d5b470f..e961f908 100644
--- a/lib/opengloves/opengloves/opengloves.hpp
+++ b/lib/opengloves/opengloves/opengloves.hpp
@@ -35,10 +35,15 @@ namespace og {
 
     template<typename Tf = float>
     union InputFingerCurl {
+        /// Access the curl as an array.
         std::array<Tf, 4> curl; // NOLINT(*-magic-numbers): I'm sure our finger aren't changing anytime soon
+
         union {
+            /// The total curl of the finger.
+            /// Only use it if you do not use per-joint tracking.
             Tf curl_total;
 
+            /// Access the individual curl joints.
             struct {
                 Tf curl_joint0;
                 Tf curl_joint1;
diff --git a/lib/opengloves/senseshift/opengloves/opengloves.hpp b/lib/opengloves/senseshift/opengloves/opengloves.hpp
index 14354bcd..0b2d6832 100644
--- a/lib/opengloves/senseshift/opengloves/opengloves.hpp
+++ b/lib/opengloves/senseshift/opengloves/opengloves.hpp
@@ -75,7 +75,7 @@ namespace SenseShift::OpenGloves {
 
         auto collectData() -> og::InputPeripheralData
         {
-            og::InputPeripheralData data;
+            og::InputPeripheralData data{};
 
             const auto& curls = this->curl.fingers;
             const auto& splays = this->splay.fingers;
diff --git a/lib/opengloves_freertos/senseshift/opengloves/opengloves_task.hpp b/lib/opengloves_freertos/senseshift/opengloves/opengloves_task.hpp
index bed2ed48..2a5a6f93 100644
--- a/lib/opengloves_freertos/senseshift/opengloves/opengloves_task.hpp
+++ b/lib/opengloves_freertos/senseshift/opengloves/opengloves_task.hpp
@@ -2,6 +2,7 @@
 
 #include <Arduino.h>
 
+#include <array>
 #include <cstddef>
 #include <optional>
 
@@ -15,6 +16,11 @@
 #include <opengloves/opengloves.hpp>
 #include <utility>
 
+#define SS_START_CALIBRATION_NOT_NULL(ptr) \
+    if ((ptr) != nullptr) {                \
+        (ptr)->startCalibration();         \
+    }
+
 namespace SenseShift::OpenGloves {
     class OpenGlovesTrackingComponent : public SenseShift::Component {
       public:
@@ -32,8 +38,8 @@ namespace SenseShift::OpenGloves {
             }
         };
 
-        OpenGlovesTrackingComponent(Config config, InputSensors& input_sensors, ITransport* communication) :
-          config(config), input_sensors_(std::move(input_sensors)), communication_(communication)
+        OpenGlovesTrackingComponent(Config& config, InputSensors& input_sensors, ITransport* communication) :
+          config_(config), input_sensors_(std::move(input_sensors)), communication_(communication)
         {
             this->encoder_ = new og::AlphaEncoder();
         }
@@ -42,19 +48,51 @@ namespace SenseShift::OpenGloves {
         {
             this->communication_->init();
             this->input_sensors_.init();
+
+            if (this->config_.always_calibrate_) {
+                this->startCalibration();
+            }
         }
 
         void tick() override
         {
+            this->input_sensors_.tick();
             const auto data = this->input_sensors_.collectData();
+            const auto length = this->encoder_->encode_input(data, buffer.data(), buffer.size());
+            this->communication_->send(buffer.data(), length);
+        }
+
+      protected:
+        void startCalibration()
+        {
+            for (auto& finger_curl : this->input_sensors_.curl.fingers) {
+                for (auto& joint_sensor : finger_curl.curl) {
+                    SS_START_CALIBRATION_NOT_NULL(joint_sensor);
+                }
+            }
 
-            char buffer[256];
-            this->encoder_->encode_input(data, buffer, sizeof(buffer));
-            this->communication_->send(buffer, sizeof(buffer));
+            for (auto& finger_splay : this->input_sensors_.splay.fingers) {
+                SS_START_CALIBRATION_NOT_NULL(finger_splay);
+            }
+
+            SS_START_CALIBRATION_NOT_NULL(this->input_sensors_.joystick.x);
+            SS_START_CALIBRATION_NOT_NULL(this->input_sensors_.joystick.y);
+            SS_START_CALIBRATION_NOT_NULL(this->input_sensors_.joystick.press);
+
+            for (auto& button : this->input_sensors_.buttons) {
+                SS_START_CALIBRATION_NOT_NULL(button.press);
+            }
+
+            for (auto& analog_button : this->input_sensors_.analog_buttons) {
+                SS_START_CALIBRATION_NOT_NULL(analog_button.press);
+                SS_START_CALIBRATION_NOT_NULL(analog_button.value);
+            }
         }
 
       private:
-        Config& config;
+        std::array<char, 256> buffer;
+
+        Config& config_;
         InputSensors input_sensors_;
         ITransport* communication_;
         og::IEncoder* encoder_;
diff --git a/test/test_io_sensor/main.cpp b/test/test_io_sensor/main.cpp
index f91382fe..188cbeaf 100644
--- a/test/test_io_sensor/main.cpp
+++ b/test/test_io_sensor/main.cpp
@@ -1,5 +1,5 @@
-#include <senseshift/input/sensor.hpp>
 #include <senseshift/input/analog_threshold.hpp>
+#include <senseshift/input/sensor.hpp>
 #include <unity.h>
 
 using namespace SenseShift::Input;
@@ -15,7 +15,7 @@ class TestAnalogCountingSensor : public ISimpleSensor<int> {
 };
 
 class TestAnalogSensor : public ISimpleSensor<int> {
-public:
+  public:
     int value = 0;
     int setupCounter = 0;
 
@@ -42,57 +42,56 @@ void test_memoized_sensor(void)
     TEST_ASSERT_EQUAL_INT(1, sensor->getValue());
 }
 
-class DummyCalibrator : public ::SenseShift::Input::Calibration::ICalibrator<int> {
+class DummyCalibrator : public ::SenseShift::Input::Calibration::ICalibrator<float> {
   public:
     uint8_t resetCounter = 0;
-    int calibrated = 0;
+    float calibrated = 0.0f;
 
     void reset() override
     {
         this->resetCounter++;
-        this->calibrated = 0;
+        this->calibrated = 0.0f;
     };
-    void update(int input) override { this->calibrated = input; };
-    int calibrate(int input) const override { return calibrated; };
+    void update(float input) override { this->calibrated = input; };
+    float calibrate(float input) const override { return calibrated; };
 };
 
 void test_calibrated_sensor(void)
 {
-    auto inner = new TestAnalogCountingSensor();
+    auto inner = new FloatSensor();
     auto calibrator = new DummyCalibrator();
 
     auto sensor = new SimpleSensorDecorator(inner);
     sensor->setCalibrator(calibrator);
 
-    TEST_ASSERT_EQUAL_INT(0, inner->setupCounter);
-    sensor->init();
-    TEST_ASSERT_EQUAL_INT(1, inner->setupCounter);
+    calibrator->update(-1.0f);
+    sensor->publishState(0.0f);
+    TEST_ASSERT_EQUAL_FLOAT(-1.0f, sensor->getValue());
 
-    calibrator->update(-1);
-    sensor->tick();
-    TEST_ASSERT_EQUAL_INT(-1, sensor->getValue());
+    sensor->publishState(100.0f);
+    TEST_ASSERT_EQUAL_FLOAT(-1.0f, sensor->getValue());
+
+    calibrator->update(2.0f);
+    sensor->publishState(102.0f);
+    TEST_ASSERT_EQUAL_FLOAT(2.0f, sensor->getValue());
 
     sensor->startCalibration();
-    sensor->tick();
-    TEST_ASSERT_EQUAL_INT(2, sensor->getValue());
+    sensor->publishState(200.0f);
+    TEST_ASSERT_EQUAL_FLOAT(200.0f, sensor->getValue());
 
-    sensor->stopCalibration();
-    sensor->tick();
-    TEST_ASSERT_EQUAL_INT(2, sensor->getValue());
+    sensor->publishState(202.0f);
+    TEST_ASSERT_EQUAL_FLOAT(202.0f, sensor->getValue());
 
-    sensor->reselCalibration();
-    sensor->tick();
-    TEST_ASSERT_EQUAL_INT(0, sensor->getValue());
-    TEST_ASSERT_EQUAL_INT(1, calibrator->resetCounter);
+    sensor->stopCalibration();
+    sensor->publishState(300.0f);
+    TEST_ASSERT_EQUAL_FLOAT(202.0f, sensor->getValue());
 }
 
 void test_sensor_filter_multiply(void)
 {
     auto inner = new TestAnalogSensor();
     auto sensor = new SimpleSensorDecorator(inner);
-    sensor->addFilters({
-      new ::SenseShift::Input::Filter::MultiplyFilter(2)
-    });
+    sensor->addFilters({ new ::SenseShift::Input::Filter::MultiplyFilter(2) });
 
     TEST_ASSERT_EQUAL_INT(0, inner->setupCounter);
     sensor->init();

From 54212c08806bef11cbd6ef340e24fd31520b440d Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Sun, 11 Feb 2024 00:16:39 +0400
Subject: [PATCH 64/82] fix(OpenGloves): incorrect gesture alpha char

---
 lib/opengloves/opengloves/opengloves.hpp     | 6 +++---
 test/test_opengloves_alpha_encoding/main.cpp | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/lib/opengloves/opengloves/opengloves.hpp b/lib/opengloves/opengloves/opengloves.hpp
index e961f908..25448169 100644
--- a/lib/opengloves/opengloves/opengloves.hpp
+++ b/lib/opengloves/opengloves/opengloves.hpp
@@ -371,12 +371,12 @@ namespace og {
         /// Alpha keys for analog buttons.
         /// <b>MUST</b> be in the same order as the `InputPeripheralData` struct.
         inline static constexpr const std::array<unsigned char, 2> ANALOG_BUTTON_ALPHA_KEY = { {
-          'P', // Trigger
-          'I', // Grab
+          'I', // Trigger
+          'L', // Grab
         } };
 
         [[nodiscard]] auto encode_input(const InputData& input, char* buffer, size_t length) const -> size_t override;
 
         [[nodiscard]] auto parse_output(const char* data, size_t length) const -> Output override;
     };
-} // namespace og
\ No newline at end of file
+} // namespace og
diff --git a/test/test_opengloves_alpha_encoding/main.cpp b/test/test_opengloves_alpha_encoding/main.cpp
index 4786fa0a..40cb2638 100644
--- a/test/test_opengloves_alpha_encoding/main.cpp
+++ b/test/test_opengloves_alpha_encoding/main.cpp
@@ -134,7 +134,7 @@ void test_encode_input_peripherals(void)
             .pinch = { true },
             .grab = { true },
           },
-          "A0B0C0D0E0MI\n"
+          "A0B0C0D0E0ML\n"
         },
         {
           InputPeripheralData({

From 637f5617c3adcf3f77fed2af1c995e1638137b6e Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Sun, 11 Feb 2024 13:20:48 +0400
Subject: [PATCH 65/82] test(OpenGloves): update Wokwi test

---
 .wokwi/lucidgloves-prototype3+serial/test.yaml | 10 +++++-----
 .wokwi/lucidgloves-prototype4+serial/test.yaml |  8 ++++----
 2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/.wokwi/lucidgloves-prototype3+serial/test.yaml b/.wokwi/lucidgloves-prototype3+serial/test.yaml
index 95f55e39..7dc4c210 100644
--- a/.wokwi/lucidgloves-prototype3+serial/test.yaml
+++ b/.wokwi/lucidgloves-prototype3+serial/test.yaml
@@ -45,32 +45,32 @@ steps:
           part-id: pot-thumb
           control: position
           value: 1
-    - wait-serial: "A4095B4095C0D0E0F2047G2047IM" # M is for Pinch gesture
+    - wait-serial: "A4095B4095C0D0E0F2047G2047MI" # M is for Pinch gesture
 
     # Curl Middle finger
     - set-control:
           part-id: pot-middle
           control: position
           value: 1
-    - wait-serial: "A4095B4095C4095D0E0F2047G2047IM"
+    - wait-serial: "A4095B4095C4095D0E0F2047G2047MI"
 
     # Curl Ring finger
     - set-control:
           part-id: pot-ring
           control: position
           value: 1
-    - wait-serial: "A4095B4095C4095D4095E0F2047G2047IM"
+    - wait-serial: "A4095B4095C4095D4095E0F2047G2047MI"
 
     # Curl Pinky finger
     - set-control:
           part-id: pot-pinky
           control: position
           value: 1
-    - wait-serial: "A4095B4095C4095D4095E4095F2047G2047ILM" # L is for the Grab gesture
+    - wait-serial: "A4095B4095C4095D4095E4095F2047G2047MIL" # L is for the Grab gesture
 
     # Partially Release Thumb finger
     - set-control:
           part-id: pot-thumb
           control: position
           value: 0.25
-    - wait-serial: 'A1024B4095C4095D4095E4095F2047G2047IL'
+    - wait-serial: "A1024B4095C4095D4095E4095F2047G2047IL"
diff --git a/.wokwi/lucidgloves-prototype4+serial/test.yaml b/.wokwi/lucidgloves-prototype4+serial/test.yaml
index f89d70f4..cfd56eb9 100644
--- a/.wokwi/lucidgloves-prototype4+serial/test.yaml
+++ b/.wokwi/lucidgloves-prototype4+serial/test.yaml
@@ -45,28 +45,28 @@ steps:
           part-id: pot-thumb
           control: position
           value: 1
-    - wait-serial: "A4095B4095C0D0E0F2047G2047IM" # M is for Pinch gesture
+    - wait-serial: "A4095B4095C0D0E0F2047G2047MI" # M is for Pinch gesture
 
     # Curl Middle finger
     - set-control:
           part-id: pot-middle
           control: position
           value: 1
-    - wait-serial: "A4095B4095C4095D0E0F2047G2047IM"
+    - wait-serial: "A4095B4095C4095D0E0F2047G2047MI"
 
     # Curl Ring finger
     - set-control:
           part-id: pot-ring
           control: position
           value: 1
-    - wait-serial: "A4095B4095C4095D4095E0F2047G2047IM"
+    - wait-serial: "A4095B4095C4095D4095E0F2047G2047MI"
 
     # Curl Pinky finger
     - set-control:
           part-id: pot-pinky
           control: position
           value: 1
-    - wait-serial: "A4095B4095C4095D4095E4095F2047G2047ILM" # L is for the Grab gesture
+    - wait-serial: "A4095B4095C4095D4095E4095F2047G2047MIL" # L is for the Grab gesture
 
     # Partially Release Thumb finger
     - set-control:

From a2fd378c335c4cf3217dd14e002ce01fab1d57ca Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Sun, 11 Feb 2024 19:45:21 +0400
Subject: [PATCH 66/82] perf(OpenGloves): fix slow tick rate

---
 .../mode_configs/opengloves/opengloves.cpp    |  9 +-
 lib/freertos/senseshift/freertos/task.hpp     | 14 ++-
 lib/opengloves/opengloves/opengloves.cpp      | 86 +++++++++--------
 .../opengloves/transport/stream.hpp           |  4 +
 .../senseshift/opengloves/opengloves_task.hpp | 96 ++++++++++++++-----
 5 files changed, 140 insertions(+), 69 deletions(-)

diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp
index 1a603125..1491b2e3 100644
--- a/firmware/mode_configs/opengloves/opengloves.cpp
+++ b/firmware/mode_configs/opengloves/opengloves.cpp
@@ -153,14 +153,13 @@ void setupMode()
     auto* pinch = new BUTTON_CLASS(PIN_BUTTON_PINCH, BUTTON_PINCH_INVERT);
 #endif
 
-    Serial.begin(115200);
-    OpenGlovesTrackingComponent::Config tracking_config(2000, true);
-    auto* opengloves_tracking =
-      new OpenGlovesTrackingComponent(tracking_config, input_sensors, new StreamTransport(Serial));
+    auto* communication = AutoConfig::setupTransport();
+    OpenGlovesTrackingComponent::Config tracking_config(CALIBRATION_DURATION, CALIBRATION_ALWAYS_CALIBRATE);
+    auto* opengloves_tracking = new OpenGlovesTrackingComponent(tracking_config, input_sensors, communication);
 
     auto* opengloves_tracking_task = new ::SenseShift::FreeRTOS::ComponentUpdateTask<OpenGlovesTrackingComponent>(
       opengloves_tracking,
-      1000 / 60,
+      1000 / UPDATE_RATE,
       {
         .name = "OpenGlovesSensorTask",
         .stackDepth = 8192,
diff --git a/lib/freertos/senseshift/freertos/task.hpp b/lib/freertos/senseshift/freertos/task.hpp
index aab82a70..38ea81a0 100644
--- a/lib/freertos/senseshift/freertos/task.hpp
+++ b/lib/freertos/senseshift/freertos/task.hpp
@@ -133,10 +133,20 @@ namespace SenseShift::FreeRTOS {
       protected:
         [[noreturn]] void run()
         {
+            auto now = millis();
+            auto targetHz = 1000 / this->updateDelay_;
+
             while (true) {
+                now = millis();
+
                 this->component_->tick();
-                delay(this->updateDelay_);
-                // log_i("high watermark %d", uxTaskGetStackHighWaterMark(NULL));
+
+                const auto elapsed = millis() - now;
+
+                log_d("T: %d, Fmax: %dHz, Ft: %dHz", elapsed, 1000 / elapsed, targetHz);
+                if (elapsed < this->updateDelay_) {
+                    delay(this->updateDelay_ - elapsed);
+                }
             }
         }
 
diff --git a/lib/opengloves/opengloves/opengloves.cpp b/lib/opengloves/opengloves/opengloves.cpp
index 924f8914..94e044d2 100644
--- a/lib/opengloves/opengloves/opengloves.cpp
+++ b/lib/opengloves/opengloves/opengloves.cpp
@@ -5,22 +5,43 @@
 #include <cstdio>
 #include <variant>
 
-#ifdef OG_ENCODE_FAST
-template<typename Tp = int, typename Tu = float>
-auto ifloor(Tu x) -> Tp
-{
-    return (Tp) x - (x < (Tp) x);
-}
-
-template<typename Tp = int, typename Tu = float>
-auto iceil(Tu x) -> Tp
-{
-    return (Tp) x + (x > (Tp) x);
-}
-#endif
-
 namespace og {
 
+#ifdef OG_ENCODE_FASTER
+    inline auto ifloor(float x) -> int
+    {
+        union Cast {
+            double d;
+            long l;
+        };
+        volatile Cast c;
+        c.d = d + 6755399441055743.5;
+        return c.l;
+    }
+#elifdef OG_ENCODE_FAST
+    /// Source: https://stackoverflow.com/questions/429632/429812#429812
+    inline int float2int(double d)
+    {
+        union Cast {
+            double d;
+            long l;
+        };
+        volatile Cast c;
+        c.d = d + 6755399441055744;
+        return c.l;
+    }
+
+    inline auto ifloor(float x) -> int
+    {
+        return float2int(x) - (x < float2int(x));
+    }
+#else
+    inline auto ifloor(float x) -> int
+    {
+        return static_cast<int>(std::floor(x));
+    }
+#endif
+
     auto AlphaEncoder::encode_input(const InputData& input, char* buffer, size_t length) const -> size_t
     {
         if (std::holds_alternative<InputInfoData>(input)) {
@@ -54,13 +75,8 @@ namespace og {
                 const auto& finger = curls[i];
                 const auto& finger_curl = finger.curl_total;
 
-                written += snprintf(
-                  buffer + written,
-                  length - written,
-                  "%c%u",
-                  'A' + i,
-                  ifloor<std::uint16_t, float>(finger_curl * MAX_ANALOG_VALUE)
-                );
+                written +=
+                  snprintf(buffer + written, length - written, "%c%u", 'A' + i, ifloor(finger_curl * MAX_ANALOG_VALUE));
             }
 #else
             for (auto i = 0; i < curls.size(); i++) {
@@ -71,18 +87,18 @@ namespace og {
                 written += snprintf(
                   buffer + written,
                   length - written,
-                  "%c%.0f",
+                  "%c%u",
                   finger_alpha_key,
-                  std::floor(finger_curl.curl_total * MAX_ANALOG_VALUE)
+                  ifloor(finger_curl.curl_total * MAX_ANALOG_VALUE)
                 );
 
                 if (finger_splay > 0.0F) {
                     written += snprintf(
                       buffer + written,
                       length - written,
-                      "(%cB)%.0f",
+                      "(%cB)%u",
                       finger_alpha_key,
-                      std::floor(finger_splay * MAX_ANALOG_VALUE)
+                      ifloor(finger_splay * MAX_ANALOG_VALUE)
                     );
                 }
 
@@ -98,29 +114,21 @@ namespace og {
                     written += snprintf(
                       buffer + written,
                       length - written,
-                      "(%cA%c)%.0f",
+                      "(%cA%c)%u",
                       finger_alpha_key,
                       joint_alpha_key,
-                      std::floor(joint * MAX_ANALOG_VALUE)
+                      ifloor(joint * MAX_ANALOG_VALUE)
                     );
                 }
             }
 #endif
             if (peripheral.joystick.x != 0.0F) {
-                written += snprintf(
-                  buffer + written,
-                  length - written,
-                  "F%.0f",
-                  std::floor(peripheral.joystick.x * MAX_ANALOG_VALUE)
-                );
+                written +=
+                  snprintf(buffer + written, length - written, "F%u", ifloor(peripheral.joystick.x * MAX_ANALOG_VALUE));
             }
             if (peripheral.joystick.y != 0.0F) {
-                written += snprintf(
-                  buffer + written,
-                  length - written,
-                  "G%.0f",
-                  std::floor(peripheral.joystick.y * MAX_ANALOG_VALUE)
-                );
+                written +=
+                  snprintf(buffer + written, length - written, "G%u", ifloor(peripheral.joystick.y * MAX_ANALOG_VALUE));
             }
             if (peripheral.joystick.press) {
                 written += snprintf(buffer + written, length - written, "H");
diff --git a/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp b/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp
index d3128165..d233c44a 100644
--- a/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp
+++ b/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp
@@ -74,6 +74,8 @@ namespace SenseShift::OpenGloves {
             return serial->isReady() && serial->hasClient();
         }
 
+        void init() override {}
+
         virtual size_t send(const char* buffer, size_t length) override
         {
             auto written = this->channel->write(buffer, length);
@@ -89,6 +91,8 @@ namespace SenseShift::OpenGloves {
       public:
         BLESerialTransport(BLESerial& channel) : IStreamTransport(&channel){};
 
+        void init() override {}
+
         bool isReady() override
         {
             auto* serial = static_cast<BLESerial*>(this->channel);
diff --git a/lib/opengloves_freertos/senseshift/opengloves/opengloves_task.hpp b/lib/opengloves_freertos/senseshift/opengloves/opengloves_task.hpp
index 2a5a6f93..b2e6a4cf 100644
--- a/lib/opengloves_freertos/senseshift/opengloves/opengloves_task.hpp
+++ b/lib/opengloves_freertos/senseshift/opengloves/opengloves_task.hpp
@@ -15,10 +15,11 @@
 #include "senseshift/utility.hpp"
 #include <opengloves/opengloves.hpp>
 #include <utility>
+#include <vector>
 
-#define SS_START_CALIBRATION_NOT_NULL(ptr) \
-    if ((ptr) != nullptr) {                \
-        (ptr)->startCalibration();         \
+#define SS_ADD_CALIBRATOR_NOT_NULL(PTR, STORAGE) \
+    if ((PTR) != nullptr) {                      \
+        STORAGE.push_back(PTR);                  \
     }
 
 namespace SenseShift::OpenGloves {
@@ -42,6 +43,29 @@ namespace SenseShift::OpenGloves {
           config_(config), input_sensors_(std::move(input_sensors)), communication_(communication)
         {
             this->encoder_ = new og::AlphaEncoder();
+
+            for (auto& finger_curl : this->input_sensors_.curl.fingers) {
+                for (auto& joint_sensor : finger_curl.curl) {
+                    SS_ADD_CALIBRATOR_NOT_NULL(joint_sensor, this->calibrated_inputs_);
+                }
+            }
+
+            for (auto& finger_splay : this->input_sensors_.splay.fingers) {
+                SS_ADD_CALIBRATOR_NOT_NULL(finger_splay, this->calibrated_inputs_);
+            }
+
+            SS_ADD_CALIBRATOR_NOT_NULL(this->input_sensors_.joystick.x, this->calibrated_inputs_);
+            SS_ADD_CALIBRATOR_NOT_NULL(this->input_sensors_.joystick.y, this->calibrated_inputs_);
+            SS_ADD_CALIBRATOR_NOT_NULL(this->input_sensors_.joystick.press, this->calibrated_inputs_);
+
+            for (auto& button : this->input_sensors_.buttons) {
+                SS_ADD_CALIBRATOR_NOT_NULL(button.press, this->calibrated_inputs_);
+            }
+
+            for (auto& analog_button : this->input_sensors_.analog_buttons) {
+                SS_ADD_CALIBRATOR_NOT_NULL(analog_button.press, this->calibrated_inputs_);
+                SS_ADD_CALIBRATOR_NOT_NULL(analog_button.value, this->calibrated_inputs_);
+            }
         }
 
         void init() override
@@ -49,49 +73,75 @@ namespace SenseShift::OpenGloves {
             this->communication_->init();
             this->input_sensors_.init();
 
-            if (this->config_.always_calibrate_) {
+            // If the calibration button is not present, start calibration immediately.
+            if (this->config_.always_calibrate_ || this->input_sensors_.button_calibrate.press == nullptr) {
                 this->startCalibration();
             }
         }
 
         void tick() override
         {
+            // const auto start = millis();
+
+            // auto now = millis();
             this->input_sensors_.tick();
+            // const auto tickTime = millis() - now;
+
+            // now = millis();
             const auto data = this->input_sensors_.collectData();
+            // const auto collectTime = millis() - now;
+
+            if (data.button_calibrate.press) {
+                this->startCalibration();
+            }
+
+            // now = millis();
             const auto length = this->encoder_->encode_input(data, buffer.data(), buffer.size());
+            // const auto encodeTime = millis() - now;
+
+            // now = millis();
             this->communication_->send(buffer.data(), length);
+            // const auto sendTime = millis() - now;
+
+            if (!this->config_.always_calibrate_ && (millis() - this->calibration_start_time_) > this->config_.calibration_duration_ms_) {
+                this->stopCalibration();
+            }
+
+            // log_d(
+            //   "total: %d, tick: %d, collect: %d, encode: %d, send: %d",
+            //   millis() - start,
+            //   tickTime,
+            //   collectTime,
+            //   encodeTime,
+            //   sendTime
+            // );
         }
 
       protected:
         void startCalibration()
         {
-            for (auto& finger_curl : this->input_sensors_.curl.fingers) {
-                for (auto& joint_sensor : finger_curl.curl) {
-                    SS_START_CALIBRATION_NOT_NULL(joint_sensor);
-                }
-            }
-
-            for (auto& finger_splay : this->input_sensors_.splay.fingers) {
-                SS_START_CALIBRATION_NOT_NULL(finger_splay);
-            }
-
-            SS_START_CALIBRATION_NOT_NULL(this->input_sensors_.joystick.x);
-            SS_START_CALIBRATION_NOT_NULL(this->input_sensors_.joystick.y);
-            SS_START_CALIBRATION_NOT_NULL(this->input_sensors_.joystick.press);
-
-            for (auto& button : this->input_sensors_.buttons) {
-                SS_START_CALIBRATION_NOT_NULL(button.press);
+            log_i("Starting calibration");
+            for (auto& calibrated_input : this->calibrated_inputs_) {
+                calibrated_input->reselCalibration();
+                calibrated_input->startCalibration();
             }
+            this->calibration_start_time_ = millis();
+        }
 
-            for (auto& analog_button : this->input_sensors_.analog_buttons) {
-                SS_START_CALIBRATION_NOT_NULL(analog_button.press);
-                SS_START_CALIBRATION_NOT_NULL(analog_button.value);
+        void stopCalibration()
+        {
+            log_i("Stopping calibration");
+            for (auto& calibrated_input : this->calibrated_inputs_) {
+                calibrated_input->stopCalibration();
             }
         }
 
       private:
         std::array<char, 256> buffer;
 
+        unsigned long long calibration_start_time_;
+        std::vector<::SenseShift::Input::Calibration::ICalibrated*> calibrated_inputs_;
+
         Config& config_;
         InputSensors input_sensors_;
         ITransport* communication_;

From 9b493d22e2d889cbc8a24f832b110b603b86e354 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Sun, 11 Feb 2024 22:40:18 +0400
Subject: [PATCH 67/82] feat(OpenGloves): add decoder for FFB

---
 lib/opengloves/opengloves/opengloves.cpp     | 103 ++++++++++++++++++-
 lib/opengloves/opengloves/opengloves.hpp     |  55 ++++++++--
 test/test_opengloves_alpha_encoding/main.cpp |  55 ++++++++++
 3 files changed, 202 insertions(+), 11 deletions(-)

diff --git a/lib/opengloves/opengloves/opengloves.cpp b/lib/opengloves/opengloves/opengloves.cpp
index 94e044d2..b454efd4 100644
--- a/lib/opengloves/opengloves/opengloves.cpp
+++ b/lib/opengloves/opengloves/opengloves.cpp
@@ -1,8 +1,10 @@
 #include "opengloves/opengloves.hpp"
 
+#include <cctype>
 #include <cmath>
 #include <cstdint>
 #include <cstdio>
+#include <string>
 #include <variant>
 
 namespace og {
@@ -161,8 +163,105 @@ namespace og {
         return 0;
     }
 
-    auto AlphaEncoder::parse_output(const char* data, size_t length) const -> Output
+    auto AlphaEncoder::decode_output(const char* data, size_t length) const -> OutputData
     {
-        return {};
+        if (length == 0) {
+            return OutputInvalid{};
+        }
+
+        const auto commands = split_to_map(data, length);
+        if (commands.empty()) {
+            return OutputInvalid{};
+        }
+
+        // We assume all commands are for ffb, if there is any ffb command
+        const auto& thumb_curl = commands.find("A");
+        if (thumb_curl != commands.end()) {
+            OutputForceFeedbackData ffb{};
+
+            ffb.thumb = std::stof(thumb_curl->second) / MAX_ANALOG_VALUE;
+
+            const auto& index_curl = commands.find("B");
+            if (index_curl != commands.end()) {
+                ffb.index = std::stof(index_curl->second) / MAX_ANALOG_VALUE;
+            }
+
+            const auto& middle_curl = commands.find("C");
+            if (middle_curl != commands.end()) {
+                ffb.middle = std::stof(middle_curl->second) / MAX_ANALOG_VALUE;
+            }
+
+            const auto& ring_curl = commands.find("D");
+            if (ring_curl != commands.end()) {
+                ffb.ring = std::stof(ring_curl->second) / MAX_ANALOG_VALUE;
+            }
+
+            const auto& pinky_curl = commands.find("E");
+            if (pinky_curl != commands.end()) {
+                ffb.pinky = std::stof(pinky_curl->second) / MAX_ANALOG_VALUE;
+            }
+
+            return ffb;
+        }
+
+        // const auto& haptics_frequency = commands.find("F");
+        // if (haptics_frequency != commands.end()) {
+        //     OutputHaptics haptics{};
+        //     return haptics;
+        // }
+
+        return OutputInvalid{};
+    }
+
+    /// Splits the input data into a map of commands and their respective values.
+    ///
+    /// Example: `A100(AB)200B300(BB)400C500\n` -> `{"A": "100", "(AB)": "200", "B": "300", "(BB)": "400", "C": "500"}`
+    auto AlphaEncoder::split_to_map(const char* data, size_t length) const -> std::map<std::string, std::string>
+    {
+        std::map<std::string, std::string> result{};
+
+        // Start at the beginning of the data
+        size_t command_start = 0;
+        for (size_t i = 0; i < length; i++) {
+            const auto& current_char = data[i];
+
+            // Start a new command if the character is non-numeric or an opening parenthesis
+            // and previous character is a numeric character
+            const bool is_command_start = ((isdigit(current_char)) == 0) || current_char == '(';
+            const bool prev_is_digit = isdigit(data[i - 1]) != 0;
+            if (is_command_start && i > 0 && prev_is_digit) {
+                split_command(data, command_start, i, result);
+                command_start = i;
+            }
+        }
+
+        // Add the last command
+        split_command(data, command_start, length, result);
+
+        return result;
+    }
+
+    void AlphaEncoder::split_command(
+      const char* data, size_t start, size_t length, std::map<std::string, std::string>& commands
+    ) const
+    {
+        const std::string current_command = std::string(data + start, length - start);
+
+        if (current_command.empty()) {
+            return;
+        }
+
+        const size_t split_index = current_command.find_first_of("0123456789");
+
+        // If there is no numeric value, the command is empty (likely a binary command)
+        if (split_index == std::string::npos) {
+            commands[current_command] = "";
+            return;
+        }
+
+        const std::string command = current_command.substr(0, split_index);
+        const std::string value = current_command.substr(split_index, current_command.length() - split_index);
+
+        commands[command] = value;
     }
 } // namespace og
\ No newline at end of file
diff --git a/lib/opengloves/opengloves/opengloves.hpp b/lib/opengloves/opengloves/opengloves.hpp
index 25448169..5bd4266f 100644
--- a/lib/opengloves/opengloves/opengloves.hpp
+++ b/lib/opengloves/opengloves/opengloves.hpp
@@ -51,6 +51,12 @@ namespace og {
                 Tf curl_joint3;
             };
         };
+
+        auto operator==(const InputFingerCurl& other) const -> bool
+        {
+            return this->curl_joint0 == other.curl_joint0 && this->curl_joint1 == other.curl_joint1
+                   && this->curl_joint2 == other.curl_joint2 && this->curl_joint3 == other.curl_joint3;
+        }
     };
     using InputFingerCurlData = InputFingerCurl<float>;
 
@@ -68,6 +74,12 @@ namespace og {
                 Tp little;
             };
         };
+
+        auto operator==(const InputFinger& other) const -> bool
+        {
+            return this->thumb == other.thumb && this->index == other.index && this->middle == other.middle
+                   && this->ring == other.ring && this->pinky == other.pinky;
+        }
     };
     using InputFingerData = InputFinger<float>;
 
@@ -131,7 +143,27 @@ namespace og {
 
     using InputData = std::variant<InputInfoData, InputPeripheralData>;
 
-    class Output {};
+    template<typename Tf = float, typename Tb = bool>
+    using OutputForceFeedback = InputFinger<Tf>;
+    using OutputForceFeedbackData = OutputForceFeedback<float, bool>;
+
+    template<typename Tf = float, typename Tb = bool>
+    struct OutputHaptics {
+        Tf frequency;
+        Tf duration;
+        Tf amplitude;
+
+        auto operator==(const OutputHaptics& other) const -> bool
+        {
+            return frequency == other.frequency && duration == other.duration && amplitude == other.amplitude;
+        }
+    };
+    using OutputHapticsData = OutputHaptics<float, bool>;
+
+    class OutputInvalid {
+        auto operator==(const OutputInvalid& /*unused*/) const -> bool { return true; }
+    };
+    using OutputData = std::variant<OutputInvalid, OutputForceFeedbackData, OutputHapticsData>;
 
     class IEncoder {
       public:
@@ -149,22 +181,22 @@ namespace og {
             return buffer;
         }
 
-        [[nodiscard]] virtual auto parse_output(const char* data, size_t length) const -> Output = 0;
+        [[nodiscard]] virtual auto decode_output(const char* data, size_t length) const -> OutputData = 0;
 
-        [[nodiscard]] inline auto parse_output(const std::vector<char>& data) const -> Output
+        [[nodiscard]] inline auto decode_output(const std::vector<char>& data) const -> OutputData
         {
-            return this->parse_output(data.data(), data.size());
+            return this->decode_output(data.data(), data.size());
         }
 
-        [[nodiscard]] inline auto parse_output(const std::string& data) const -> Output
+        [[nodiscard]] inline auto decode_output(const std::string& data) const -> OutputData
         {
-            return this->parse_output(data.data(), data.length());
+            return this->decode_output(data.data(), data.length());
         }
 
 #ifdef ARDUINO
-        [[nodiscard]] inline auto parse_output(const String& data) const -> Output
+        [[nodiscard]] inline auto decode_output(const String& data) const -> OutputData
         {
-            return this->parse_output(data.c_str(), data.length());
+            return this->decode_output(data.c_str(), data.length());
         }
 #endif // ARDUINO
     };
@@ -377,6 +409,11 @@ namespace og {
 
         [[nodiscard]] auto encode_input(const InputData& input, char* buffer, size_t length) const -> size_t override;
 
-        [[nodiscard]] auto parse_output(const char* data, size_t length) const -> Output override;
+        [[nodiscard]] auto decode_output(const char* data, size_t length) const -> OutputData override;
+
+      protected:
+        [[nodiscard]] auto split_to_map(const char* data, size_t length) const -> std::map<std::string, std::string>;
+        void split_command(const char* data, size_t start, size_t length, std::map<std::string, std::string>& commands)
+          const;
     };
 } // namespace og
diff --git a/test/test_opengloves_alpha_encoding/main.cpp b/test/test_opengloves_alpha_encoding/main.cpp
index 40cb2638..9467b45c 100644
--- a/test/test_opengloves_alpha_encoding/main.cpp
+++ b/test/test_opengloves_alpha_encoding/main.cpp
@@ -173,11 +173,66 @@ void test_encode_input_peripherals(void)
     }
 }
 
+void test_decode_output_ffb(void)
+{
+    const IEncoder* encoder = new AlphaEncoder();
+
+    std::map<std::string, OutputForceFeedbackData> cases = {
+        {
+          "A0B0C0D0E0\n",
+          OutputForceFeedbackData{
+            .thumb = 0.0f,
+            .index = 0.0f,
+            .middle = 0.0f,
+            .ring = 0.0f,
+            .pinky = 0.0f,
+          },
+        },
+        {
+          "A0\n",
+          OutputForceFeedbackData{
+            .thumb = 0.0f,
+            .index = 0.0f,
+            .middle = 0.0f,
+            .ring = 0.0f,
+            .pinky = 0.0f,
+          },
+        },
+        {
+          "A819B1638C2457D3276E4095\n",
+          OutputForceFeedbackData{
+            .thumb = 0.2f,
+            .index = 0.4f,
+            .middle = 0.6f,
+            .ring = 0.8f,
+            .pinky = 1.0f,
+          },
+        },
+        {
+          "A4095B4095C4095D4095E4095\n",
+          OutputForceFeedbackData{
+            .thumb = 1.0f,
+            .index = 1.0f,
+            .middle = 1.0f,
+            .ring = 1.0f,
+            .pinky = 1.0f,
+          },
+        },
+    };
+
+    for (const auto& [data, expected] : cases) {
+        const auto decoded = encoder->decode_output(data.c_str(), data.size());
+        TEST_ASSERT_TRUE(std::holds_alternative<OutputForceFeedbackData>(decoded));
+        TEST_ASSERT_TRUE(std::get<OutputForceFeedbackData>(decoded) == expected);
+    }
+}
+
 int process(void)
 {
     UNITY_BEGIN();
 
     RUN_TEST(test_encode_input_peripherals);
+    RUN_TEST(test_decode_output_ffb);
 
     return UNITY_END();
 }

From 529a0ccc413306b56095b8afea2d3f8598c45f6f Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Sun, 18 Feb 2024 13:42:34 +0400
Subject: [PATCH 68/82] feat(OpenGloves): bring back FFB

---
 .github/workflows/release.yml                 |   4 +-
 .../mode_configs/opengloves/opengloves.cpp    |  53 +++-
 ini/opengloves-lucidgloves.ini                | 114 ++++----
 ini/opengloves.ini                            | 118 ++++-----
 lib/arduino/library.json                      |  21 +-
 .../senseshift/arduino/output/servo.hpp       |  31 +++
 .../senseshift/freertos/input/sensor.hpp      |  10 -
 .../senseshift/body/hands/hands_interface.hpp |  36 ++-
 lib/opengloves/opengloves/opengloves.cpp      |  19 +-
 lib/opengloves/opengloves/opengloves.hpp      |   5 +-
 .../senseshift/opengloves/autoconfig.hpp      |  17 +-
 .../senseshift/opengloves/constants.hpp       |   6 +-
 .../senseshift/opengloves/opengloves.hpp      |  79 +++++-
 .../opengloves/transport/stream.hpp           |  36 +--
 .../senseshift/opengloves/opengloves_task.hpp | 246 +++++-------------
 platformio.ini                                |  48 ++--
 16 files changed, 441 insertions(+), 402 deletions(-)
 create mode 100644 lib/arduino/senseshift/arduino/output/servo.hpp
 delete mode 100644 lib/freertos/senseshift/freertos/input/sensor.hpp

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 6eb7910a..d2eecde2 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -132,8 +132,8 @@ jobs:
           - indexer-cs
           - indexer-csf
         comm_flag:
-          - OPENGLOVES_COMMUNCATION=OPENGLOVES_COMM_SERIAL
-          - OPENGLOVES_COMMUNCATION=OPENGLOVES_COMM_BTSERIAL
+          - OPENGLOVES_COMMUNICATION=OPENGLOVES_COMM_SERIAL
+          - OPENGLOVES_COMMUNICATION=OPENGLOVES_COMM_BTSERIAL
     steps:
       - uses: actions/checkout@v3
         with:
diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp
index 1491b2e3..0720afe7 100644
--- a/firmware/mode_configs/opengloves/opengloves.cpp
+++ b/firmware/mode_configs/opengloves/opengloves.cpp
@@ -2,6 +2,7 @@
 #include "senseshift/body/hands/input/total_curl.hpp"
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/input/sensor/digital.hpp>
+#include <senseshift/arduino/output/servo.hpp>
 #include <senseshift/body/hands/input/gesture.hpp>
 #include <senseshift/input/calibration.hpp>
 #include <senseshift/input/filter.hpp>
@@ -13,11 +14,16 @@
 
 using namespace ::SenseShift::Input;
 using namespace ::SenseShift::Arduino::Input;
+using namespace ::SenseShift::Arduino::Output;
 using namespace ::SenseShift::Body::Hands::Input;
 using namespace ::SenseShift::OpenGloves;
 
 InputSensors input_sensors;
 
+#ifdef FFB_ENABLED
+OutputWriters output_writers;
+#endif
+
 void setupMode()
 {
 #if FINGER_THUMB_SPLAY
@@ -154,19 +160,62 @@ void setupMode()
 #endif
 
     auto* communication = AutoConfig::setupTransport();
+    auto* encoding = new og::AlphaEncoder();
     OpenGlovesTrackingComponent::Config tracking_config(CALIBRATION_DURATION, CALIBRATION_ALWAYS_CALIBRATE);
-    auto* opengloves_tracking = new OpenGlovesTrackingComponent(tracking_config, input_sensors, communication);
+    auto* opengloves_tracking =
+      new OpenGlovesTrackingComponent(tracking_config, input_sensors, communication, encoding);
 
     auto* opengloves_tracking_task = new ::SenseShift::FreeRTOS::ComponentUpdateTask<OpenGlovesTrackingComponent>(
       opengloves_tracking,
       1000 / UPDATE_RATE,
       {
-        .name = "OpenGlovesSensorTask",
+        .name = "OG_TRACKING",
         .stackDepth = 8192,
         .priority = 1,
       }
     );
     opengloves_tracking_task->begin();
+
+#if FFB_ENABLED
+
+#if FFB_THUMB_ENABLED
+    auto* thumb_ffb_output = new ServoOutput(PIN_FFB_THUMB);
+    output_writers.ffb.thumb = thumb_ffb_output;
+#endif
+
+#if FFB_INDEX_ENABLED
+    auto* index_ffb_output = new ServoOutput(PIN_FFB_INDEX);
+    output_writers.ffb.index = index_ffb_output;
+#endif
+
+#if FFB_MIDDLE_ENABLED
+    auto* middle_ffb_output = new ServoOutput(PIN_FFB_MIDDLE);
+    output_writers.ffb.middle = middle_ffb_output;
+#endif
+
+#if FFB_RING_ENABLED
+    auto* ring_ffb_output = new ServoOutput(PIN_FFB_RING);
+    output_writers.ffb.ring = ring_ffb_output;
+#endif
+
+#if FFB_PINKY_ENABLED
+    auto* pinky_ffb_output = new ServoOutput(PIN_FFB_PINKY);
+    output_writers.ffb.pinky = pinky_ffb_output;
+#endif
+
+    auto* og_ffb = new OpenGlovesForceFeedbackComponent(output_writers, communication, encoding);
+
+    auto* og_ffb_task = new ::SenseShift::FreeRTOS::ComponentUpdateTask<OpenGlovesForceFeedbackComponent>(
+      og_ffb,
+      1000 / UPDATE_RATE,
+      {
+        .name = "OG_FFB",
+        .stackDepth = 8192,
+        .priority = 1,
+      }
+    );
+    og_ffb_task->begin();
+#endif
 }
 
 void loopMode()
diff --git a/ini/opengloves-lucidgloves.ini b/ini/opengloves-lucidgloves.ini
index f150cfc4..1c50b00a 100644
--- a/ini/opengloves-lucidgloves.ini
+++ b/ini/opengloves-lucidgloves.ini
@@ -12,24 +12,26 @@ monitor_speed = ${opengloves.monitor_speed}
 
 build_flags = ${opengloves.build_flags} -D OG_ENCODE_FAST
 
-              ; Pins configuration
-              ; Comment out to disable
-              -D PIN_FINGER_THUMB=32
-              -D PIN_FINGER_INDEX=35
-              -D PIN_FINGER_MIDDLE=34
-              -D PIN_FINGER_RING=39
-              -D PIN_FINGER_PINKY=36
+			  ; Pins configuration
+			  ; Comment out to disable
+			  -D PIN_FINGER_THUMB=32
+			  -D PIN_FINGER_INDEX=35
+			  -D PIN_FINGER_MIDDLE=34
+			  -D PIN_FINGER_RING=39
+			  -D PIN_FINGER_PINKY=36
 
-              -D PIN_JOYSTICK_X=33
-              -D PIN_JOYSTICK_Y=25
-              -D PIN_BUTTON_JOYSTICK=26
+			  -D PIN_JOYSTICK_X=33
+			  -D PIN_JOYSTICK_Y=25
+			  -D PIN_BUTTON_JOYSTICK=26
 
-              -D PIN_BUTTON_A=27
-              -D PIN_BUTTON_B=14
+			  -D PIN_BUTTON_A=27
+			  -D PIN_BUTTON_B=14
+
+			  -D CALIBRATION_ALWAYS_CALIBRATE=true
 
 build_unflags = ${opengloves.build_unflags}
 build_src_filter = ${opengloves.build_src_filter}
-                   +<mode_configs/opengloves/opengloves.cpp>
+				   +<mode_configs/opengloves/opengloves.cpp>
 lib_deps = ${opengloves.lib_deps}
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -45,29 +47,29 @@ upload_speed = ${opengloves.upload_speed}
 monitor_speed = ${opengloves.monitor_speed}
 
 build_flags = ${opengloves.build_flags} -D OG_ENCODE_FAST
-              ; Pins configuration
-              ; Comment out to disable
-              -D PIN_FINGER_THUMB=32
-              -D PIN_FINGER_INDEX=35
-              -D PIN_FINGER_MIDDLE=34
-              -D PIN_FINGER_RING=39
-              -D PIN_FINGER_PINKY=36
-
-              -D PIN_JOYSTICK_X=33
-              -D PIN_JOYSTICK_Y=25
-              -D PIN_BUTTON_JOYSTICK=26
-
-              -D PIN_BUTTON_A=27
-              -D PIN_BUTTON_B=14
-              ; -D PIN_BUTTON_MENU=27
-              -D PIN_BUTTON_CALIBRATE=12
+			  ; Pins configuration
+			  ; Comment out to disable
+			  -D PIN_FINGER_THUMB=32
+			  -D PIN_FINGER_INDEX=35
+			  -D PIN_FINGER_MIDDLE=34
+			  -D PIN_FINGER_RING=39
+			  -D PIN_FINGER_PINKY=36
+
+			  -D PIN_JOYSTICK_X=33
+			  -D PIN_JOYSTICK_Y=25
+			  -D PIN_BUTTON_JOYSTICK=26
+
+			  -D PIN_BUTTON_A=27
+			  -D PIN_BUTTON_B=14
+			  ; -D PIN_BUTTON_MENU=27
+			  -D PIN_BUTTON_CALIBRATE=12
 ; -D PIN_BUTTON_TRIGGER=12 ; unused if GESTURE_TRIGGER is true
 ; -D PIN_BUTTON_GRAB=13 ; unused if GESTURE_GRAB is true
 ; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
 
 build_unflags = ${opengloves.build_unflags}
 build_src_filter = ${opengloves.build_src_filter}
-                   +<mode_configs/opengloves/opengloves.cpp>
+				   +<mode_configs/opengloves/opengloves.cpp>
 lib_deps = ${opengloves.lib_deps}
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -83,33 +85,33 @@ upload_speed = ${opengloves.upload_speed}
 monitor_speed = ${opengloves.monitor_speed}
 
 build_flags = ${opengloves.build_flags}
-              ; Pins configuration
-              ; Comment out to disable
-              -D PIN_FINGER_THUMB=32
-              -D PIN_FINGER_INDEX=35
-              -D PIN_FINGER_MIDDLE=34
-              -D PIN_FINGER_RING=39
-              -D PIN_FINGER_PINKY=36
-
-              -D PIN_JOYSTICK_X=33
-              -D PIN_JOYSTICK_Y=25
-              -D PIN_BUTTON_JOYSTICK=26
-
-              -D PIN_BUTTON_A=27
-              -D PIN_BUTTON_B=14
-              ; -D PIN_BUTTON_MENU=27
-              -D PIN_BUTTON_CALIBRATE=12
-              ; -D PIN_BUTTON_TRIGGER=12 ; unused if GESTURE_TRIGGER is true
-              ; -D PIN_BUTTON_GRAB=13 ; unused if GESTURE_GRAB is true
-              ; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
-
-              -D PIN_FFB_THUMB=17
-              -D PIN_FFB_INDEX=21
-              -D PIN_FFB_MIDDLE=19
-              -D PIN_FFB_RING=18
-              -D PIN_FFB_PINKY=5
+			  ; Pins configuration
+			  ; Comment out to disable
+			  -D PIN_FINGER_THUMB=32
+			  -D PIN_FINGER_INDEX=35
+			  -D PIN_FINGER_MIDDLE=34
+			  -D PIN_FINGER_RING=39
+			  -D PIN_FINGER_PINKY=36
+
+			  -D PIN_JOYSTICK_X=33
+			  -D PIN_JOYSTICK_Y=25
+			  -D PIN_BUTTON_JOYSTICK=26
+
+			  -D PIN_BUTTON_A=27
+			  -D PIN_BUTTON_B=14
+			  ; -D PIN_BUTTON_MENU=27
+			  -D PIN_BUTTON_CALIBRATE=12
+			  ; -D PIN_BUTTON_TRIGGER=12 ; unused if GESTURE_TRIGGER is true
+			  ; -D PIN_BUTTON_GRAB=13 ; unused if GESTURE_GRAB is true
+			  ; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
+
+			  -D PIN_FFB_THUMB=17
+			  -D PIN_FFB_INDEX=21
+			  -D PIN_FFB_MIDDLE=19
+			  -D PIN_FFB_RING=18
+			  -D PIN_FFB_PINKY=5
 
 build_unflags = ${opengloves.build_unflags}
 build_src_filter = ${opengloves.build_src_filter}
-                   +<mode_configs/opengloves/opengloves.cpp>
+				   +<mode_configs/opengloves/opengloves.cpp>
 lib_deps = ${opengloves.lib_deps}
diff --git a/ini/opengloves.ini b/ini/opengloves.ini
index b7bb70cc..12819792 100644
--- a/ini/opengloves.ini
+++ b/ini/opengloves.ini
@@ -1,60 +1,60 @@
 [opengloves]
-platform         	= platformio/espressif32@^6.1.0
-platform_packages	=
-    platformio/framework-arduinoespressif32@^3.20007.0
-framework 	     	= arduino
-board            	= esp32doit-devkit-v1
-upload_speed     	= 921600
-monitor_speed    	= 115200
-
-build_flags 		= ${common.build_flags}
-	-D OPENGLOVES
-
-	; Communication
-	; -D OPENGLOVES_COMMUNCATION=OPENGLOVES_COMM_SERIAL ; OPENGLOVES_COMM_SERIAL, OPENGLOVES_COMM_BTSERIAL, OPENGLOVES_COMM_BLESERIAL
-	; Serial
-	-D SERIAL_BAUDRATE=115200
-	-D SERIAL_PORT=Serial ; Serial, Serial1, Serial2, Serial3
-	; BTSerial
-	'-D BTSERIAL_PREFIX="SenseShift_OG"'
-	; '-D BTSERIAL_NAME="SenseShift_OG_Left"'
-
-	; Sensors
-	-D FINGER_THUMB_INVERT=false
-	-D FINGER_INDEX_INVERT=false
-	-D FINGER_MIDDLE_INVERT=false
-	-D FINGER_RING_INVERT=false
-	-D FINGER_PINKY_INVERT=false
-
-	-D FINGER_THUMB_SPLAY_INVERT=false
-	-D FINGER_INDEX_SPLAY_INVERT=false
-	-D FINGER_MIDDLE_SPLAY_INVERT=false
-	-D FINGER_RING_SPLAY_INVERT=false
-	-D FINGER_PINKY_SPLAY_INVERT=false
-
-	-D JOYSTICK_X_INVERT=false
-	-D JOYSTICK_Y_INVERT=false
-	-D JOYSTICK_DEADZONE=0.1
-	-D BUTTON_JOYSTICK_INVERT=false
-
-	-D BUTTON_A_INVERT=false
-	-D BUTTON_B_INVERT=false
-	-D BUTTON_MENU_INVERT=false
-	-D BUTTON_CALIBRATE_INVERT=false
-	-D BUTTON_TRIGGER_INVERT=false
-	-D BUTTON_GRAB_INVERT=false
-	-D BUTTON_PINCH_INVERT=false
-
-	-D GESTURE_TRIGGER_ENABLED=true
-	-D GESTURE_GRAB_ENABLED=true
-	-D GESTURE_PINCH_ENABLED=true
-
-	; Calibration
-	-D CALIBRATION_ALWAYS_CALIBRATE=true
-	-D CALIBRATION_DURATION=2000 ; in ms
-
-	-D UPDATE_RATE=90 ; sensors update rate in Hz
-
-build_unflags   	= ${common.build_unflags}
-build_src_filter	= ${common.build_src_filter}
-lib_deps    		= ${common.lib_deps}
+platform = platformio/espressif32@^6.1.0
+platform_packages =
+	platformio/framework-arduinoespressif32@^3.20014.231204
+framework = arduino
+board = esp32doit-devkit-v1
+upload_speed = 921600
+monitor_speed = 115200
+
+build_flags = ${common.build_flags}
+			  -D OPENGLOVES
+
+			  ; Communication
+			  ; -D OPENGLOVES_COMMUNICATION=OPENGLOVES_COMM_BLESERIAL ; OPENGLOVES_COMM_SERIAL, OPENGLOVES_COMM_BTSERIAL, OPENGLOVES_COMM_BLESERIAL
+			  ; Serial
+			  -D SERIAL_BAUDRATE=115200
+			  -D SERIAL_PORT=Serial ; Serial, Serial1, Serial2, Serial3
+			  ; BTSerial
+			  '-D BTSERIAL_PREFIX="SenseShift_OG"'
+			  ; '-D BTSERIAL_NAME="SenseShift_OG_Left"'
+
+			  ; Sensors
+			  -D FINGER_THUMB_INVERT=false
+			  -D FINGER_INDEX_INVERT=false
+			  -D FINGER_MIDDLE_INVERT=false
+			  -D FINGER_RING_INVERT=false
+			  -D FINGER_PINKY_INVERT=false
+
+			  -D FINGER_THUMB_SPLAY_INVERT=false
+			  -D FINGER_INDEX_SPLAY_INVERT=false
+			  -D FINGER_MIDDLE_SPLAY_INVERT=false
+			  -D FINGER_RING_SPLAY_INVERT=false
+			  -D FINGER_PINKY_SPLAY_INVERT=false
+
+			  -D JOYSTICK_X_INVERT=false
+			  -D JOYSTICK_Y_INVERT=false
+			  -D JOYSTICK_DEADZONE=0.1
+			  -D BUTTON_JOYSTICK_INVERT=false
+
+			  -D BUTTON_A_INVERT=false
+			  -D BUTTON_B_INVERT=false
+			  -D BUTTON_MENU_INVERT=false
+			  -D BUTTON_CALIBRATE_INVERT=false
+			  -D BUTTON_TRIGGER_INVERT=false
+			  -D BUTTON_GRAB_INVERT=false
+			  -D BUTTON_PINCH_INVERT=false
+
+			  -D GESTURE_TRIGGER_ENABLED=true
+			  -D GESTURE_GRAB_ENABLED=true
+			  -D GESTURE_PINCH_ENABLED=true
+
+			  ; Calibration
+			  -D CALIBRATION_ALWAYS_CALIBRATE=false
+			  -D CALIBRATION_DURATION=2000 ; in ms
+
+			  -D UPDATE_RATE=90 ; sensors update rate in Hz
+
+build_unflags = ${common.build_unflags}
+build_src_filter = ${common.build_src_filter}
+lib_deps = ${common.lib_deps}
diff --git a/lib/arduino/library.json b/lib/arduino/library.json
index 8434426d..b906817b 100644
--- a/lib/arduino/library.json
+++ b/lib/arduino/library.json
@@ -1,12 +1,13 @@
 {
-    "$schema": "https://raw.githubusercontent.com/platformio/platformio-core/develop/platformio/assets/schema/library.json",
-    "frameworks": "arduino",
-    "platforms": "*",
-    "dependencies": {
-        "adafruit/Adafruit BusIO": "^1.14.1",
-        "adafruit/Adafruit Unified Sensor": "^1.1.4",
-        "adafruit/Adafruit PWM Servo Driver Library": "^2.4.0",
-        "adafruit/Adafruit INA219": "^1.2.1",
-        "sparkfun/SparkFun MAX1704x Fuel Gauge Arduino Library": "^1.0.4"
-    }
+  "$schema": "https://raw.githubusercontent.com/platformio/platformio-core/develop/platformio/assets/schema/library.json",
+  "frameworks": "arduino",
+  "platforms": "*",
+  "dependencies": {
+    "adafruit/Adafruit BusIO": "^1.14.1",
+    "adafruit/Adafruit Unified Sensor": "^1.1.4",
+    "adafruit/Adafruit PWM Servo Driver Library": "^2.4.0",
+    "adafruit/Adafruit INA219": "^1.2.1",
+    "sparkfun/SparkFun MAX1704x Fuel Gauge Arduino Library": "^1.0.4",
+    "madhephaestus/ESP32Servo": "^1.1.2"
+  }
 }
diff --git a/lib/arduino/senseshift/arduino/output/servo.hpp b/lib/arduino/senseshift/arduino/output/servo.hpp
new file mode 100644
index 00000000..81f238eb
--- /dev/null
+++ b/lib/arduino/senseshift/arduino/output/servo.hpp
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <cstddef>
+#include <cstdint>
+
+#ifdef ESP32
+#include <ESP32Servo.h>
+#else
+#include <Servo.h>
+#endif
+
+#include <senseshift/output/output.hpp>
+
+namespace SenseShift::Arduino::Output {
+    class ServoOutput : public ::SenseShift::Output::IFloatOutput {
+      public:
+        ServoOutput(size_t pin) : pin_(pin){};
+
+        void init() override { this->servo_.attach(this->pin_); }
+
+        void writeState(const ValueType value) override
+        {
+            const auto duty = static_cast<std::uint16_t>(value * 180);
+            this->servo_.write(duty);
+        }
+
+      private:
+        Servo servo_;
+        size_t pin_;
+    };
+} // namespace SenseShift::Arduino::Output
\ No newline at end of file
diff --git a/lib/freertos/senseshift/freertos/input/sensor.hpp b/lib/freertos/senseshift/freertos/input/sensor.hpp
deleted file mode 100644
index 2a31d67d..00000000
--- a/lib/freertos/senseshift/freertos/input/sensor.hpp
+++ /dev/null
@@ -1,10 +0,0 @@
-#pragma once
-
-#include <senseshift/freertos/task.hpp>
-#include <senseshift/input/sensor.hpp>
-
-#include <cstdint>
-
-namespace SenseShift::FreeRTOS::Input {
-
-} // namespace SenseShift::FreeRTOS::Input
diff --git a/lib/hands/senseshift/body/hands/hands_interface.hpp b/lib/hands/senseshift/body/hands/hands_interface.hpp
index a8887906..cee4877e 100644
--- a/lib/hands/senseshift/body/hands/hands_interface.hpp
+++ b/lib/hands/senseshift/body/hands/hands_interface.hpp
@@ -4,23 +4,21 @@
 
 #include <cstdint>
 
-namespace SenseShift::Body {
-    namespace Hands {
-        using HandLateralityIndex = std::uint8_t;
-        enum class HandSide : HandLateralityIndex { Left, Right };
-        using FingerIndex = std::uint8_t;
-        enum class Finger : FingerIndex {
-            Thumb,
-            Index,
-            Middle,
-            Ring,
-            Little,
-        };
+namespace SenseShift::Body::Hands {
+    using HandLateralityIndex = std::uint8_t;
+    enum class HandSide : HandLateralityIndex { Left, Right };
+    using FingerIndex = std::uint8_t;
+    enum class Finger : FingerIndex {
+        Thumb,
+        Index,
+        Middle,
+        Ring,
+        Little,
+    };
 
-        namespace Haptics {
-            /// @brief Helper with position of the haptic device on the fingertip.
-            /// Distal phalanx of the volar surface of the any finger.
-            static constexpr const ::SenseShift::Body::Haptics::Position FINGERTIP_POSITION(127, 16);
-        } // namespace Haptics
-    } // namespace Hands
-} // namespace SenseShift::Body
+    namespace Haptics {
+        /// @brief Helper with position of the haptic device on the fingertip.
+        /// Distal phalanx of the volar surface of the any finger.
+        static constexpr const ::SenseShift::Body::Haptics::Position FINGERTIP_POSITION(127, 16);
+    } // namespace Haptics
+} // namespace SenseShift::Body::Hands
diff --git a/lib/opengloves/opengloves/opengloves.cpp b/lib/opengloves/opengloves/opengloves.cpp
index b454efd4..8f3f0c45 100644
--- a/lib/opengloves/opengloves/opengloves.cpp
+++ b/lib/opengloves/opengloves/opengloves.cpp
@@ -10,7 +10,7 @@
 namespace og {
 
 #ifdef OG_ENCODE_FASTER
-    inline auto ifloor(float x) -> int
+    inline auto ifloor(float d) -> int
     {
         union Cast {
             double d;
@@ -176,27 +176,32 @@ namespace og {
 
         // We assume all commands are for ffb, if there is any ffb command
         const auto& thumb_curl = commands.find("A");
-        if (thumb_curl != commands.end()) {
+        const auto& index_curl = commands.find("B");
+        const auto& middle_curl = commands.find("C");
+        const auto& ring_curl = commands.find("D");
+        const auto& pinky_curl = commands.find("E");
+
+        if (thumb_curl != commands.end() || index_curl != commands.end() || middle_curl != commands.end() ||
+            ring_curl != commands.end() || pinky_curl != commands.end()
+        ) {
             OutputForceFeedbackData ffb{};
 
-            ffb.thumb = std::stof(thumb_curl->second) / MAX_ANALOG_VALUE;
+            if (thumb_curl != commands.end()) {
+                ffb.thumb = std::stof(thumb_curl->second) / MAX_ANALOG_VALUE;
+            }
 
-            const auto& index_curl = commands.find("B");
             if (index_curl != commands.end()) {
                 ffb.index = std::stof(index_curl->second) / MAX_ANALOG_VALUE;
             }
 
-            const auto& middle_curl = commands.find("C");
             if (middle_curl != commands.end()) {
                 ffb.middle = std::stof(middle_curl->second) / MAX_ANALOG_VALUE;
             }
 
-            const auto& ring_curl = commands.find("D");
             if (ring_curl != commands.end()) {
                 ffb.ring = std::stof(ring_curl->second) / MAX_ANALOG_VALUE;
             }
 
-            const auto& pinky_curl = commands.find("E");
             if (pinky_curl != commands.end()) {
                 ffb.pinky = std::stof(pinky_curl->second) / MAX_ANALOG_VALUE;
             }
diff --git a/lib/opengloves/opengloves/opengloves.hpp b/lib/opengloves/opengloves/opengloves.hpp
index 5bd4266f..2d9fd3b4 100644
--- a/lib/opengloves/opengloves/opengloves.hpp
+++ b/lib/opengloves/opengloves/opengloves.hpp
@@ -114,7 +114,10 @@ namespace og {
         InputJoystick<Tf, Tb> joystick;
 
         union {
-            std::array<InputButton<Tb>, 5> buttons;
+            /// Buttons as array.
+            /// <b>MUST</b> be the same length as the struct below
+            std::array<InputButton<Tb>, 5> buttons; // NOLINT(*-magic-numbers) We keep it here for clarity
+
             struct {
                 InputButton<Tb> button_a;
                 InputButton<Tb> button_b;
diff --git a/lib/opengloves/senseshift/opengloves/autoconfig.hpp b/lib/opengloves/senseshift/opengloves/autoconfig.hpp
index 4f114d03..a2e452b0 100644
--- a/lib/opengloves/senseshift/opengloves/autoconfig.hpp
+++ b/lib/opengloves/senseshift/opengloves/autoconfig.hpp
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <senseshift/opengloves/constants.hpp>
 #include <senseshift/opengloves/opengloves.hpp>
 
 #ifdef ARDUINO
@@ -8,8 +9,8 @@
 
 #pragma region Communication
 
-#ifndef OPENGLOVES_COMMUNCATION
-#define OPENGLOVES_COMMUNCATION OPENGLOVES_COMM_SERIAL
+#ifndef OPENGLOVES_COMMUNICATION
+#define OPENGLOVES_COMMUNICATION OPENGLOVES_COMM_SERIAL
 #endif
 
 #ifndef SERIAL_PORT
@@ -182,14 +183,14 @@ namespace SenseShift::OpenGloves::AutoConfig {
     /**
      * Setup the transport for the OpenGloves interface.
      */
-    [[nodiscard]] ITransport* setupTransport(void)
+    [[nodiscard]] auto setupTransport() -> ITransport*
     {
-#if OPENGLOVES_COMMUNCATION == OPENGLOVES_COMM_SERIAL // Serial
+#if OPENGLOVES_COMMUNICATION == OPENGLOVES_COMM_SERIAL // Serial
         auto* pSerial = &SERIAL_PORT;
         pSerial->begin(SERIAL_BAUDRATE);
         return new StreamTransport(pSerial);
-#elif (OPENGLOVES_COMMUNCATION == OPENGLOVES_COMM_BTSERIAL) \
-  || (OPENGLOVES_COMMUNCATION == OPENGLOVES_COMM_BLESERIAL) // Bluetooth Serial
+#elif (OPENGLOVES_COMMUNICATION == OPENGLOVES_COMM_BTSERIAL) \
+  || (OPENGLOVES_COMMUNICATION == OPENGLOVES_COMM_BLESERIAL) // Bluetooth Serial
         std::string name;
 #ifdef BTSERIAL_NAME
         name = BTSERIAL_NAME;
@@ -201,11 +202,11 @@ namespace SenseShift::OpenGloves::AutoConfig {
         log_i("Generated Bluetooth name: %s", name.c_str());
 #endif
 
-#if OPENGLOVES_COMMUNCATION == OPENGLOVES_COMM_BTSERIAL    // Bluetooth Classic
+#if OPENGLOVES_COMMUNICATION == OPENGLOVES_COMM_BTSERIAL    // Bluetooth Classic
         BluetoothSerial* pBtSerial = new BluetoothSerial();
         pBtSerial->begin(name.c_str());
         return new BluetoothSerialTransport(*pBtSerial);
-#elif OPENGLOVES_COMMUNCATION == OPENGLOVES_COMM_BLESERIAL // Bluetooth Low Energy
+#elif OPENGLOVES_COMMUNICATION == OPENGLOVES_COMM_BLESERIAL // Bluetooth Low Energy
         BLESerial* pBleSerial = new BLESerial();
         pBleSerial->begin(name.c_str());
         return new BLESerialTransport(*pBleSerial);
diff --git a/lib/opengloves/senseshift/opengloves/constants.hpp b/lib/opengloves/senseshift/opengloves/constants.hpp
index df163940..b9de70c9 100644
--- a/lib/opengloves/senseshift/opengloves/constants.hpp
+++ b/lib/opengloves/senseshift/opengloves/constants.hpp
@@ -2,6 +2,6 @@
 
 #define OPENGLOVES_FINGERS_TASK_PRIORITY 1
 
-#define OPENGLOVES_COMM_SERIAL 0x1
-#define OPENGLOVES_COMM_BTSERIAL 0x2
-#define OPENGLOVES_COMM_BLESERIAL 0x3
+#define OPENGLOVES_COMM_SERIAL 1
+#define OPENGLOVES_COMM_BTSERIAL 2
+#define OPENGLOVES_COMM_BLESERIAL 3
diff --git a/lib/opengloves/senseshift/opengloves/opengloves.hpp b/lib/opengloves/senseshift/opengloves/opengloves.hpp
index 0b2d6832..cd4cbd0e 100644
--- a/lib/opengloves/senseshift/opengloves/opengloves.hpp
+++ b/lib/opengloves/senseshift/opengloves/opengloves.hpp
@@ -1,13 +1,16 @@
 #pragma once
 
 #include <cstddef>
-#include <cstdint>
-#include <map>
-#include <optional>
-#include <string>
+#include <set>
+#include <variant>
+#include <vector>
 
 #include <opengloves/opengloves.hpp>
 
+#include <senseshift/core/component.hpp>
+#include <senseshift/input/sensor.hpp>
+#include <senseshift/output/output.hpp>
+
 namespace SenseShift::OpenGloves {
     class ITransport : public IInitializable {
       public:
@@ -19,18 +22,27 @@ namespace SenseShift::OpenGloves {
     using FloatSensor = ::SenseShift::Input::FloatSensor;
     using BinarySensor = ::SenseShift::Input::BinarySensor;
 
-    class InputSensors : public og::InputPeripheral<FloatSensor*, BinarySensor*>, public Component {
+    class InputSensors :
+      public og::InputPeripheral<FloatSensor*, BinarySensor*>,
+      public Component,
+      public ::SenseShift::Input::Calibration::ICalibrated {
       public:
         void init() override
         {
             for (auto& finger_curl : this->curl.fingers) {
                 for (auto& joint_sensor : finger_curl.curl) {
-                    SS_INIT_NOT_NULL(joint_sensor);
+                    if (joint_sensor != nullptr) {
+                        joint_sensor->init();
+                        this->calibrated_inputs_.insert(joint_sensor);
+                    }
                 }
             }
 
             for (auto& finger_splay : this->splay.fingers) {
-                SS_INIT_NOT_NULL(finger_splay);
+                if (finger_splay != nullptr) {
+                    finger_splay->init();
+                    this->calibrated_inputs_.insert(finger_splay);
+                }
             }
 
             SS_INIT_NOT_NULL(this->joystick.x);
@@ -124,5 +136,58 @@ namespace SenseShift::OpenGloves {
 
             return data;
         }
+
+        void reselCalibration() override
+        {
+            for (const auto& calibrated_input : this->calibrated_inputs_) {
+                calibrated_input->reselCalibration();
+            }
+        }
+
+        void startCalibration() override
+        {
+            for (const auto& calibrated_input : this->calibrated_inputs_) {
+                calibrated_input->startCalibration();
+            }
+        }
+
+        void stopCalibration() override
+        {
+            for (const auto& calibrated_input : this->calibrated_inputs_) {
+                calibrated_input->stopCalibration();
+            }
+        }
+
+      private:
+        std::set<FloatSensor*> calibrated_inputs_{};
+    };
+
+    using FloatOutput = ::SenseShift::Output::IFloatOutput;
+
+    class OutputWriters : public IInitializable {
+      public:
+        og::OutputForceFeedback<FloatOutput*, void*> ffb;
+
+        void init() override
+        {
+            for (auto& finger : this->ffb.fingers) {
+                if (finger != nullptr) {
+                    finger->init();
+                }
+            }
+        }
+
+        void apply(const og::OutputData& data)
+        {
+            if (std::holds_alternative<og::OutputForceFeedbackData>(data)) {
+                const auto& ffb_data = std::get<og::OutputForceFeedbackData>(data);
+                for (auto i = 0; i < this->ffb.fingers.size(); i++) {
+                    auto* finger = this->ffb.fingers[i];
+                    if (finger != nullptr) {
+                        finger->writeState(ffb_data.fingers[i]);
+                    }
+                }
+            }
+        }
     };
 } // namespace SenseShift::OpenGloves
diff --git a/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp b/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp
index d233c44a..12d413e2 100644
--- a/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp
+++ b/lib/opengloves_arduino/senseshift/opengloves/transport/stream.hpp
@@ -1,5 +1,7 @@
 #pragma once
 
+#include <array>
+
 #include <senseshift/buffer.hpp>
 
 #include <BLESerial.hpp>
@@ -14,12 +16,12 @@ namespace SenseShift::OpenGloves {
     class IStreamTransport : public ITransport {
       protected:
         Stream* channel;
-        char* buffer = new char[256];
+        std::array<char, 256> buffer_{};
 
       public:
         IStreamTransport(Stream* channel) : channel(channel){};
 
-        size_t send(const char* buffer, size_t length) override
+        auto send(const char* buffer, size_t length) -> size_t override
         {
             if (!this->isReady()) {
                 return 0;
@@ -31,17 +33,17 @@ namespace SenseShift::OpenGloves {
             return written;
         }
 
-        virtual bool isReady() = 0;
+        virtual auto isReady() -> bool = 0;
 
-        virtual bool hasData() override
+        auto hasData() -> bool override
         {
             return this->isReady() && this->channel != nullptr && this->channel->available() > 0;
         }
 
-        virtual size_t read(char* buffer, size_t length)
+        auto read(char* buffer, size_t length) -> size_t override
         {
             if (!this->hasData()) {
-                return false;
+                return 0U;
             }
 
             size_t bytesRead = this->channel->readBytesUntil('\n', buffer, length);
@@ -53,12 +55,12 @@ namespace SenseShift::OpenGloves {
 
     class StreamTransport : public IStreamTransport {
       public:
-        StreamTransport(Stream& channel) : IStreamTransport(&channel){};
-        StreamTransport(Stream* channel) : IStreamTransport(channel){};
+        explicit StreamTransport(Stream& channel) : IStreamTransport(&channel){};
+        explicit StreamTransport(Stream* channel) : IStreamTransport(channel){};
 
         void init() override { this->mReady = true; }
 
-        bool isReady() override { return this->channel != nullptr && this->mReady; }
+        auto isReady() -> bool override { return this->channel != nullptr && this->mReady; }
 
       private:
         bool mReady = false;
@@ -66,9 +68,9 @@ namespace SenseShift::OpenGloves {
 
     class BluetoothSerialTransport : public IStreamTransport {
       public:
-        BluetoothSerialTransport(BluetoothSerial& channel) : IStreamTransport(&channel){};
+        explicit BluetoothSerialTransport(BluetoothSerial& channel) : IStreamTransport(&channel){};
 
-        bool isReady() override
+        auto isReady() -> bool override
         {
             auto* serial = static_cast<BluetoothSerial*>(this->channel);
             return serial->isReady() && serial->hasClient();
@@ -76,7 +78,7 @@ namespace SenseShift::OpenGloves {
 
         void init() override {}
 
-        virtual size_t send(const char* buffer, size_t length) override
+        auto send(const char* buffer, size_t length) -> size_t override
         {
             auto written = this->channel->write(buffer, length);
 
@@ -89,11 +91,11 @@ namespace SenseShift::OpenGloves {
 
     class BLESerialTransport : public IStreamTransport {
       public:
-        BLESerialTransport(BLESerial& channel) : IStreamTransport(&channel){};
+        explicit BLESerialTransport(BLESerial& channel) : IStreamTransport(&channel){};
 
         void init() override {}
 
-        bool isReady() override
+        auto isReady() -> bool override
         {
             auto* serial = static_cast<BLESerial*>(this->channel);
             return serial->connected();
@@ -108,7 +110,7 @@ namespace SenseShift::OpenGloves {
         {
             auto* client = static_cast<WiFiClient*>(this->channel);
             if (client != nullptr) {
-                if (client->connected()) {
+                if (client->connected() != 0U) {
                     return;
                 }
             }
@@ -117,14 +119,14 @@ namespace SenseShift::OpenGloves {
             this->channel = new WiFiClient(this->m_server.available());
         }
 
-        bool isReady() override
+        auto isReady() -> bool override
         {
             if (this->channel == nullptr) {
                 return false;
             }
 
             auto* client = static_cast<WiFiClient*>(this->channel);
-            return client->connected();
+            return client->connected() != 0U;
         }
 
       private:
diff --git a/lib/opengloves_freertos/senseshift/opengloves/opengloves_task.hpp b/lib/opengloves_freertos/senseshift/opengloves/opengloves_task.hpp
index b2e6a4cf..f8509f3d 100644
--- a/lib/opengloves_freertos/senseshift/opengloves/opengloves_task.hpp
+++ b/lib/opengloves_freertos/senseshift/opengloves/opengloves_task.hpp
@@ -17,11 +17,6 @@
 #include <utility>
 #include <vector>
 
-#define SS_ADD_CALIBRATOR_NOT_NULL(PTR, STORAGE) \
-    if ((PTR) != nullptr) {                      \
-        STORAGE.push_back(PTR);                  \
-    }
-
 namespace SenseShift::OpenGloves {
     class OpenGlovesTrackingComponent : public SenseShift::Component {
       public:
@@ -39,33 +34,14 @@ namespace SenseShift::OpenGloves {
             }
         };
 
-        OpenGlovesTrackingComponent(Config& config, InputSensors& input_sensors, ITransport* communication) :
-          config_(config), input_sensors_(std::move(input_sensors)), communication_(communication)
+        OpenGlovesTrackingComponent(
+          Config& config,
+          InputSensors& input_sensors,
+          ITransport* communication,
+          og::IEncoder* encoder = new og::AlphaEncoder()
+        ) :
+          config_(config), input_sensors_(std::move(input_sensors)), communication_(communication), encoder_(encoder)
         {
-            this->encoder_ = new og::AlphaEncoder();
-
-            for (auto& finger_curl : this->input_sensors_.curl.fingers) {
-                for (auto& joint_sensor : finger_curl.curl) {
-                    SS_ADD_CALIBRATOR_NOT_NULL(joint_sensor, this->calibrated_inputs_);
-                }
-            }
-
-            for (auto& finger_splay : this->input_sensors_.splay.fingers) {
-                SS_ADD_CALIBRATOR_NOT_NULL(finger_splay, this->calibrated_inputs_);
-            }
-
-            SS_ADD_CALIBRATOR_NOT_NULL(this->input_sensors_.joystick.x, this->calibrated_inputs_);
-            SS_ADD_CALIBRATOR_NOT_NULL(this->input_sensors_.joystick.y, this->calibrated_inputs_);
-            SS_ADD_CALIBRATOR_NOT_NULL(this->input_sensors_.joystick.press, this->calibrated_inputs_);
-
-            for (auto& button : this->input_sensors_.buttons) {
-                SS_ADD_CALIBRATOR_NOT_NULL(button.press, this->calibrated_inputs_);
-            }
-
-            for (auto& analog_button : this->input_sensors_.analog_buttons) {
-                SS_ADD_CALIBRATOR_NOT_NULL(analog_button.press, this->calibrated_inputs_);
-                SS_ADD_CALIBRATOR_NOT_NULL(analog_button.value, this->calibrated_inputs_);
-            }
         }
 
         void init() override
@@ -81,39 +57,47 @@ namespace SenseShift::OpenGloves {
 
         void tick() override
         {
-            // const auto start = millis();
+            // const auto start = micros();
 
-            // auto now = millis();
+            // auto now = micros();
             this->input_sensors_.tick();
-            // const auto tickTime = millis() - now;
+            // const auto tickTime = micros() - now;
 
-            // now = millis();
+            // now = micros();
             const auto data = this->input_sensors_.collectData();
-            // const auto collectTime = millis() - now;
+            // const auto collectTime = micros() - now;
 
-            if (data.button_calibrate.press) {
+            bool const calibrate_pressed = data.button_calibrate.press;
+            if (calibrate_pressed && this->calibration_start_time_ == 0) {
                 this->startCalibration();
             }
 
-            // now = millis();
+            // now = micros();
             const auto length = this->encoder_->encode_input(data, buffer.data(), buffer.size());
-            // const auto encodeTime = millis() - now;
+            // const auto encodeTime = micros() - now;
 
-            // now = millis();
+            // now = micros();
             this->communication_->send(buffer.data(), length);
-            // const auto sendTime = millis() - now;
+            // const auto sendTime = micros() - now;
+
+            if (!this->config_.always_calibrate_ && this->calibration_start_time_ != 0) {
+                const auto calibration_elapsed = millis() - this->calibration_start_time_;
+                const bool calibration_done = calibration_elapsed >= this->config_.calibration_duration_ms_;
 
-            if (!this->config_.always_calibrate_ && (millis() - this->calibration_start_time_) > this->config_.calibration_duration_ms_) {
-                this->stopCalibration();
+                if (calibration_done) {
+                    this->stopCalibration();
+                }
             }
 
-            // log_d(
-            //   "total: %d, tick: %d, collect: %d, encode: %d, send: %d",
-            //   millis() - start,
+            // const auto total = micros() - start;
+            // log_i(
+            //   "total: %d, tick: %d, collect: %d, encode: %d, send: %d, c/s: %.2f",
+            //   total,
             //   tickTime,
             //   collectTime,
             //   encodeTime,
-            //   sendTime
+            //   sendTime,
+            //   1000000.0 / total
             // );
         }
 
@@ -121,26 +105,22 @@ namespace SenseShift::OpenGloves {
         void startCalibration()
         {
             log_i("Starting calibration");
-            for (auto& calibrated_input : this->calibrated_inputs_) {
-                calibrated_input->reselCalibration();
-                calibrated_input->startCalibration();
-            }
+            this->input_sensors_.reselCalibration();
+            this->input_sensors_.startCalibration();
             this->calibration_start_time_ = millis();
         }
 
         void stopCalibration()
         {
             log_i("Stopping calibration");
-            for (auto& calibrated_input : this->calibrated_inputs_) {
-                calibrated_input->stopCalibration();
-            }
+            this->input_sensors_.stopCalibration();
+            this->calibration_start_time_ = 0;
         }
 
       private:
-        std::array<char, 256> buffer;
+        std::array<char, 256> buffer = {};
 
-        unsigned long long calibration_start_time_;
-        std::vector<::SenseShift::Input::Calibration::ICalibrated*> calibrated_inputs_;
+        unsigned long long calibration_start_time_ = 0;
 
         Config& config_;
         InputSensors input_sensors_;
@@ -148,124 +128,36 @@ namespace SenseShift::OpenGloves {
         og::IEncoder* encoder_;
     };
 
-    //    class OpenGlovesForceFeedbackTask : public SenseShift::FreeRTOS::Task<OpenGlovesForceFeedbackTask> {
-    //        friend class SenseShift::FreeRTOS::Task<OpenGlovesForceFeedbackTask>;
-    //
-    //      public:
-    //        OpenGlovesForceFeedbackTask(
-    //          ::SenseShift::OpenGloves::ITransport& communication,
-    //          HandActuators& actuators,
-    //          size_t updateRate,
-    //          SenseShift::FreeRTOS::TaskConfig taskConfig
-    //        ) :
-    //          communication(communication),
-    //          actuators(actuators),
-    //          SenseShift::FreeRTOS::Task<OpenGlovesForceFeedbackTask>(taskConfig)
-    //        {
-    //            this->updateIntervalMs = 1000 / updateRate;
-    //        };
-    //
-    //        void begin() override
-    //        {
-    //            log_d("Starting OpenGloves force feedback task: %p", this);
-    //            this->setup();
-    //            this->SenseShift::FreeRTOS::Task<OpenGlovesForceFeedbackTask>::begin();
-    //        };
-    //
-    //      private:
-    //        ::SenseShift::OpenGloves::ITransport& communication;
-    //        HandActuators& actuators;
-    //        size_t updateIntervalMs;
-    //        ::SenseShift::OpenGloves::AlphaEncodingService encodingService =
-    //          ::SenseShift::OpenGloves::AlphaEncodingService();
-    //
-    //        void setup()
-    //        {
-    //            log_d("Setting up OpenGloves force feedback task: %p", this);
-    //            this->communication.setup();
-    //
-    //            if (this->actuators.thumb.has_value()) {
-    //                this->actuators.thumb.value()->setup();
-    //            }
-    //
-    //            if (this->actuators.index.has_value()) {
-    //                this->actuators.index.value()->setup();
-    //            }
-    //
-    //            if (this->actuators.middle.has_value()) {
-    //                this->actuators.middle.value()->setup();
-    //            }
-    //
-    //            if (this->actuators.ring.has_value()) {
-    //                this->actuators.ring.value()->setup();
-    //            }
-    //
-    //            if (this->actuators.pinky.has_value()) {
-    //                this->actuators.pinky.value()->setup();
-    //            }
-    //        }
-    //
-    //        void run()
-    //        {
-    //            char commandBuffer[256];
-    //            std::string command;
-    //            while (true) {
-    //                auto now = millis();
-    //
-    //                if (this->communication.hasData()) {
-    //                    auto bytesRead = this->communication.read(commandBuffer, sizeof(commandBuffer));
-    //                    if (bytesRead == 0) {
-    //                        continue;
-    //                    }
-    //
-    //                    std::map<::OpenGloves::Command, uint16_t> commands = {};
-    //                    this->encodingService.deserialize(commandBuffer, bytesRead, commands);
-    //
-    //                    for (auto& [command, value] : commands) {
-    //                        this->handleCommand(command, value);
-    //                    }
-    //                }
-    //
-    //                // Delay until the next update.
-    //                auto elapsed = millis() - now;
-    //                if (elapsed < this->updateIntervalMs) {
-    //                    delay(this->updateIntervalMs - elapsed);
-    //                }
-    //            }
-    //        }
-    //
-    //        void handleCommand(Command command, uint16_t value)
-    //        {
-    //            switch (command) {
-    //                case Command::ThumbCurl:
-    //                    if (this->actuators.thumb.has_value()) {
-    //                        this->actuators.thumb.value()->writeOutput(value);
-    //                    }
-    //                    break;
-    //                case Command::IndexCurl:
-    //                    if (this->actuators.index.has_value()) {
-    //                        this->actuators.index.value()->writeOutput(value);
-    //                    }
-    //                    break;
-    //                case Command::MiddleCurl:
-    //                    if (this->actuators.middle.has_value()) {
-    //                        this->actuators.middle.value()->writeOutput(value);
-    //                    }
-    //                    break;
-    //                case Command::RingCurl:
-    //                    if (this->actuators.ring.has_value()) {
-    //                        this->actuators.ring.value()->writeOutput(value);
-    //                    }
-    //                    break;
-    //                case Command::PinkyCurl:
-    //                    if (this->actuators.pinky.has_value()) {
-    //                        this->actuators.pinky.value()->writeOutput(value);
-    //                    }
-    //                    break;
-    //                default:
-    //                    log_w("Unhandled command: %d", command);
-    //                    break;
-    //            }
-    //        }
-    //    };
+    class OpenGlovesForceFeedbackComponent : public SenseShift::Component {
+      public:
+        OpenGlovesForceFeedbackComponent(
+          OutputWriters& output_writers,
+          ::SenseShift::OpenGloves::ITransport* communication,
+          og::IEncoder* encoder = new og::AlphaEncoder()
+        ) :
+          output_writers_(output_writers), communication_(communication), encoder_(encoder){};
+
+        void init() override
+        {
+            log_d("Setting up OpenGloves force feedback task: %p", this);
+            this->communication_->init();
+            this->output_writers_.init();
+        }
+
+        void tick() override
+        {
+            if (this->communication_->hasData()) {
+                const auto length = this->communication_->read(this->buffer.data(), this->buffer.size());
+                const auto output = this->encoder_->decode_output(this->buffer.data(), length);
+                this->output_writers_.apply(output);
+            }
+        }
+
+      private:
+        std::array<char, 256> buffer = {};
+
+        OutputWriters output_writers_;
+        ::SenseShift::OpenGloves::ITransport* communication_;
+        og::IEncoder* encoder_;
+    };
 } // namespace SenseShift::OpenGloves
diff --git a/platformio.ini b/platformio.ini
index 2b718462..ea55d804 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -9,58 +9,58 @@
 ; https://docs.platformio.org/page/projectconf.html
 
 [platformio]
-description   = Open Source Haptic-feedback device firmware
-lib_dir       = ./lib
-src_dir       = ./firmware
+description = Open Source Haptic-feedback device firmware
+lib_dir = ./lib
+src_dir = ./firmware
 extra_configs =
-    ini/bhaptics.ini
+	ini/bhaptics.ini
 	ini/opengloves.ini
 	ini/opengloves-lucidgloves.ini
 	ini/opengloves-indexer.ini
-default_envs  = bhaptics_tactsuit_x16, bhaptics_tactal, bhaptics_tactglove_left, lucidgloves-prototype3
+default_envs = bhaptics_tactsuit_x16, bhaptics_tactal, bhaptics_tactglove_left, lucidgloves-prototype3
 
 [common]
-build_unflags     =
+build_unflags =
 	-std=gnu++11
-build_flags       =
+build_flags =
 	-std=gnu++17
 	-D __OH_FIRMWARE__
 	-D CORE_DEBUG_LEVEL=3
 ;	-D DEBUG_MODE=0
 ;	-D DEBUG_ESP_PORT=Serial
 ;	-D SENSESHIFT_SERIAL_PLOTTER=true
-	-D SENSESHIFT_BATTERY_ENABLED=true
+;	-D SENSESHIFT_BATTERY_ENABLED=true
 ;	-D SENSESHIFT_BLE_USE_NIMBLE=true
 
-build_src_filter  =
+build_src_filter =
 	+<*>
 	-<mode_configs>
 
-lib_deps          =
+lib_deps =
 
 [env]
-build_flags      = ${common.build_flags}
-build_unflags    = ${common.build_unflags}
+build_flags = ${common.build_flags}
+build_unflags = ${common.build_unflags}
 build_src_filter = ${common.build_src_filter}
-lib_deps         = ${common.lib_deps}
-lib_ldf_mode     = deep+
+lib_deps = ${common.lib_deps}
+lib_ldf_mode = deep+
 
-check_tool  = clangtidy
+check_tool = clangtidy
 check_flags =
 	clangtidy: --config-file=./.clang-tidy --fix
 
 debug_build_flags = -Os
 
 [env:native]
-platform 		 = native
+platform = native
 
-build_unflags    = ${common.build_unflags}
-build_flags      = ${common.build_flags}
-	-lgcov
-	--coverage
+build_unflags = ${common.build_unflags}
+build_flags = ${common.build_flags}
+			  -lgcov
+			  --coverage
 build_src_filter = ${common.build_src_filter}
-	+<mode_configs/test.cpp>
-lib_deps         = ${common.lib_deps}
-	fabiobatsilva/ArduinoFake@^0.4
+				   +<mode_configs/test.cpp>
+lib_deps = ${common.lib_deps}
+		   fabiobatsilva/ArduinoFake@^0.4
 
-test_ignore      = test_embedded
+test_ignore = test_embedded

From dbf35e25094344c14f5b14a76387a8f5afe51de3 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Sun, 18 Feb 2024 13:45:55 +0400
Subject: [PATCH 69/82] fix(BLE Serial): fix sending duplicate strings

---
 ini/bhaptics.ini             | 418 +++++++++++++++++------------------
 lib/ble_serial/BLESerial.hpp |   4 +-
 2 files changed, 211 insertions(+), 211 deletions(-)

diff --git a/ini/bhaptics.ini b/ini/bhaptics.ini
index ac624ea8..f3289e11 100644
--- a/ini/bhaptics.ini
+++ b/ini/bhaptics.ini
@@ -1,71 +1,71 @@
 [bhaptics]
-platform         	= platformio/espressif32@^6.1.0
-platform_packages	=
-    platformio/framework-arduinoespressif32@^3.20007.0
-framework 	     	= arduino
-board            	= esp32doit-devkit-v1
-upload_speed     	= 921600
-monitor_speed    	= 115200
+platform = platformio/espressif32@^6.1.0
+platform_packages =
+	platformio/framework-arduinoespressif32@^3.20014.231204
+framework = arduino
+board = esp32doit-devkit-v1
+upload_speed = 921600
+monitor_speed = 115200
 
-build_flags 		= ${common.build_flags}
-	-D BHAPTICS
-build_unflags   	= ${common.build_unflags}
-build_src_filter	= ${common.build_src_filter}
-lib_deps    		= ${common.lib_deps}
+build_flags = ${common.build_flags}
+			  -D BHAPTICS
+build_unflags = ${common.build_unflags}
+build_src_filter = ${common.build_src_filter}
+lib_deps = ${common.lib_deps}
 
 [env:bhaptics_tactsuit_x16]
-platform			= ${bhaptics.platform}
-platform_packages	= ${bhaptics.platform_packages}
-framework			= ${bhaptics.framework}
-board				= ${bhaptics.board}
-upload_speed		= ${bhaptics.upload_speed}
-monitor_speed		= ${bhaptics.monitor_speed}
+platform = ${bhaptics.platform}
+platform_packages = ${bhaptics.platform_packages}
+framework = ${bhaptics.framework}
+board = ${bhaptics.board}
+upload_speed = ${bhaptics.upload_speed}
+monitor_speed = ${bhaptics.monitor_speed}
 
-build_flags 		= ${bhaptics.build_flags}
-	-D BH_DEVICE_TACTSUITX16
-	-D BH_BLE_APPEARANCE=510
-	'-D BLUETOOTH_NAME="TactSuitX16"'
-	'-D BH_SERIAL_NUMBER={ 0x0d, 0x3a, 0xeb, 0x77, 0xbe, 0xf8, 0x7a, 0x1e, 0x3b, 0x2a }'
-build_unflags 		= ${bhaptics.build_unflags}
-build_src_filter	= ${bhaptics.build_src_filter}
-	+<mode_configs/bhaptics/tactsuit_x16.cpp>
-lib_deps    		= ${bhaptics.lib_deps}
+build_flags = ${bhaptics.build_flags}
+			  -D BH_DEVICE_TACTSUITX16
+			  -D BH_BLE_APPEARANCE=510
+			  '-D BLUETOOTH_NAME="TactSuitX16"'
+			  '-D BH_SERIAL_NUMBER={ 0x0d, 0x3a, 0xeb, 0x77, 0xbe, 0xf8, 0x7a, 0x1e, 0x3b, 0x2a }'
+build_unflags = ${bhaptics.build_unflags}
+build_src_filter = ${bhaptics.build_src_filter}
+				   +<mode_configs/bhaptics/tactsuit_x16.cpp>
+lib_deps = ${bhaptics.lib_deps}
 
 [env:bhaptics_tactsuit_x16_pca9685]
-platform			= ${bhaptics.platform}
-platform_packages	= ${bhaptics.platform_packages}
-framework			= ${bhaptics.framework}
-board				= ${bhaptics.board}
-upload_speed		= ${bhaptics.upload_speed}
-monitor_speed		= ${bhaptics.monitor_speed}
+platform = ${bhaptics.platform}
+platform_packages = ${bhaptics.platform_packages}
+framework = ${bhaptics.framework}
+board = ${bhaptics.board}
+upload_speed = ${bhaptics.upload_speed}
+monitor_speed = ${bhaptics.monitor_speed}
 
-build_flags 		= ${bhaptics.build_flags}
-	-D BH_DEVICE_TACTSUITX16
-	-D BH_BLE_APPEARANCE=510
-	'-D BLUETOOTH_NAME="TactSuitX16"'
-	'-D BH_SERIAL_NUMBER={ 0x0d, 0x3a, 0xeb, 0x77, 0xbe, 0xf8, 0x7a, 0x1e, 0x3b, 0x2a }'
-build_unflags 		= ${bhaptics.build_unflags}
-build_src_filter  	= ${bhaptics.build_src_filter}
-	+<mode_configs/bhaptics/tactsuit_x16_pca9685.cpp>
-lib_deps    		= ${bhaptics.lib_deps}
+build_flags = ${bhaptics.build_flags}
+			  -D BH_DEVICE_TACTSUITX16
+			  -D BH_BLE_APPEARANCE=510
+			  '-D BLUETOOTH_NAME="TactSuitX16"'
+			  '-D BH_SERIAL_NUMBER={ 0x0d, 0x3a, 0xeb, 0x77, 0xbe, 0xf8, 0x7a, 0x1e, 0x3b, 0x2a }'
+build_unflags = ${bhaptics.build_unflags}
+build_src_filter = ${bhaptics.build_src_filter}
+				   +<mode_configs/bhaptics/tactsuit_x16_pca9685.cpp>
+lib_deps = ${bhaptics.lib_deps}
 
 [env:bhaptics_tactsuit_x40]
-platform			= ${bhaptics.platform}
-platform_packages	= ${bhaptics.platform_packages}
-framework			= ${bhaptics.framework}
-board				= ${bhaptics.board}
-upload_speed		= ${bhaptics.upload_speed}
-monitor_speed		= ${bhaptics.monitor_speed}
+platform = ${bhaptics.platform}
+platform_packages = ${bhaptics.platform_packages}
+framework = ${bhaptics.framework}
+board = ${bhaptics.board}
+upload_speed = ${bhaptics.upload_speed}
+monitor_speed = ${bhaptics.monitor_speed}
 
-build_flags 		= ${bhaptics.build_flags}
-	-D BH_DEVICE_TACTSUITX40
-	-D BH_BLE_APPEARANCE=509
-	'-D BLUETOOTH_NAME="TactSuitX40"'
-	'-D BH_SERIAL_NUMBER={ 0xcf, 0xcb, 0x0d, 0x95, 0x5f, 0xf6, 0xee, 0x2c, 0xbd, 0x73 }'
-build_unflags 		= ${bhaptics.build_unflags}
-build_src_filter  	= ${bhaptics.build_src_filter}
-	+<mode_configs/bhaptics/tactsuit_x40.cpp>
-lib_deps    		= ${bhaptics.lib_deps}
+build_flags = ${bhaptics.build_flags}
+			  -D BH_DEVICE_TACTSUITX40
+			  -D BH_BLE_APPEARANCE=509
+			  '-D BLUETOOTH_NAME="TactSuitX40"'
+			  '-D BH_SERIAL_NUMBER={ 0xcf, 0xcb, 0x0d, 0x95, 0x5f, 0xf6, 0xee, 0x2c, 0xbd, 0x73 }'
+build_unflags = ${bhaptics.build_unflags}
+build_src_filter = ${bhaptics.build_src_filter}
+				   +<mode_configs/bhaptics/tactsuit_x40.cpp>
+lib_deps = ${bhaptics.lib_deps}
 
 ; [env:bhaptics_tactbelt]
 ; platform			= ${bhaptics.platform}
@@ -86,183 +86,183 @@ lib_deps    		= ${bhaptics.lib_deps}
 ; lib_deps    		= ${bhaptics.lib_deps}
 
 [env:bhaptics_tactosy2_forearm_left]
-platform			= ${bhaptics.platform}
-platform_packages	= ${bhaptics.platform_packages}
-framework			= ${bhaptics.framework}
-board				= ${bhaptics.board}
-upload_speed		= ${bhaptics.upload_speed}
-monitor_speed		= ${bhaptics.monitor_speed}
+platform = ${bhaptics.platform}
+platform_packages = ${bhaptics.platform_packages}
+framework = ${bhaptics.framework}
+board = ${bhaptics.board}
+upload_speed = ${bhaptics.upload_speed}
+monitor_speed = ${bhaptics.monitor_speed}
 
-build_flags 		= ${bhaptics.build_flags}
-	-D BH_DEVICE_TACTOSY2
-	-D BH_BLE_APPEARANCE=508
-	'-D BLUETOOTH_NAME="Tactosy2_L"'
-	'-D BH_SERIAL_NUMBER={ 0xa0, 0xba, 0x0a, 0xd1, 0xbf, 0x36, 0x11, 0x30, 0xa4, 0xff }'
-build_unflags 		= ${bhaptics.build_unflags}
-build_src_filter  	= ${bhaptics.build_src_filter}
-	+<mode_configs/bhaptics/tactosy2.cpp>
-lib_deps    		= ${bhaptics.lib_deps}
+build_flags = ${bhaptics.build_flags}
+			  -D BH_DEVICE_TACTOSY2
+			  -D BH_BLE_APPEARANCE=508
+			  '-D BLUETOOTH_NAME="Tactosy2_L"'
+			  '-D BH_SERIAL_NUMBER={ 0xa0, 0xba, 0x0a, 0xd1, 0xbf, 0x36, 0x11, 0x30, 0xa4, 0xff }'
+build_unflags = ${bhaptics.build_unflags}
+build_src_filter = ${bhaptics.build_src_filter}
+				   +<mode_configs/bhaptics/tactosy2.cpp>
+lib_deps = ${bhaptics.lib_deps}
 
 [env:bhaptics_tactosy2_forearm_right]
-platform			= ${bhaptics.platform}
-platform_packages	= ${bhaptics.platform_packages}
-framework			= ${bhaptics.framework}
-board				= ${bhaptics.board}
-upload_speed		= ${bhaptics.upload_speed}
-monitor_speed		= ${bhaptics.monitor_speed}
+platform = ${bhaptics.platform}
+platform_packages = ${bhaptics.platform_packages}
+framework = ${bhaptics.framework}
+board = ${bhaptics.board}
+upload_speed = ${bhaptics.upload_speed}
+monitor_speed = ${bhaptics.monitor_speed}
 
-build_flags 		= ${bhaptics.build_flags}
-	-D BH_DEVICE_TACTOSY2
-	-D BH_BLE_APPEARANCE=508
-	'-D BLUETOOTH_NAME="Tactosy2_R"'
-	'-D BH_SERIAL_NUMBER={ 0xb0, 0x1c, 0xc1, 0xf8, 0xec, 0x12, 0x18, 0x4e, 0x09, 0x77 }'
-build_unflags 		= ${bhaptics.build_unflags}
-build_src_filter  	= ${bhaptics.build_src_filter}
-	+<mode_configs/bhaptics/tactosy2.cpp>
-lib_deps    		= ${bhaptics.lib_deps}
+build_flags = ${bhaptics.build_flags}
+			  -D BH_DEVICE_TACTOSY2
+			  -D BH_BLE_APPEARANCE=508
+			  '-D BLUETOOTH_NAME="Tactosy2_R"'
+			  '-D BH_SERIAL_NUMBER={ 0xb0, 0x1c, 0xc1, 0xf8, 0xec, 0x12, 0x18, 0x4e, 0x09, 0x77 }'
+build_unflags = ${bhaptics.build_unflags}
+build_src_filter = ${bhaptics.build_src_filter}
+				   +<mode_configs/bhaptics/tactosy2.cpp>
+lib_deps = ${bhaptics.lib_deps}
 
 [env:bhaptics_tactosyh_hand_left]
-platform			= ${bhaptics.platform}
-platform_packages	= ${bhaptics.platform_packages}
-framework			= ${bhaptics.framework}
-board				= ${bhaptics.board}
-upload_speed		= ${bhaptics.upload_speed}
-monitor_speed		= ${bhaptics.monitor_speed}
+platform = ${bhaptics.platform}
+platform_packages = ${bhaptics.platform_packages}
+framework = ${bhaptics.framework}
+board = ${bhaptics.board}
+upload_speed = ${bhaptics.upload_speed}
+monitor_speed = ${bhaptics.monitor_speed}
 
-build_flags 		= ${bhaptics.build_flags}
-	-D BH_DEVICE_TACTOSYH
-	-D BH_BLE_APPEARANCE=508
-	'-D BLUETOOTH_NAME="TactosyH_L"'
-	'-D BH_SERIAL_NUMBER={ 0xc1, 0x36, 0xdc, 0x21, 0xc9, 0xd4, 0x17, 0x85, 0xbb, 0x90 }'
-build_unflags 		= ${bhaptics.build_unflags}
-build_src_filter  	= ${bhaptics.build_src_filter}
-	+<mode_configs/bhaptics/tactosyh.cpp>
-lib_deps    		= ${bhaptics.lib_deps}
+build_flags = ${bhaptics.build_flags}
+			  -D BH_DEVICE_TACTOSYH
+			  -D BH_BLE_APPEARANCE=508
+			  '-D BLUETOOTH_NAME="TactosyH_L"'
+			  '-D BH_SERIAL_NUMBER={ 0xc1, 0x36, 0xdc, 0x21, 0xc9, 0xd4, 0x17, 0x85, 0xbb, 0x90 }'
+build_unflags = ${bhaptics.build_unflags}
+build_src_filter = ${bhaptics.build_src_filter}
+				   +<mode_configs/bhaptics/tactosyh.cpp>
+lib_deps = ${bhaptics.lib_deps}
 
 [env:bhaptics_tactosyh_hand_right]
-platform			= ${bhaptics.platform}
-platform_packages	= ${bhaptics.platform_packages}
-framework			= ${bhaptics.framework}
-board				= ${bhaptics.board}
-upload_speed		= ${bhaptics.upload_speed}
-monitor_speed		= ${bhaptics.monitor_speed}
+platform = ${bhaptics.platform}
+platform_packages = ${bhaptics.platform_packages}
+framework = ${bhaptics.framework}
+board = ${bhaptics.board}
+upload_speed = ${bhaptics.upload_speed}
+monitor_speed = ${bhaptics.monitor_speed}
 
-build_flags 		= ${bhaptics.build_flags}
-	-D BH_DEVICE_TACTOSYH
-	-D BH_BLE_APPEARANCE=508
-	'-D BLUETOOTH_NAME="TactosyH_R"'
-	'-D BH_SERIAL_NUMBER={ 0xc7, 0x5f, 0x3b, 0x06, 0x38, 0xba, 0x34, 0xfa, 0x36, 0xc1 }'
-build_unflags 		= ${bhaptics.build_unflags}
-build_src_filter 	= ${bhaptics.build_src_filter}
-	+<mode_configs/bhaptics/tactosyh.cpp>
-lib_deps    		= ${bhaptics.lib_deps}
+build_flags = ${bhaptics.build_flags}
+			  -D BH_DEVICE_TACTOSYH
+			  -D BH_BLE_APPEARANCE=508
+			  '-D BLUETOOTH_NAME="TactosyH_R"'
+			  '-D BH_SERIAL_NUMBER={ 0xc7, 0x5f, 0x3b, 0x06, 0x38, 0xba, 0x34, 0xfa, 0x36, 0xc1 }'
+build_unflags = ${bhaptics.build_unflags}
+build_src_filter = ${bhaptics.build_src_filter}
+				   +<mode_configs/bhaptics/tactosyh.cpp>
+lib_deps = ${bhaptics.lib_deps}
 
 [env:bhaptics_tactosyf_foot_left]
-platform			= ${bhaptics.platform}
-platform_packages	= ${bhaptics.platform_packages}
-framework			= ${bhaptics.framework}
-board				= ${bhaptics.board}
-upload_speed		= ${bhaptics.upload_speed}
-monitor_speed		= ${bhaptics.monitor_speed}
+platform = ${bhaptics.platform}
+platform_packages = ${bhaptics.platform_packages}
+framework = ${bhaptics.framework}
+board = ${bhaptics.board}
+upload_speed = ${bhaptics.upload_speed}
+monitor_speed = ${bhaptics.monitor_speed}
 
-build_flags 		= ${bhaptics.build_flags}
-	-D BH_DEVICE_TACTOSYF
-	-D BH_BLE_APPEARANCE=508
-	'-D BLUETOOTH_NAME="TactosyF_L"'
-	'-D BH_SERIAL_NUMBER={ 0x1a, 0x45, 0x83, 0x44, 0x03, 0xc5, 0xf3, 0xc3, 0xf3, 0xb8 }'
-build_unflags 		= ${bhaptics.build_unflags}
-build_src_filter  	= ${bhaptics.build_src_filter}
-	+<mode_configs/bhaptics/tactosyf.cpp>
-lib_deps    		= ${bhaptics.lib_deps}
+build_flags = ${bhaptics.build_flags}
+			  -D BH_DEVICE_TACTOSYF
+			  -D BH_BLE_APPEARANCE=508
+			  '-D BLUETOOTH_NAME="TactosyF_L"'
+			  '-D BH_SERIAL_NUMBER={ 0x1a, 0x45, 0x83, 0x44, 0x03, 0xc5, 0xf3, 0xc3, 0xf3, 0xb8 }'
+build_unflags = ${bhaptics.build_unflags}
+build_src_filter = ${bhaptics.build_src_filter}
+				   +<mode_configs/bhaptics/tactosyf.cpp>
+lib_deps = ${bhaptics.lib_deps}
 
 [env:bhaptics_tactosyf_foot_right]
-platform			= ${bhaptics.platform}
-platform_packages	= ${bhaptics.platform_packages}
-framework			= ${bhaptics.framework}
-board				= ${bhaptics.board}
-upload_speed		= ${bhaptics.upload_speed}
-monitor_speed		= ${bhaptics.monitor_speed}
+platform = ${bhaptics.platform}
+platform_packages = ${bhaptics.platform_packages}
+framework = ${bhaptics.framework}
+board = ${bhaptics.board}
+upload_speed = ${bhaptics.upload_speed}
+monitor_speed = ${bhaptics.monitor_speed}
 
-build_flags 		= ${bhaptics.build_flags}
-	-D BH_DEVICE_TACTOSYF
-	-D BH_BLE_APPEARANCE=508
-	'-D BLUETOOTH_NAME="TactosyF_R"'
-	'-D BH_SERIAL_NUMBER={ 0x14, 0xb9, 0x02, 0x62, 0x41, 0xe4, 0x04, 0xb2, 0xc5, 0x11 }'
-build_unflags 		= ${bhaptics.build_unflags}
-build_src_filter  	= ${bhaptics.build_src_filter}
-	+<mode_configs/bhaptics/tactosyf.cpp>
-lib_deps    		= ${bhaptics.lib_deps}
+build_flags = ${bhaptics.build_flags}
+			  -D BH_DEVICE_TACTOSYF
+			  -D BH_BLE_APPEARANCE=508
+			  '-D BLUETOOTH_NAME="TactosyF_R"'
+			  '-D BH_SERIAL_NUMBER={ 0x14, 0xb9, 0x02, 0x62, 0x41, 0xe4, 0x04, 0xb2, 0xc5, 0x11 }'
+build_unflags = ${bhaptics.build_unflags}
+build_src_filter = ${bhaptics.build_src_filter}
+				   +<mode_configs/bhaptics/tactosyf.cpp>
+lib_deps = ${bhaptics.lib_deps}
 
 [env:bhaptics_tactal]
-platform			= ${bhaptics.platform}
-platform_packages	= ${bhaptics.platform_packages}
-framework			= ${bhaptics.framework}
-board				= ${bhaptics.board}
-upload_speed		= ${bhaptics.upload_speed}
-monitor_speed		= ${bhaptics.monitor_speed}
+platform = ${bhaptics.platform}
+platform_packages = ${bhaptics.platform_packages}
+framework = ${bhaptics.framework}
+board = ${bhaptics.board}
+upload_speed = ${bhaptics.upload_speed}
+monitor_speed = ${bhaptics.monitor_speed}
 
-build_flags 		= ${bhaptics.build_flags}
-	-D BH_DEVICE_TACTAL
-	-D BH_BLE_APPEARANCE=508
-	'-D BLUETOOTH_NAME="Tactal_"'
-	'-D BH_SERIAL_NUMBER={ 0xed, 0xcb, 0x55, 0x7c, 0xd7, 0xb9, 0x16, 0xc5, 0x18, 0x2a }'
-build_unflags 		= ${bhaptics.build_unflags}
-build_src_filter  	= ${bhaptics.build_src_filter}
-	+<mode_configs/bhaptics/tactal.cpp>
-lib_deps    		= ${bhaptics.lib_deps}
+build_flags = ${bhaptics.build_flags}
+			  -D BH_DEVICE_TACTAL
+			  -D BH_BLE_APPEARANCE=508
+			  '-D BLUETOOTH_NAME="Tactal_"'
+			  '-D BH_SERIAL_NUMBER={ 0xed, 0xcb, 0x55, 0x7c, 0xd7, 0xb9, 0x16, 0xc5, 0x18, 0x2a }'
+build_unflags = ${bhaptics.build_unflags}
+build_src_filter = ${bhaptics.build_src_filter}
+				   +<mode_configs/bhaptics/tactal.cpp>
+lib_deps = ${bhaptics.lib_deps}
 
 [env:bhaptics_tactvisor]
-platform			= ${bhaptics.platform}
-platform_packages	= ${bhaptics.platform_packages}
-framework			= ${bhaptics.framework}
-board				= ${bhaptics.board}
-upload_speed		= ${bhaptics.upload_speed}
-monitor_speed		= ${bhaptics.monitor_speed}
+platform = ${bhaptics.platform}
+platform_packages = ${bhaptics.platform_packages}
+framework = ${bhaptics.framework}
+board = ${bhaptics.board}
+upload_speed = ${bhaptics.upload_speed}
+monitor_speed = ${bhaptics.monitor_speed}
 
-build_flags 		= ${bhaptics.build_flags}
-	-D BH_DEVICE_TACTAL
-	-D BH_BLE_APPEARANCE=508
-	'-D BLUETOOTH_NAME="TactVisor_V____"'
-	'-D BH_SERIAL_NUMBER={ 0x5e, 0xa3, 0xdd, 0x12, 0x00, 0x01, 0x43, 0xc1, 0x26, 0x8a }'
-build_unflags 		= ${bhaptics.build_unflags}
-build_src_filter  	= ${bhaptics.build_src_filter}
-	+<mode_configs/bhaptics/tactvisor.cpp>
-lib_deps    		= ${bhaptics.lib_deps}
+build_flags = ${bhaptics.build_flags}
+			  -D BH_DEVICE_TACTAL
+			  -D BH_BLE_APPEARANCE=508
+			  '-D BLUETOOTH_NAME="TactVisor_V____"'
+			  '-D BH_SERIAL_NUMBER={ 0x5e, 0xa3, 0xdd, 0x12, 0x00, 0x01, 0x43, 0xc1, 0x26, 0x8a }'
+build_unflags = ${bhaptics.build_unflags}
+build_src_filter = ${bhaptics.build_src_filter}
+				   +<mode_configs/bhaptics/tactvisor.cpp>
+lib_deps = ${bhaptics.lib_deps}
 
 [env:bhaptics_tactglove_left]
-platform			= ${bhaptics.platform}
-platform_packages	= ${bhaptics.platform_packages}
-framework			= ${bhaptics.framework}
-board				= ${bhaptics.board}
-upload_speed		= ${bhaptics.upload_speed}
-monitor_speed		= ${bhaptics.monitor_speed}
+platform = ${bhaptics.platform}
+platform_packages = ${bhaptics.platform_packages}
+framework = ${bhaptics.framework}
+board = ${bhaptics.board}
+upload_speed = ${bhaptics.upload_speed}
+monitor_speed = ${bhaptics.monitor_speed}
 
-build_flags 		= ${bhaptics.build_flags}
-	-D BH_DEVICE_TACTGLOVE
-	-D SENSESHIFT_HAND_SIDE=Left
-	-D BH_BLE_APPEARANCE=508
-	'-D BLUETOOTH_NAME="TactGlove (L"'
-	'-D BH_SERIAL_NUMBER={ 0xcd, 0x0b, 0x81, 0x45, 0x85, 0xf9, 0x2b, 0x6c, 0xed, 0x5b }'
-build_unflags 		= ${bhaptics.build_unflags}
-build_src_filter  	= ${bhaptics.build_src_filter}
-	+<mode_configs/bhaptics/tactglove.cpp>
-lib_deps    		= ${bhaptics.lib_deps}
+build_flags = ${bhaptics.build_flags}
+			  -D BH_DEVICE_TACTGLOVE
+			  -D SENSESHIFT_HAND_SIDE=Left
+			  -D BH_BLE_APPEARANCE=508
+			  '-D BLUETOOTH_NAME="TactGlove (L"'
+			  '-D BH_SERIAL_NUMBER={ 0xcd, 0x0b, 0x81, 0x45, 0x85, 0xf9, 0x2b, 0x6c, 0xed, 0x5b }'
+build_unflags = ${bhaptics.build_unflags}
+build_src_filter = ${bhaptics.build_src_filter}
+				   +<mode_configs/bhaptics/tactglove.cpp>
+lib_deps = ${bhaptics.lib_deps}
 
 [env:bhaptics_tactglove_right]
-platform			= ${bhaptics.platform}
-platform_packages	= ${bhaptics.platform_packages}
-framework			= ${bhaptics.framework}
-board				= ${bhaptics.board}
-upload_speed		= ${bhaptics.upload_speed}
-monitor_speed		= ${bhaptics.monitor_speed}
+platform = ${bhaptics.platform}
+platform_packages = ${bhaptics.platform_packages}
+framework = ${bhaptics.framework}
+board = ${bhaptics.board}
+upload_speed = ${bhaptics.upload_speed}
+monitor_speed = ${bhaptics.monitor_speed}
 
-build_flags 		= ${bhaptics.build_flags}
-	-D BH_DEVICE_TACTGLOVE
-	-D SENSESHIFT_HAND_SIDE=Right
-	-D BH_BLE_APPEARANCE=508
-	'-D BLUETOOTH_NAME="TactGlove (R"'
-	'-D BH_SERIAL_NUMBER={ 0x12, 0x0b, 0xae, 0xbf, 0xbc, 0x90, 0x3b, 0x0d, 0x84, 0xdd }'
-build_unflags 		= ${bhaptics.build_unflags}
-build_src_filter 	= ${bhaptics.build_src_filter}
-	+<mode_configs/bhaptics/tactglove.cpp>
-lib_deps    		= ${bhaptics.lib_deps}
+build_flags = ${bhaptics.build_flags}
+			  -D BH_DEVICE_TACTGLOVE
+			  -D SENSESHIFT_HAND_SIDE=Right
+			  -D BH_BLE_APPEARANCE=508
+			  '-D BLUETOOTH_NAME="TactGlove (R"'
+			  '-D BH_SERIAL_NUMBER={ 0x12, 0x0b, 0xae, 0xbf, 0xbc, 0x90, 0x3b, 0x0d, 0x84, 0xdd }'
+build_unflags = ${bhaptics.build_unflags}
+build_src_filter = ${bhaptics.build_src_filter}
+				   +<mode_configs/bhaptics/tactglove.cpp>
+lib_deps = ${bhaptics.lib_deps}
diff --git a/lib/ble_serial/BLESerial.hpp b/lib/ble_serial/BLESerial.hpp
index b239eec2..090ecb75 100644
--- a/lib/ble_serial/BLESerial.hpp
+++ b/lib/ble_serial/BLESerial.hpp
@@ -42,7 +42,7 @@ class BLESerial : public Stream {
         }
 
         this->m_pTxCharacteristic->setValue(const_cast<uint8_t*>(buffer), bufferSize);
-        this->flush();
+        // this->flush();
 
         return bufferSize;
     }
@@ -54,7 +54,7 @@ class BLESerial : public Stream {
         }
 
         this->m_pTxCharacteristic->setValue(&byte, 1);
-        this->flush();
+        // this->flush();
 
         return 1;
     }

From 74cd0aaf56365a1cab9703df4a4b798698519af5 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Sun, 18 Feb 2024 15:17:06 +0400
Subject: [PATCH 70/82] fix(bHaptics): removed file being included

---
 firmware/mode_configs/bhaptics/tactal.cpp               | 7 +++----
 firmware/mode_configs/bhaptics/tactglove.cpp            | 7 +++----
 firmware/mode_configs/bhaptics/tactosy2.cpp             | 7 +++----
 firmware/mode_configs/bhaptics/tactosyf.cpp             | 7 +++----
 firmware/mode_configs/bhaptics/tactosyh.cpp             | 7 +++----
 firmware/mode_configs/bhaptics/tactsuit_x16.cpp         | 7 +++----
 firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp | 7 +++----
 firmware/mode_configs/bhaptics/tactsuit_x40.cpp         | 7 +++----
 firmware/mode_configs/bhaptics/tactvisor.cpp            | 7 +++----
 9 files changed, 27 insertions(+), 36 deletions(-)

diff --git a/firmware/mode_configs/bhaptics/tactal.cpp b/firmware/mode_configs/bhaptics/tactal.cpp
index 8a452ab5..ec8ea898 100644
--- a/firmware/mode_configs/bhaptics/tactal.cpp
+++ b/firmware/mode_configs/bhaptics/tactal.cpp
@@ -4,22 +4,21 @@
 #include <Arduino.h>
 #include <Wire.h>
 
-#include "senseshift.h"
+#include <senseshift.h>
 
-#include "senseshift/battery/input/battery_sensor.hpp"
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/ledc.hpp>
+#include <senseshift/battery/input/battery_sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
-#include <senseshift/freertos/input/sensor.hpp>
+#include <senseshift/freertos/task.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Input;
 using namespace SenseShift::Input::Filter;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
-using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::Battery::Input;
 using namespace SenseShift::BH;
diff --git a/firmware/mode_configs/bhaptics/tactglove.cpp b/firmware/mode_configs/bhaptics/tactglove.cpp
index 1fa2aa4e..d67f58e6 100644
--- a/firmware/mode_configs/bhaptics/tactglove.cpp
+++ b/firmware/mode_configs/bhaptics/tactglove.cpp
@@ -4,15 +4,15 @@
 #include <Arduino.h>
 #include <Wire.h>
 
-#include "senseshift.h"
+#include <senseshift.h>
 
-#include "senseshift/battery/input/battery_sensor.hpp"
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/ledc.hpp>
+#include <senseshift/battery/input/battery_sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
-#include <senseshift/freertos/input/sensor.hpp>
+#include <senseshift/freertos/task.hpp>
 #include <senseshift/utility.hpp>
 
 using namespace SenseShift;
@@ -20,7 +20,6 @@ using namespace SenseShift::Input;
 using namespace SenseShift::Input::Filter;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
-using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::Battery::Input;
 using namespace SenseShift::BH;
diff --git a/firmware/mode_configs/bhaptics/tactosy2.cpp b/firmware/mode_configs/bhaptics/tactosy2.cpp
index e630072b..c3053bd3 100644
--- a/firmware/mode_configs/bhaptics/tactosy2.cpp
+++ b/firmware/mode_configs/bhaptics/tactosy2.cpp
@@ -4,22 +4,21 @@
 #include <Arduino.h>
 #include <Wire.h>
 
-#include "senseshift.h"
+#include <senseshift.h>
 
-#include "senseshift/battery/input/battery_sensor.hpp"
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/ledc.hpp>
+#include <senseshift/battery/input/battery_sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
-#include <senseshift/freertos/input/sensor.hpp>
+#include <senseshift/freertos/task.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Input;
 using namespace SenseShift::Input::Filter;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
-using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::Battery::Input;
 using namespace SenseShift::BH;
diff --git a/firmware/mode_configs/bhaptics/tactosyf.cpp b/firmware/mode_configs/bhaptics/tactosyf.cpp
index d35a6427..b2c47e26 100644
--- a/firmware/mode_configs/bhaptics/tactosyf.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyf.cpp
@@ -4,22 +4,21 @@
 #include <Arduino.h>
 #include <Wire.h>
 
-#include "senseshift.h"
+#include <senseshift.h>
 
-#include "senseshift/battery/input/battery_sensor.hpp"
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/ledc.hpp>
+#include <senseshift/battery/input/battery_sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
-#include <senseshift/freertos/input/sensor.hpp>
+#include <senseshift/freertos/task.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Input;
 using namespace SenseShift::Input::Filter;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
-using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::Battery::Input;
 using namespace SenseShift::BH;
diff --git a/firmware/mode_configs/bhaptics/tactosyh.cpp b/firmware/mode_configs/bhaptics/tactosyh.cpp
index 3fd258cc..11bbe0a4 100644
--- a/firmware/mode_configs/bhaptics/tactosyh.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyh.cpp
@@ -4,22 +4,21 @@
 #include <Arduino.h>
 #include <Wire.h>
 
-#include "senseshift.h"
+#include <senseshift.h>
 
-#include "senseshift/battery/input/battery_sensor.hpp"
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/ledc.hpp>
+#include <senseshift/battery/input/battery_sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
-#include <senseshift/freertos/input/sensor.hpp>
+#include <senseshift/freertos/task.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Input;
 using namespace SenseShift::Input::Filter;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
-using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::Battery::Input;
 using namespace SenseShift::BH;
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
index 1f8bd345..ae6011f2 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
@@ -4,22 +4,21 @@
 #include <Arduino.h>
 #include <Wire.h>
 
-#include "senseshift.h"
+#include <senseshift.h>
 
-#include "senseshift/battery/input/battery_sensor.hpp"
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/ledc.hpp>
+#include <senseshift/battery/input/battery_sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
-#include <senseshift/freertos/input/sensor.hpp>
+#include <senseshift/freertos/task.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Input;
 using namespace SenseShift::Input::Filter;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
-using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::Battery::Input;
 using namespace SenseShift::BH;
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
index e3ac3769..b8b05c77 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
@@ -4,22 +4,21 @@
 #include <Arduino.h>
 #include <Wire.h>
 
-#include "senseshift.h"
+#include <senseshift.h>
 
-#include "senseshift/battery/input/battery_sensor.hpp"
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/pca9685.hpp>
+#include <senseshift/battery/input/battery_sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
-#include <senseshift/freertos/input/sensor.hpp>
+#include <senseshift/freertos/task.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Input;
 using namespace SenseShift::Input::Filter;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
-using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::Battery::Input;
 using namespace SenseShift::BH;
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
index 3a88f119..a3d77b06 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
@@ -4,23 +4,22 @@
 #include <Arduino.h>
 #include <Wire.h>
 
-#include "senseshift.h"
+#include <senseshift.h>
 
-#include "senseshift/battery/input/battery_sensor.hpp"
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/ledc.hpp>
 #include <senseshift/arduino/output/pca9685.hpp>
+#include <senseshift/battery/input/battery_sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
-#include <senseshift/freertos/input/sensor.hpp>
+#include <senseshift/freertos/task.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Input;
 using namespace SenseShift::Input::Filter;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
-using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::Battery::Input;
 using namespace SenseShift::BH;
diff --git a/firmware/mode_configs/bhaptics/tactvisor.cpp b/firmware/mode_configs/bhaptics/tactvisor.cpp
index 83b44903..38ece6af 100644
--- a/firmware/mode_configs/bhaptics/tactvisor.cpp
+++ b/firmware/mode_configs/bhaptics/tactvisor.cpp
@@ -4,22 +4,21 @@
 #include <Arduino.h>
 #include <Wire.h>
 
-#include "senseshift.h"
+#include <senseshift.h>
 
-#include "senseshift/battery/input/battery_sensor.hpp"
 #include <senseshift/arduino/input/sensor/analog.hpp>
 #include <senseshift/arduino/output/ledc.hpp>
+#include <senseshift/battery/input/battery_sensor.hpp>
 #include <senseshift/bh/ble/connection.hpp>
 #include <senseshift/bh/devices.hpp>
 #include <senseshift/bh/encoding.hpp>
-#include <senseshift/freertos/input/sensor.hpp>
+#include <senseshift/freertos/task.hpp>
 
 using namespace SenseShift;
 using namespace SenseShift::Input;
 using namespace SenseShift::Input::Filter;
 using namespace SenseShift::Arduino::Output;
 using namespace SenseShift::Arduino::Input;
-using namespace SenseShift::FreeRTOS::Input;
 using namespace SenseShift::Battery;
 using namespace SenseShift::Battery::Input;
 using namespace SenseShift::BH;

From ddff5a42355b7215cd6b5bf99918a44cec2bee4a Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Sun, 18 Feb 2024 18:20:23 +0400
Subject: [PATCH 71/82] style(clang-format): lint-fix

---
 firmware/senseshift.cpp                       |  4 +-
 .../senseshift/arduino/output/analog.hpp      | 10 +-
 .../senseshift/arduino/output/pca9685.hpp     |  4 +-
 .../senseshift/arduino/output/ledc.hpp        | 42 ++++----
 lib/bhaptics/senseshift/bh/devices.hpp        | 43 +++-----
 lib/bhaptics/senseshift/bh/encoding.hpp       | 55 +++++------
 lib/haptics/senseshift/body/haptics/body.cpp  |  4 +-
 lib/haptics/senseshift/body/haptics/body.hpp  | 10 +-
 lib/haptics/senseshift/body/haptics/plane.cpp |  9 +-
 lib/haptics/senseshift/body/haptics/plane.hpp | 13 +--
 lib/io/senseshift/output/output.hpp           |  2 +-
 lib/math/senseshift/math/point2.hpp           |  5 +-
 test/test_bhaptics_encoding/main.cpp          | 97 ++++++++++---------
 test/test_core_helpers/main.cpp               |  2 +-
 14 files changed, 142 insertions(+), 158 deletions(-)

diff --git a/firmware/senseshift.cpp b/firmware/senseshift.cpp
index d6445325..a4f7ab8b 100644
--- a/firmware/senseshift.cpp
+++ b/firmware/senseshift.cpp
@@ -9,11 +9,11 @@
 #include <senseshift/core/logging.hpp>
 
 namespace SenseShift {
-    static const char *const TAG = "application";
+    static const char* const TAG = "application";
 
     Application::Application()
     {
-        this->vibro_body_ = new Body::Haptics::FloatBody ();
+        this->vibro_body_ = new Body::Haptics::FloatBody();
     }
 
     void Application::postEvent(const IEvent* event)
diff --git a/lib/arduino/senseshift/arduino/output/analog.hpp b/lib/arduino/senseshift/arduino/output/analog.hpp
index 7dec32b6..28358ac3 100644
--- a/lib/arduino/senseshift/arduino/output/analog.hpp
+++ b/lib/arduino/senseshift/arduino/output/analog.hpp
@@ -12,14 +12,12 @@ namespace SenseShift::Arduino::Output {
       public:
         static inline constexpr std::uint16_t MAX_INTENSITY = 255;
 
-        explicit AnalogOutput(const std::uint8_t pin) : pin_(pin) {
-        }
+        explicit AnalogOutput(const std::uint8_t pin) : pin_(pin) {}
 
-        void init() override {
-            pinMode(this->pin_, OUTPUT);
-        }
+        void init() override { pinMode(this->pin_, OUTPUT); }
 
-        void writeState(const float value) override {
+        void writeState(const float value) override
+        {
             const auto duty = static_cast<int>(value * MAX_INTENSITY);
             analogWrite(this->pin_, duty);
         }
diff --git a/lib/arduino/senseshift/arduino/output/pca9685.hpp b/lib/arduino/senseshift/arduino/output/pca9685.hpp
index 62ea63cf..1bbffd38 100644
--- a/lib/arduino/senseshift/arduino/output/pca9685.hpp
+++ b/lib/arduino/senseshift/arduino/output/pca9685.hpp
@@ -16,9 +16,7 @@ namespace SenseShift::Arduino::Output {
 
         PCA9685Output(Adafruit_PWMServoDriver* driver, const std::uint8_t num) : driver_(driver), channel_(num){};
 
-        void init() override {
-            this->driver_->begin();
-        }
+        void init() override { this->driver_->begin(); }
 
         void writeState(const ValueType value) override
         {
diff --git a/lib/arduino_esp32/senseshift/arduino/output/ledc.hpp b/lib/arduino_esp32/senseshift/arduino/output/ledc.hpp
index 2d1d810c..6c29efed 100644
--- a/lib/arduino_esp32/senseshift/arduino/output/ledc.hpp
+++ b/lib/arduino_esp32/senseshift/arduino/output/ledc.hpp
@@ -4,49 +4,53 @@
 
 #include <esp32-hal-ledc.h>
 
-#include <senseshift/output/output.hpp>
 #include <senseshift/core/logging.hpp>
+#include <senseshift/output/output.hpp>
 
 namespace SenseShift::Arduino::Output {
-    static const char *const TAG = "output.ledc";
+    static const char* const TAG = "output.ledc";
 
     /// Arduino analog output
     class LedcOutput : public ::SenseShift::Output::IFloatOutput {
-    public:
+      public:
         explicit LedcOutput(
-          const std::uint8_t pin,
-          const std::uint8_t analog_resolution = 12,
-          const std::uint32_t analog_frequency = 60
-        ) : pin_(pin), analog_resolution_(analog_resolution), analog_frequency_(analog_frequency) {}
+          const std::uint8_t pin, const std::uint8_t analog_resolution = 12, const std::uint32_t analog_frequency = 60
+        ) :
+          pin_(pin), analog_resolution_(analog_resolution), analog_frequency_(analog_frequency)
+        {
+        }
 
-        void init() override {
+        void init() override
+        {
             pinMode(this->pin_, OUTPUT);
             this->channel_ = findChannel(this->pin_);
 
             LOG_D(TAG, "GPIO %d - Setting up Channel %d", this->pin_, this->channel_);
-            if(ledcSetup(this->channel_, this->analog_frequency_, this->analog_resolution_) == 0){
-                LOG_E(TAG, "setup failed (freq = %u, resolution = %u). Try setting different resolution or frequency", this->analog_frequency_, this->analog_resolution_);
+            if (ledcSetup(this->channel_, this->analog_frequency_, this->analog_resolution_) == 0) {
+                LOG_E(
+                  TAG,
+                  "setup failed (freq = %u, resolution = %u). Try setting different resolution or frequency",
+                  this->analog_frequency_,
+                  this->analog_resolution_
+                );
                 return;
             }
             ledcAttachPin(this->pin_, this->channel_);
         }
 
-        [[nodiscard]] auto getMaxValue() const -> std::uint32_t {
-            return (1 << analog_resolution_) - 1;
-        }
+        [[nodiscard]] auto getMaxValue() const -> std::uint32_t { return (1 << analog_resolution_) - 1; }
 
-        void writeState(const float value) override {
+        void writeState(const float value) override
+        {
             const auto duty = static_cast<std::uint32_t>(value * this->getMaxValue());
             LOG_V(TAG, "GPIO %d - Writing %d to Channel %d", this->pin_, duty, this->channel_);
             ledcWrite(this->channel_, duty);
         };
 
-    protected:
-        static auto findChannel(std::uint8_t /*pin*/) -> std::int8_t {
-            return CHANNELS++;
-        }
+      protected:
+        static auto findChannel(std::uint8_t /*pin*/) -> std::int8_t { return CHANNELS++; }
 
-    private:
+      private:
         static inline std::uint8_t CHANNELS = 0;
 
         std::uint8_t pin_;
diff --git a/lib/bhaptics/senseshift/bh/devices.hpp b/lib/bhaptics/senseshift/bh/devices.hpp
index b90d627c..5cd2b438 100644
--- a/lib/bhaptics/senseshift/bh/devices.hpp
+++ b/lib/bhaptics/senseshift/bh/devices.hpp
@@ -5,8 +5,8 @@
 
 #include "senseshift/body/hands/hands_interface.hpp"
 
-#include <senseshift/body/haptics/plane.hpp>
 #include <senseshift/body/haptics/body.hpp>
+#include <senseshift/body/haptics/plane.hpp>
 
 #pragma region BH_DEVICE_TACTSUITX40
 
@@ -299,8 +299,12 @@ namespace SenseShift::BH {
 
     // TactGlove Wrist motor position
     static constexpr const Position WRIST_MOTOR_POSITION(127, 191);
-    static constexpr const std::array<OutputLayout, BH_LAYOUT_TACTGLOVE_SIZE> TactGloveLeftLayout = { BH_LAYOUT_TACTGLOVE_LEFT };
-    static constexpr const std::array<OutputLayout, BH_LAYOUT_TACTGLOVE_SIZE> TactGloveRightLayout = { BH_LAYOUT_TACTGLOVE_RIGHT };
+    static constexpr const std::array<OutputLayout, BH_LAYOUT_TACTGLOVE_SIZE> TactGloveLeftLayout = {
+        BH_LAYOUT_TACTGLOVE_LEFT
+    };
+    static constexpr const std::array<OutputLayout, BH_LAYOUT_TACTGLOVE_SIZE> TactGloveRightLayout = {
+        BH_LAYOUT_TACTGLOVE_RIGHT
+    };
 
     inline void addTactGloveActuators(
       FloatBody* hapticBody,
@@ -311,49 +315,32 @@ namespace SenseShift::BH {
       FloatBody::Plane::Actuator* const ring,
       FloatBody::Plane::Actuator* const little,
       FloatBody::Plane::Actuator* const wrist
-    ) {
+    )
+    {
         const auto& layout = (side == HandSide::Left) ? TactGloveLeftLayout : TactGloveRightLayout;
 
         if (thumb != nullptr) {
-            hapticBody->addTarget(
-              std::get<0>(layout[0]),
-              new FloatPlane({ {std::get<1>(layout[0]), thumb } })
-            );
+            hapticBody->addTarget(std::get<0>(layout[0]), new FloatPlane({ { std::get<1>(layout[0]), thumb } }));
         }
 
         if (index != nullptr) {
-            hapticBody->addTarget(
-              std::get<0>(layout[1]),
-              new FloatPlane({ {std::get<1>(layout[1]), index } })
-            );
+            hapticBody->addTarget(std::get<0>(layout[1]), new FloatPlane({ { std::get<1>(layout[1]), index } }));
         }
 
         if (middle != nullptr) {
-            hapticBody->addTarget(
-              std::get<0>(layout[2]),
-              new FloatPlane({ {std::get<1>(layout[2]), middle } })
-            );
+            hapticBody->addTarget(std::get<0>(layout[2]), new FloatPlane({ { std::get<1>(layout[2]), middle } }));
         }
 
         if (ring != nullptr) {
-            hapticBody->addTarget(
-              std::get<0>(layout[3]),
-              new FloatPlane({ {std::get<1>(layout[3]), ring } })
-            );
+            hapticBody->addTarget(std::get<0>(layout[3]), new FloatPlane({ { std::get<1>(layout[3]), ring } }));
         }
 
         if (little != nullptr) {
-            hapticBody->addTarget(
-              std::get<0>(layout[4]),
-              new FloatPlane({ {std::get<1>(layout[4]), little } })
-            );
+            hapticBody->addTarget(std::get<0>(layout[4]), new FloatPlane({ { std::get<1>(layout[4]), little } }));
         }
 
         if (wrist != nullptr) {
-            hapticBody->addTarget(
-              std::get<0>(layout[5]),
-              new FloatPlane({ {std::get<1>(layout[5]), wrist } })
-            );
+            hapticBody->addTarget(std::get<0>(layout[5]), new FloatPlane({ { std::get<1>(layout[5]), wrist } }));
         }
     }
 } // namespace SenseShift::BH
diff --git a/lib/bhaptics/senseshift/bh/encoding.hpp b/lib/bhaptics/senseshift/bh/encoding.hpp
index 1a8b8af4..e23a55f3 100644
--- a/lib/bhaptics/senseshift/bh/encoding.hpp
+++ b/lib/bhaptics/senseshift/bh/encoding.hpp
@@ -7,8 +7,8 @@
 #include <string>
 #include <tuple>
 
-#include <senseshift/body/haptics/interface.hpp>
 #include <senseshift/body/haptics/body.hpp>
+#include <senseshift/body/haptics/interface.hpp>
 
 namespace SenseShift::BH {
     class Decoder {
@@ -30,26 +30,21 @@ namespace SenseShift::BH {
           const std::array<std::uint8_t, N>& value,
           const std::array<OutputLayout, N>& layout,
           const Effect effect
-        ) {
+        )
+        {
             for (size_t i = 0; i < N; i++) {
                 const auto [target, position] = layout[i];
                 const std::uint8_t byte = value[i];
 
-                output->effect(
-                  target,
-                  position,
-                  static_cast<FloatBody::Plane::Value>(effectDataFromByte(byte))
-                );
+                output->effect(target, position, static_cast<FloatBody::Plane::Value>(effectDataFromByte(byte)));
             }
         }
 
         template<size_t N>
         static void applyPlain(
-          FloatBody* output,
-          std::string& value,
-          const std::array<OutputLayout, N>& layout,
-          const Effect effect
-        ) {
+          FloatBody* output, std::string& value, const std::array<OutputLayout, N>& layout, const Effect effect
+        )
+        {
             std::array<std::uint8_t, N> buf{};
             std::size_t copyLength = std::min(value.size(), sizeof(buf));
             std::memcpy(buf.data(), value.c_str(), copyLength);
@@ -67,16 +62,13 @@ namespace SenseShift::BH {
           const std::array<Position, N>& layout,
           const Effect effect,
           const Target target
-        ) {
+        )
+        {
             for (size_t i = 0; i < N; i++) {
                 const auto position = layout[i];
                 const uint8_t byte = value[i];
 
-                output->effect(
-                  target,
-                  position,
-                  static_cast<FloatBody::Plane::Value>(effectDataFromByte(byte))
-                );
+                output->effect(target, position, static_cast<FloatBody::Plane::Value>(effectDataFromByte(byte)));
             }
         }
 
@@ -87,7 +79,8 @@ namespace SenseShift::BH {
           const std::array<Position, N>& layout,
           const Effect effect,
           const Target target
-        ) {
+        )
+        {
             std::array<std::uint8_t, N> buf{};
             std::size_t copyLength = std::min(value.size(), sizeof(buf));
             std::memcpy(buf.data(), value.c_str(), copyLength);
@@ -102,7 +95,8 @@ namespace SenseShift::BH {
           FloatBody* output,
           const std::array<uint8_t, VEST_PAYLOAD_SIZE>& value,
           const std::array<OutputLayout, VEST_LAYOUT_SIZE>& layout
-        ) {
+        )
+        {
             for (size_t i = 0; i < VEST_PAYLOAD_SIZE; i++) {
                 const std::uint8_t byte = value[i];
                 const size_t actIndex = i * 2;
@@ -120,11 +114,9 @@ namespace SenseShift::BH {
             }
         }
 
-        static void applyVest(
-          FloatBody* output,
-          std::string& value,
-          const std::array<OutputLayout, VEST_LAYOUT_SIZE>& layout
-        ) {
+        static void
+          applyVest(FloatBody* output, std::string& value, const std::array<OutputLayout, VEST_LAYOUT_SIZE>& layout)
+        {
             std::array<std::uint8_t, VEST_PAYLOAD_SIZE> buf{};
             const size_t copyLength = std::min(value.size(), sizeof(buf));
             std::memcpy(buf.data(), value.c_str(), copyLength);
@@ -141,7 +133,8 @@ namespace SenseShift::BH {
           const std::array<std::uint8_t, VEST_PAYLOAD_SIZE>& value,
           const std::array<OutputLayout, VEST_LAYOUT_SIZE>& layout,
           const std::array<std::uint8_t, N>& layoutGroups
-        ) {
+        )
+        {
             std::array<std::uint8_t, VEST_LAYOUT_SIZE> result{};
 
             // Unpack values
@@ -182,11 +175,8 @@ namespace SenseShift::BH {
                 const auto target = std::get<0>(layout[i]);
                 const auto position = std::get<1>(layout[i]);
 
-                output->effect(
-                  target,
-                  position,
-                  static_cast<FloatBody::Plane::Value>(effectDataFromByte(result[i], 15))
-                );
+                output
+                  ->effect(target, position, static_cast<FloatBody::Plane::Value>(effectDataFromByte(result[i], 15)));
             }
         }
 
@@ -196,7 +186,8 @@ namespace SenseShift::BH {
           std::string& value,
           const std::array<OutputLayout, VEST_LAYOUT_SIZE>& layout,
           const std::array<std::uint8_t, N>& layoutGroups
-        ) {
+        )
+        {
             std::array<std::uint8_t, VEST_PAYLOAD_SIZE> buf{};
             const size_t copyLength = std::min(value.size(), sizeof(buf));
             std::memcpy(buf.data(), value.c_str(), copyLength);
diff --git a/lib/haptics/senseshift/body/haptics/body.cpp b/lib/haptics/senseshift/body/haptics/body.cpp
index bb373d3e..eac297c1 100644
--- a/lib/haptics/senseshift/body/haptics/body.cpp
+++ b/lib/haptics/senseshift/body/haptics/body.cpp
@@ -1,11 +1,11 @@
 #include "senseshift/body/haptics/body.hpp"
 #include "senseshift/body/haptics/interface.hpp"
 
-#include <senseshift/output/output.hpp>
 #include <senseshift/core/logging.hpp>
+#include <senseshift/output/output.hpp>
 
 namespace SenseShift::Body::Haptics {
-    static const char *const TAG = "haptic.body";
+    static const char* const TAG = "haptic.body";
 
     template<typename Tp, typename Ta>
     void OutputBody<Tp, Ta>::effect(const Target& target, const Position& pos, const typename Plane::Value& val)
diff --git a/lib/haptics/senseshift/body/haptics/body.hpp b/lib/haptics/senseshift/body/haptics/body.hpp
index 6586220a..d381348a 100644
--- a/lib/haptics/senseshift/body/haptics/body.hpp
+++ b/lib/haptics/senseshift/body/haptics/body.hpp
@@ -23,17 +23,17 @@ namespace SenseShift::Body::Haptics {
 
         OutputBody() = default;
 
-        void setup() {
+        void setup()
+        {
             for (auto& [target, plane] : this->targets_) {
                 plane->setup();
             }
         }
 
-        void addTarget(Target target, Plane* plane) {
-            this->targets_[target] = plane;
-        }
+        void addTarget(Target target, Plane* plane) { this->targets_[target] = plane; }
 
-        auto getTarget(Target target) -> std::optional<Plane*> {
+        auto getTarget(Target target) -> std::optional<Plane*>
+        {
             auto find = this->targets_.find(target);
             if (find == this->targets_.end()) {
                 return std::nullopt;
diff --git a/lib/haptics/senseshift/body/haptics/plane.cpp b/lib/haptics/senseshift/body/haptics/plane.cpp
index 9fbe77ee..85fb184b 100644
--- a/lib/haptics/senseshift/body/haptics/plane.cpp
+++ b/lib/haptics/senseshift/body/haptics/plane.cpp
@@ -4,11 +4,11 @@
 #include <algorithm>
 #include <map>
 
-#include <senseshift/output/output.hpp>
 #include <senseshift/core/logging.hpp>
+#include <senseshift/output/output.hpp>
 
 namespace SenseShift::Body::Haptics {
-    static const char *const TAG = "haptic.plane";
+    static const char* const TAG = "haptic.plane";
 
     template<typename Tc, typename To>
     void OutputPlane<Tc, To>::setActuators(const ActuatorMap& actuators)
@@ -58,7 +58,8 @@ namespace SenseShift::Body::Haptics {
     }
 
     template<typename Tc, typename To>
-    [[nodiscard]] auto OutputPlane_Closest<Tc, To>::findClosestPoint(const PositionSet& pts, const Position& target) -> const Position&
+    [[nodiscard]] auto OutputPlane_Closest<Tc, To>::findClosestPoint(const PositionSet& pts, const Position& target)
+      -> const Position&
     {
         // check if exact point exists
         const auto find = pts.find(target);
@@ -69,7 +70,7 @@ namespace SenseShift::Body::Haptics {
         // find the closest point by square distance
         std::multimap<float, Position> distance_map = {};
         for (const auto& point : pts) {
-            distance_map.insert({(target - point), point });
+            distance_map.insert({ (target - point), point });
         }
 
         const auto nearest = std::min_element(distance_map.begin(), distance_map.end());
diff --git a/lib/haptics/senseshift/body/haptics/plane.hpp b/lib/haptics/senseshift/body/haptics/plane.hpp
index a12b3d75..5971ee32 100644
--- a/lib/haptics/senseshift/body/haptics/plane.hpp
+++ b/lib/haptics/senseshift/body/haptics/plane.hpp
@@ -7,9 +7,9 @@
 #include <set>
 #include <vector>
 
+#include <senseshift/math/point2.hpp>
 #include <senseshift/output/output.hpp>
 #include <senseshift/utility.hpp>
-#include <senseshift/math/point2.hpp>
 
 namespace SenseShift::Body::Haptics {
     /// Output "plane" (e.g. Chest, Palm, Finger, etc.).
@@ -59,17 +59,18 @@ namespace SenseShift::Body::Haptics {
     /// \tparam To The type of the output value.
     template<typename Tc, typename To>
     class OutputPlane_Closest : public OutputPlane<Tc, To> {
-    public:
+      public:
         using Value = To;
         using PositionSet = typename OutputPlane<Tc, To>::PositionSet;
 
-        explicit OutputPlane_Closest(const typename OutputPlane<Tc, To>::ActuatorMap& actuators) : OutputPlane<Tc, To>(actuators)
+        explicit OutputPlane_Closest(const typename OutputPlane<Tc, To>::ActuatorMap& actuators) :
+          OutputPlane<Tc, To>(actuators)
         {
         }
 
         void effect(const Position&, const Value&) override;
 
-    private:
+      private:
         [[nodiscard]] static auto findClosestPoint(const PositionSet&, const Position&) -> const Position&;
     };
 
@@ -79,10 +80,10 @@ namespace SenseShift::Body::Haptics {
     // TODO: configurable margin
     class PlaneMapper_Margin {
       public:
-
         /// Maps a 2D matrix into a 1D (coord, object) map.
         template<typename Tp>
-        [[nodiscard]] static constexpr auto mapMatrixCoordinates(std::vector<std::vector<Tp*>> map2d) -> std::map<Position, Tp*>
+        [[nodiscard]] static constexpr auto mapMatrixCoordinates(std::vector<std::vector<Tp*>> map2d)
+          -> std::map<Position, Tp*>
         {
             std::map<Position, Tp*> points{};
 
diff --git a/lib/io/senseshift/output/output.hpp b/lib/io/senseshift/output/output.hpp
index 0392714f..581770d7 100644
--- a/lib/io/senseshift/output/output.hpp
+++ b/lib/io/senseshift/output/output.hpp
@@ -5,7 +5,7 @@
 namespace SenseShift::Output {
     template<typename Tp>
     class IOutput : public IInitializable {
-    public:
+      public:
         using ValueType = Tp;
 
         virtual void writeState(ValueType value) = 0;
diff --git a/lib/math/senseshift/math/point2.hpp b/lib/math/senseshift/math/point2.hpp
index faa2f475..57d3f1e2 100644
--- a/lib/math/senseshift/math/point2.hpp
+++ b/lib/math/senseshift/math/point2.hpp
@@ -25,7 +25,10 @@ namespace SenseShift::Math {
 
         constexpr inline auto operator!=(const Point2<Tp>& rhs) const -> bool { return !(*this == rhs); }
 
-        constexpr auto operator<(const Point2<Tp>& rhs) const -> bool { return std::tie(x, y) < std::tie(rhs.x, rhs.y); }
+        constexpr auto operator<(const Point2<Tp>& rhs) const -> bool
+        {
+            return std::tie(x, y) < std::tie(rhs.x, rhs.y);
+        }
 
         constexpr auto operator-(const Point2<Tp>& rhs) const -> float
         {
diff --git a/test/test_bhaptics_encoding/main.cpp b/test/test_bhaptics_encoding/main.cpp
index 72b70776..0d171a91 100644
--- a/test/test_bhaptics_encoding/main.cpp
+++ b/test/test_bhaptics_encoding/main.cpp
@@ -16,16 +16,17 @@ class TestActuator : public IOutput<float> {
     void writeState(float newIntensity) override { this->intensity = newIntensity; }
 };
 
-#define ASSERT_EQUAL_FLOAT_ROUNDED(expected, actual, precision)                 \
-    TEST_ASSERT_EQUAL_FLOAT(                                                    \
-      std::round(expected * std::pow(10, precision)) / std::pow(10, precision), \
-      std::round(actual * std::pow(10, precision)) / std::pow(10, precision)    \
+#define ASSERT_EQUAL_FLOAT_ROUNDED(expected, actual, precision)                \
+    TEST_ASSERT_EQUAL_FLOAT(                                                   \
+      std::round(expected* std::pow(10, precision)) / std::pow(10, precision), \
+      std::round(actual * std::pow(10, precision)) / std::pow(10, precision)   \
     )
 
 void test_layout_tactsuitx16(void)
 {
     static const std::array<OutputLayout, BH_LAYOUT_TACTSUITX16_SIZE> bhLayout = { BH_LAYOUT_TACTSUITX16 };
-    static const std::array<std::uint8_t,BH_LAYOUT_TACTSUITX16_GROUPS_SIZE> layoutGroups = BH_LAYOUT_TACTSUITX16_GROUPS;
+    static const std::array<std::uint8_t, BH_LAYOUT_TACTSUITX16_GROUPS_SIZE> layoutGroups =
+      BH_LAYOUT_TACTSUITX16_GROUPS;
 
     auto body = new FloatBody();
 
@@ -69,22 +70,22 @@ void test_layout_tactsuitx16(void)
 
     Decoder::applyVestGrouped(body, values, bhLayout, layoutGroups);
     ASSERT_EQUAL_FLOAT_ROUNDED(0, actuator0->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(273.0F/4095.0F, actuator1->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(3276.0F/4095.0F, actuator2->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(3549.0F/4095.0F, actuator3->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(546.0F/4095.0F, actuator4->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(819.0F/4095.0F, actuator5->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(3822.0F/4095.0F, actuator6->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(4095.0F/4095.0F, actuator7->intensity, 2);
-
-    ASSERT_EQUAL_FLOAT_ROUNDED(1092.0F/4095.0F, actuator8->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(1365.0F/4095.0F, actuator9->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(2184.0F/4095.0F, actuator10->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(2457.0F/4095.0F, actuator11->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(1638.0F/4095.0F, actuator12->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(1911.0F/4095.0F, actuator13->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(2730.0F/4095.0F, actuator14->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(3003.0F/4095.0F, actuator15->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(273.0F / 4095.0F, actuator1->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(3276.0F / 4095.0F, actuator2->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(3549.0F / 4095.0F, actuator3->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(546.0F / 4095.0F, actuator4->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(819.0F / 4095.0F, actuator5->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(3822.0F / 4095.0F, actuator6->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(4095.0F / 4095.0F, actuator7->intensity, 2);
+
+    ASSERT_EQUAL_FLOAT_ROUNDED(1092.0F / 4095.0F, actuator8->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1365.0F / 4095.0F, actuator9->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(2184.0F / 4095.0F, actuator10->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(2457.0F / 4095.0F, actuator11->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1638.0F / 4095.0F, actuator12->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1911.0F / 4095.0F, actuator13->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(2730.0F / 4095.0F, actuator14->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(3003.0F / 4095.0F, actuator15->intensity, 2);
 }
 
 void test_layout_tactsuitx40(void)
@@ -127,36 +128,36 @@ void test_layout_tactsuitx40(void)
       bhLayout
     );
     ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(frontMatrix[0][0])->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(273.0F/4095.0F, static_cast<TestActuator*>(frontMatrix[0][1])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(273.0F / 4095.0F, static_cast<TestActuator*>(frontMatrix[0][1])->intensity, 2);
     ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(frontMatrix[0][2])->intensity, 2);
     ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(frontMatrix[0][3])->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(546.0F/4095.0F, static_cast<TestActuator*>(frontMatrix[1][0])->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(819.0F/4095.0F, static_cast<TestActuator*>(frontMatrix[1][1])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(546.0F / 4095.0F, static_cast<TestActuator*>(frontMatrix[1][0])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(819.0F / 4095.0F, static_cast<TestActuator*>(frontMatrix[1][1])->intensity, 2);
     ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(frontMatrix[1][2])->intensity, 2);
     ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(frontMatrix[1][3])->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(1092.0F/4095.0F, static_cast<TestActuator*>(frontMatrix[2][0])->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(1365.0F/4095.0F, static_cast<TestActuator*>(frontMatrix[2][1])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1092.0F / 4095.0F, static_cast<TestActuator*>(frontMatrix[2][0])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1365.0F / 4095.0F, static_cast<TestActuator*>(frontMatrix[2][1])->intensity, 2);
     ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(frontMatrix[2][2])->intensity, 2);
     ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(frontMatrix[2][3])->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(1638.0F/4095.0F, static_cast<TestActuator*>(frontMatrix[3][0])->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(1911.0F/4095.0F, static_cast<TestActuator*>(frontMatrix[3][1])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1638.0F / 4095.0F, static_cast<TestActuator*>(frontMatrix[3][0])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1911.0F / 4095.0F, static_cast<TestActuator*>(frontMatrix[3][1])->intensity, 2);
     ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(frontMatrix[3][2])->intensity, 2);
     ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(frontMatrix[3][3])->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(2184.0F/4095.0F, static_cast<TestActuator*>(frontMatrix[4][0])->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(2457.0F/4095.0F, static_cast<TestActuator*>(frontMatrix[4][1])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(2184.0F / 4095.0F, static_cast<TestActuator*>(frontMatrix[4][0])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(2457.0F / 4095.0F, static_cast<TestActuator*>(frontMatrix[4][1])->intensity, 2);
     ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(frontMatrix[4][2])->intensity, 2);
     ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(frontMatrix[4][3])->intensity, 2);
 
-    ASSERT_EQUAL_FLOAT_ROUNDED(2730.0F/4095.0F, static_cast<TestActuator*>(backMatrix[0][0])->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(3003.0F/4095.0F, static_cast<TestActuator*>(backMatrix[0][1])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(2730.0F / 4095.0F, static_cast<TestActuator*>(backMatrix[0][0])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(3003.0F / 4095.0F, static_cast<TestActuator*>(backMatrix[0][1])->intensity, 2);
     ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(backMatrix[0][2])->intensity, 2);
     ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(backMatrix[0][3])->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(3276.0F/4095.0F, static_cast<TestActuator*>(backMatrix[1][0])->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(3549.0F/4095.0F, static_cast<TestActuator*>(backMatrix[1][1])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(3276.0F / 4095.0F, static_cast<TestActuator*>(backMatrix[1][0])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(3549.0F / 4095.0F, static_cast<TestActuator*>(backMatrix[1][1])->intensity, 2);
     ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(backMatrix[1][2])->intensity, 2);
     ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(backMatrix[1][3])->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(3822.0F/4095.0F, static_cast<TestActuator*>(backMatrix[2][0])->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(4095.0F/4095.0F, static_cast<TestActuator*>(backMatrix[2][1])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(3822.0F / 4095.0F, static_cast<TestActuator*>(backMatrix[2][0])->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(4095.0F / 4095.0F, static_cast<TestActuator*>(backMatrix[2][1])->intensity, 2);
     ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(backMatrix[2][2])->intensity, 2);
     ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(backMatrix[2][3])->intensity, 2);
     ASSERT_EQUAL_FLOAT_ROUNDED(0, static_cast<TestActuator*>(backMatrix[3][0])->intensity, 2);
@@ -199,11 +200,11 @@ void test_layout_tactal(void)
 
     Decoder::applyPlain(body, { 0x10, 0x20, 0x30, 0x40, 0x50, 0x60 }, bhLayout, Effect::Vibro, Target::FaceFront);
     ASSERT_EQUAL_FLOAT_ROUNDED(0.16F, actuator0->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(1310.0F/4095.0F, actuator1->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(1965.0F/4095.0F, actuator2->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(2620.0F/4095.0F, actuator3->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(3276.0F/4095.0F, actuator4->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(3931.0F/4095.0F, actuator5->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1310.0F / 4095.0F, actuator1->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1965.0F / 4095.0F, actuator2->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(2620.0F / 4095.0F, actuator3->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(3276.0F / 4095.0F, actuator4->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(3931.0F / 4095.0F, actuator5->intensity, 2);
 }
 
 void test_layout_tactglove(void)
@@ -230,7 +231,7 @@ void test_layout_tactglove(void)
     );
 
     Decoder::applyPlain(body, { 0x64, 0x00, 0x00, 0x00, 0x00, 0x00 }, bhLayout, Effect::Vibro);
-    ASSERT_EQUAL_FLOAT_ROUNDED(4095.0F/4095.0F, actuatorThumb->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(4095.0F / 4095.0F, actuatorThumb->intensity, 2);
     ASSERT_EQUAL_FLOAT_ROUNDED(0, actuatorIndex->intensity, 2);
     ASSERT_EQUAL_FLOAT_ROUNDED(0, actuatorMiddle->intensity, 2);
     ASSERT_EQUAL_FLOAT_ROUNDED(0, actuatorRing->intensity, 2);
@@ -238,12 +239,12 @@ void test_layout_tactglove(void)
     ASSERT_EQUAL_FLOAT_ROUNDED(0, actuatorWrist->intensity, 2);
 
     Decoder::applyPlain(body, { 0x10, 0x20, 0x30, 0x40, 0x50, 0x60 }, bhLayout, Effect::Vibro);
-    ASSERT_EQUAL_FLOAT_ROUNDED(655.0F/4095.0F, actuatorThumb->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(1310.0F/4095.0F, actuatorIndex->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(1965.0F/4095.0F, actuatorMiddle->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(2620.0F/4095.0F, actuatorRing->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(3276.0F/4095.0F, actuatorLittle->intensity, 2);
-    ASSERT_EQUAL_FLOAT_ROUNDED(3931.0F/4095.0F, actuatorWrist->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(655.0F / 4095.0F, actuatorThumb->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1310.0F / 4095.0F, actuatorIndex->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(1965.0F / 4095.0F, actuatorMiddle->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(2620.0F / 4095.0F, actuatorRing->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(3276.0F / 4095.0F, actuatorLittle->intensity, 2);
+    ASSERT_EQUAL_FLOAT_ROUNDED(3931.0F / 4095.0F, actuatorWrist->intensity, 2);
 }
 
 int process(void)
diff --git a/test/test_core_helpers/main.cpp b/test/test_core_helpers/main.cpp
index d13815bd..5dbfd888 100644
--- a/test/test_core_helpers/main.cpp
+++ b/test/test_core_helpers/main.cpp
@@ -1,8 +1,8 @@
 #include <senseshift/core/helpers.hpp>
 #include <unity.h>
 
-#include <map>
 #include <cstdint>
+#include <map>
 
 using namespace SenseShift;
 

From c2d34c6c05c9a7fb1b51619370b83fb7daa8e405 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Sun, 18 Feb 2024 18:20:23 +0400
Subject: [PATCH 72/82] ci(Wokwi): execute calibration

---
 .wokwi/lucidgloves-prototype4+serial/test.yaml    | 15 +++++++++++++++
 .../senseshift/opengloves/autoconfig.hpp          |  4 ++++
 2 files changed, 19 insertions(+)

diff --git a/.wokwi/lucidgloves-prototype4+serial/test.yaml b/.wokwi/lucidgloves-prototype4+serial/test.yaml
index cfd56eb9..d3a1f656 100644
--- a/.wokwi/lucidgloves-prototype4+serial/test.yaml
+++ b/.wokwi/lucidgloves-prototype4+serial/test.yaml
@@ -3,6 +3,21 @@ version: 1
 author: Leonid Meleshin
 
 steps:
+    - delay: 2000ms
+
+    # Press calibration button
+    - set-control:
+          part-id: btn3
+          control: pressed
+          value: 1
+
+    - delay: 250ms
+
+    - set-control:
+          part-id: btn3
+          control: pressed
+          value: 0
+
     - wait-serial: "A0B0C0D0E0F2047G2047"
 
     # Press the 'A' button
diff --git a/lib/opengloves/senseshift/opengloves/autoconfig.hpp b/lib/opengloves/senseshift/opengloves/autoconfig.hpp
index a2e452b0..dd97a556 100644
--- a/lib/opengloves/senseshift/opengloves/autoconfig.hpp
+++ b/lib/opengloves/senseshift/opengloves/autoconfig.hpp
@@ -174,6 +174,10 @@
 #define FFB_ENABLED \
     (FFB_THUMB_ENABLED || FFB_INDEX_ENABLED || FFB_MIDDLE_ENABLED || FFB_RING_ENABLED || FFB_PINKY_ENABLED)
 
+#ifndef CALIBRATION_ALWAYS_CALIBRATE
+#define CALIBRATION_ALWAYS_CALIBRATE false
+#endif
+
 #ifndef UPDATE_RATE
 #define UPDATE_RATE 90
 #endif

From 6c1bcc2457de7a591037087178240a84fb89e9b1 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Sun, 18 Feb 2024 21:42:06 +0400
Subject: [PATCH 73/82] feat(deps): remove Frozen lib from submodules

---
 .gitmodules    | 3 ---
 lib/frozen     | 1 -
 platformio.ini | 1 +
 3 files changed, 1 insertion(+), 4 deletions(-)
 delete mode 100644 .gitmodules
 delete mode 160000 lib/frozen

diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index fb9d97bc..00000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "lib/frozen"]
-	path = lib/frozen
-	url = https://github.com/senseshift/frozen.git
diff --git a/lib/frozen b/lib/frozen
deleted file mode 160000
index 3a4f00cb..00000000
--- a/lib/frozen
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 3a4f00cb83d580880b8a79864a479e1a14eb8c74
diff --git a/platformio.ini b/platformio.ini
index 4815cf7a..ce93b43b 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -37,6 +37,7 @@ build_src_filter  =
 	-<mode_configs>
 
 lib_deps          =
+	https://github.com/senseshift/frozen.git#feature/platformio
 
 [env]
 build_flags      = ${common.build_flags}

From 2f0b2164da0c01e8920e3b4b982e5c31fdceb93c Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Mon, 19 Feb 2024 15:59:20 +0400
Subject: [PATCH 74/82] refactor(OpenGloves): separate autoconfig for
 tracking/ffb

---
 .../mode_configs/opengloves/opengloves.cpp    | 195 +-----------------
 lib/freertos/senseshift/freertos/task.hpp     |   5 +
 .../senseshift/opengloves/autoconfig.hpp      | 185 ++++++++++++++++-
 3 files changed, 198 insertions(+), 187 deletions(-)

diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp
index 0720afe7..382ad083 100644
--- a/firmware/mode_configs/opengloves/opengloves.cpp
+++ b/firmware/mode_configs/opengloves/opengloves.cpp
@@ -1,172 +1,21 @@
-#include "senseshift/body/hands/input/gesture.hpp"
-#include "senseshift/body/hands/input/total_curl.hpp"
-#include <senseshift/arduino/input/sensor/analog.hpp>
-#include <senseshift/arduino/input/sensor/digital.hpp>
-#include <senseshift/arduino/output/servo.hpp>
-#include <senseshift/body/hands/input/gesture.hpp>
-#include <senseshift/input/calibration.hpp>
-#include <senseshift/input/filter.hpp>
-#include <senseshift/input/sensor.hpp>
+#include <senseshift/freertos/task.hpp>
 #include <senseshift/opengloves/autoconfig.hpp>
-#include <senseshift/opengloves/opengloves.hpp>
 #include <senseshift/opengloves/opengloves_task.hpp>
-#include <senseshift/utility.hpp>
 
-using namespace ::SenseShift::Input;
-using namespace ::SenseShift::Arduino::Input;
-using namespace ::SenseShift::Arduino::Output;
-using namespace ::SenseShift::Body::Hands::Input;
-using namespace ::SenseShift::OpenGloves;
-
-InputSensors input_sensors;
-
-#ifdef FFB_ENABLED
-OutputWriters output_writers;
-#endif
+using namespace SenseShift::OpenGloves;
 
 void setupMode()
 {
-#if FINGER_THUMB_SPLAY
-    DEFINE_FINGER_SPLAY(
-      thumb,
-      PIN_FINGER_THUMB,
-      FINGER_THUMB_INVERT,
-      CALIBRATION_CURL,
-      PIN_FINGER_THUMB_SPLAY,
-      FINGER_THUMB_SPLAY_INVERT,
-      CALIBRATION_SPLAY
-    );
-#elif FINGER_THUMB_ENABLED
-    DEFINE_FINGER(thumb_curl, PIN_FINGER_THUMB, FINGER_THUMB_INVERT, CALIBRATION_CURL);
-    input_sensors.curl.thumb.curl_total = thumb_curl_sensor;
-#endif
-
-#if FINGER_INDEX_SPLAY
-    DEFINE_FINGER_SPLAY(
-      index,
-      PIN_FINGER_INDEX,
-      FINGER_INDEX_INVERT,
-      CALIBRATION_CURL,
-      PIN_FINGER_INDEX_SPLAY,
-      FINGER_INDEX_SPLAY_INVERT,
-      CALIBRATION_SPLAY
-    );
-#elif FINGER_INDEX_ENABLED
-    DEFINE_FINGER(index_curl, PIN_FINGER_INDEX, FINGER_INDEX_INVERT, CALIBRATION_CURL);
-    input_sensors.curl.index.curl_total = index_curl_sensor;
-#endif
-
-#if FINGER_MIDDLE_SPLAY
-    DEFINE_FINGER_SPLAY(
-      middle,
-      PIN_FINGER_MIDDLE,
-      FINGER_MIDDLE_INVERT,
-      CALIBRATION_CURL,
-      PIN_FINGER_MIDDLE_SPLAY,
-      FINGER_MIDDLE_SPLAY_INVERT,
-      CALIBRATION_SPLAY
-    );
-#elif FINGER_MIDDLE_ENABLED
-    DEFINE_FINGER(middle_curl, PIN_FINGER_MIDDLE, FINGER_MIDDLE_INVERT, CALIBRATION_CURL);
-    input_sensors.curl.middle.curl_total = middle_curl_sensor;
-#endif
-
-#if FINGER_RING_SPLAY
-    DEFINE_FINGER_SPLAY(
-      ring,
-      PIN_FINGER_RING,
-      FINGER_RING_INVERT,
-      CALIBRATION_CURL,
-      PIN_FINGER_RING_SPLAY,
-      FINGER_RING_SPLAY_INVERT,
-      CALIBRATION_SPLAY
-    );
-#elif FINGER_RING_ENABLED
-    DEFINE_FINGER(ring_curl, PIN_FINGER_RING, FINGER_RING_INVERT, CALIBRATION_CURL);
-    input_sensors.curl.ring.curl_total = ring_curl_sensor;
-#endif
-
-#if FINGER_PINKY_SPLAY
-    DEFINE_FINGER_SPLAY(
-      pinky,
-      PIN_FINGER_PINKY,
-      FINGER_PINKY_INVERT,
-      CALIBRATION_CURL,
-      PIN_FINGER_PINKY_SPLAY,
-      FINGER_PINKY_SPLAY_INVERT,
-      CALIBRATION_SPLAY
-    );
-#elif FINGER_PINKY_ENABLED
-    DEFINE_FINGER(pinky_curl, PIN_FINGER_PINKY, FINGER_PINKY_INVERT, CALIBRATION_CURL);
-    input_sensors.curl.pinky.curl_total = pinky_curl_sensor;
-#endif
-
-#if JOYSTICK_ENABLED
-    DEFINE_JOYSTICK_AXIS(joystick_x, PIN_JOYSTICK_X, JOYSTICK_X_INVERT, JOYSTICK_DEADZONE);
-    DEFINE_JOYSTICK_AXIS(joystick_y, PIN_JOYSTICK_Y, JOYSTICK_Y_INVERT, JOYSTICK_DEADZONE);
-
-    input_sensors.joystick.x = joystick_x_sensor;
-    input_sensors.joystick.y = joystick_y_sensor;
-#endif
-
-#if BUTTON_A_ENABLED
-    auto* button_a = new BUTTON_CLASS(PIN_BUTTON_A, BUTTON_A_INVERT);
-    input_sensors.button_a.press = button_a;
-#endif
-#if BUTTON_B_ENABLED
-    auto* button_b = new BUTTON_CLASS(PIN_BUTTON_B, BUTTON_B_INVERT);
-    input_sensors.button_b.press = button_b;
-#endif
-#if BUTTON_JOYSTICK_ENABLED
-    auto* button_joystick = new BUTTON_CLASS(PIN_BUTTON_JOYSTICK, BUTTON_JOYSTICK_INVERT);
-    input_sensors.joystick.press = button_joystick;
-#endif
-#if BUTTON_MENU_ENABLED
-    auto* button_menu = new BUTTON_CLASS(PIN_BUTTON_MENU, BUTTON_MENU_INVERT);
-#endif
-#if BUTTON_CALIBRATE_ENABLED
-    auto* button_calibrate = new BUTTON_CLASS(PIN_BUTTON_CALIBRATE, BUTTON_CALIBRATE_INVERT);
-    input_sensors.button_calibrate.press = button_calibrate;
-#endif
-
-#if GESTURE_TRIGGER_ENABLED
-    auto* trigger = new TriggerGesture(index_curl_sensor, GESTURE_TRIGGER_THRESHOLD);
-    input_sensors.trigger.press = trigger;
-#elif BUTTON_TRIGGER_ENABLED
-    auto trigger = new BUTTON_CLASS(PIN_BUTTON_TRIGGER, BUTTON_TRIGGER_INVERT);
-#endif
-
-#if GESTURE_GRAB_ENABLED
-    auto* grab = new GrabGesture(
-      GrabGesture::Fingers{ .index = index_curl_sensor,
-                            .middle = middle_curl_sensor,
-                            .ring = ring_curl_sensor,
-                            .pinky = pinky_curl_sensor },
-      GESTURE_GRAB_THRESHOLD
-    );
-    input_sensors.grab.press = grab;
-#elif BUTTON_GRAB_ENABLED
-    auto* grab = new BUTTON_CLASS(PIN_BUTTON_GRAB, BUTTON_GRAB_INVERT);
-#endif
+    auto* communication = AutoConfig::createTransport();
+    auto* encoding = new og::AlphaEncoder();
 
-#if GESTURE_PINCH_ENABLED
-    auto* pinch = new PinchGesture(
-      PinchGesture::Fingers{ .thumb = thumb_curl_sensor, .index = index_curl_sensor },
-      GESTURE_PINCH_THRESHOLD
-    );
-    input_sensors.pinch.press = pinch;
-#elif BUTTON_PINCH_ENABLED
-    auto* pinch = new BUTTON_CLASS(PIN_BUTTON_PINCH, BUTTON_PINCH_INVERT);
-#endif
+    auto input_sensors = AutoConfig::createInput();
 
-    auto* communication = AutoConfig::setupTransport();
-    auto* encoding = new og::AlphaEncoder();
     OpenGlovesTrackingComponent::Config tracking_config(CALIBRATION_DURATION, CALIBRATION_ALWAYS_CALIBRATE);
-    auto* opengloves_tracking =
-      new OpenGlovesTrackingComponent(tracking_config, input_sensors, communication, encoding);
+    auto* og_tracking = new OpenGlovesTrackingComponent(tracking_config, input_sensors, communication, encoding);
 
-    auto* opengloves_tracking_task = new ::SenseShift::FreeRTOS::ComponentUpdateTask<OpenGlovesTrackingComponent>(
-      opengloves_tracking,
+    auto* og_tracking_task = new SenseShift::FreeRTOS::ComponentUpdateTask<OpenGlovesTrackingComponent>(
+      og_tracking,
       1000 / UPDATE_RATE,
       {
         .name = "OG_TRACKING",
@@ -174,34 +23,10 @@ void setupMode()
         .priority = 1,
       }
     );
-    opengloves_tracking_task->begin();
+    og_tracking_task->begin();
 
 #if FFB_ENABLED
-
-#if FFB_THUMB_ENABLED
-    auto* thumb_ffb_output = new ServoOutput(PIN_FFB_THUMB);
-    output_writers.ffb.thumb = thumb_ffb_output;
-#endif
-
-#if FFB_INDEX_ENABLED
-    auto* index_ffb_output = new ServoOutput(PIN_FFB_INDEX);
-    output_writers.ffb.index = index_ffb_output;
-#endif
-
-#if FFB_MIDDLE_ENABLED
-    auto* middle_ffb_output = new ServoOutput(PIN_FFB_MIDDLE);
-    output_writers.ffb.middle = middle_ffb_output;
-#endif
-
-#if FFB_RING_ENABLED
-    auto* ring_ffb_output = new ServoOutput(PIN_FFB_RING);
-    output_writers.ffb.ring = ring_ffb_output;
-#endif
-
-#if FFB_PINKY_ENABLED
-    auto* pinky_ffb_output = new ServoOutput(PIN_FFB_PINKY);
-    output_writers.ffb.pinky = pinky_ffb_output;
-#endif
+    auto output_writers = AutoConfig::createFfbOutputs();
 
     auto* og_ffb = new OpenGlovesForceFeedbackComponent(output_writers, communication, encoding);
 
diff --git a/lib/freertos/senseshift/freertos/task.hpp b/lib/freertos/senseshift/freertos/task.hpp
index 38ea81a0..ee4f3028 100644
--- a/lib/freertos/senseshift/freertos/task.hpp
+++ b/lib/freertos/senseshift/freertos/task.hpp
@@ -1,10 +1,15 @@
 #pragma once
 
+#include <cstdint>
+#include <type_traits>
+
+#include <senseshift/core/component.hpp>
 #include <senseshift/core/logging.hpp>
 
 extern "C" void delay(uint32_t ms);
 
 #if defined(ESP32)
+#include <Arduino.h>
 #include <freertos/FreeRTOS.h> // Include the base FreeRTOS definitions.
 #include <freertos/task.h>     // Include the task definitions.
 
diff --git a/lib/opengloves/senseshift/opengloves/autoconfig.hpp b/lib/opengloves/senseshift/opengloves/autoconfig.hpp
index dd97a556..de6eeaf2 100644
--- a/lib/opengloves/senseshift/opengloves/autoconfig.hpp
+++ b/lib/opengloves/senseshift/opengloves/autoconfig.hpp
@@ -1,5 +1,13 @@
 #pragma once
 
+#include <senseshift/arduino/input/sensor/analog.hpp>
+#include <senseshift/arduino/input/sensor/digital.hpp>
+#include <senseshift/arduino/output/servo.hpp>
+#include <senseshift/body/hands/input/gesture.hpp>
+#include <senseshift/body/hands/input/total_curl.hpp>
+#include <senseshift/input/calibration.hpp>
+#include <senseshift/input/filter.hpp>
+#include <senseshift/input/sensor.hpp>
 #include <senseshift/opengloves/constants.hpp>
 #include <senseshift/opengloves/opengloves.hpp>
 
@@ -145,7 +153,8 @@
 #define BUTTON_GRAB_ENABLED (!GESTURE_GRAB_ENABLED && defined(PIN_BUTTON_GRAB) && (PIN_BUTTON_GRAB != -1))
 #define BUTTON_PINCH_ENABLED (!GESTURE_PINCH_ENABLED && defined(PIN_BUTTON_PINCH) && (PIN_BUTTON_PINCH != -1))
 
-#define BUTTON_CLASS(PIN, INVERT) SimpleSensorDecorator(new DigitalSimpleSensor<INVERT>(PIN));
+#define BUTTON_CLASS(PIN, INVERT) \
+    ::SenseShift::Input::SimpleSensorDecorator(new ::SenseShift::Arduino::Input::DigitalSimpleSensor<INVERT>(PIN));
 
 #pragma endregion
 
@@ -184,10 +193,182 @@
 
 namespace SenseShift::OpenGloves::AutoConfig {
 
+    [[nodiscard]] auto createInput() -> InputSensors
+    {
+        InputSensors input_sensors;
+
+#if FINGER_THUMB_SPLAY
+        DEFINE_FINGER_SPLAY(
+          thumb,
+          PIN_FINGER_THUMB,
+          FINGER_THUMB_INVERT,
+          CALIBRATION_CURL,
+          PIN_FINGER_THUMB_SPLAY,
+          FINGER_THUMB_SPLAY_INVERT,
+          CALIBRATION_SPLAY
+        );
+#elif FINGER_THUMB_ENABLED
+        DEFINE_FINGER(thumb_curl, PIN_FINGER_THUMB, FINGER_THUMB_INVERT, CALIBRATION_CURL);
+        input_sensors.curl.thumb.curl_total = thumb_curl_sensor;
+#endif
+
+#if FINGER_INDEX_SPLAY
+        DEFINE_FINGER_SPLAY(
+          index,
+          PIN_FINGER_INDEX,
+          FINGER_INDEX_INVERT,
+          CALIBRATION_CURL,
+          PIN_FINGER_INDEX_SPLAY,
+          FINGER_INDEX_SPLAY_INVERT,
+          CALIBRATION_SPLAY
+        );
+#elif FINGER_INDEX_ENABLED
+        DEFINE_FINGER(index_curl, PIN_FINGER_INDEX, FINGER_INDEX_INVERT, CALIBRATION_CURL);
+        input_sensors.curl.index.curl_total = index_curl_sensor;
+#endif
+
+#if FINGER_MIDDLE_SPLAY
+        DEFINE_FINGER_SPLAY(
+          middle,
+          PIN_FINGER_MIDDLE,
+          FINGER_MIDDLE_INVERT,
+          CALIBRATION_CURL,
+          PIN_FINGER_MIDDLE_SPLAY,
+          FINGER_MIDDLE_SPLAY_INVERT,
+          CALIBRATION_SPLAY
+        );
+#elif FINGER_MIDDLE_ENABLED
+        DEFINE_FINGER(middle_curl, PIN_FINGER_MIDDLE, FINGER_MIDDLE_INVERT, CALIBRATION_CURL);
+        input_sensors.curl.middle.curl_total = middle_curl_sensor;
+#endif
+
+#if FINGER_RING_SPLAY
+        DEFINE_FINGER_SPLAY(
+          ring,
+          PIN_FINGER_RING,
+          FINGER_RING_INVERT,
+          CALIBRATION_CURL,
+          PIN_FINGER_RING_SPLAY,
+          FINGER_RING_SPLAY_INVERT,
+          CALIBRATION_SPLAY
+        );
+#elif FINGER_RING_ENABLED
+        DEFINE_FINGER(ring_curl, PIN_FINGER_RING, FINGER_RING_INVERT, CALIBRATION_CURL);
+        input_sensors.curl.ring.curl_total = ring_curl_sensor;
+#endif
+
+#if FINGER_PINKY_SPLAY
+        DEFINE_FINGER_SPLAY(
+          pinky,
+          PIN_FINGER_PINKY,
+          FINGER_PINKY_INVERT,
+          CALIBRATION_CURL,
+          PIN_FINGER_PINKY_SPLAY,
+          FINGER_PINKY_SPLAY_INVERT,
+          CALIBRATION_SPLAY
+        );
+#elif FINGER_PINKY_ENABLED
+        DEFINE_FINGER(pinky_curl, PIN_FINGER_PINKY, FINGER_PINKY_INVERT, CALIBRATION_CURL);
+        input_sensors.curl.pinky.curl_total = pinky_curl_sensor;
+#endif
+
+#if JOYSTICK_ENABLED
+        DEFINE_JOYSTICK_AXIS(joystick_x, PIN_JOYSTICK_X, JOYSTICK_X_INVERT, JOYSTICK_DEADZONE);
+        DEFINE_JOYSTICK_AXIS(joystick_y, PIN_JOYSTICK_Y, JOYSTICK_Y_INVERT, JOYSTICK_DEADZONE);
+
+        input_sensors.joystick.x = joystick_x_sensor;
+        input_sensors.joystick.y = joystick_y_sensor;
+#endif
+
+#if BUTTON_A_ENABLED
+        auto* button_a = new BUTTON_CLASS(PIN_BUTTON_A, BUTTON_A_INVERT);
+        input_sensors.button_a.press = button_a;
+#endif
+#if BUTTON_B_ENABLED
+        auto* button_b = new BUTTON_CLASS(PIN_BUTTON_B, BUTTON_B_INVERT);
+        input_sensors.button_b.press = button_b;
+#endif
+#if BUTTON_JOYSTICK_ENABLED
+        auto* button_joystick = new BUTTON_CLASS(PIN_BUTTON_JOYSTICK, BUTTON_JOYSTICK_INVERT);
+        input_sensors.joystick.press = button_joystick;
+#endif
+#if BUTTON_MENU_ENABLED
+        auto* button_menu = new BUTTON_CLASS(PIN_BUTTON_MENU, BUTTON_MENU_INVERT);
+#endif
+#if BUTTON_CALIBRATE_ENABLED
+        auto* button_calibrate = new BUTTON_CLASS(PIN_BUTTON_CALIBRATE, BUTTON_CALIBRATE_INVERT);
+        input_sensors.button_calibrate.press = button_calibrate;
+#endif
+
+#if GESTURE_TRIGGER_ENABLED
+        auto* trigger = new Body::Hands::Input::TriggerGesture(index_curl_sensor, GESTURE_TRIGGER_THRESHOLD);
+        input_sensors.trigger.press = trigger;
+#elif BUTTON_TRIGGER_ENABLED
+        auto trigger = new BUTTON_CLASS(PIN_BUTTON_TRIGGER, BUTTON_TRIGGER_INVERT);
+#endif
+
+#if GESTURE_GRAB_ENABLED
+        auto* grab = new Body::Hands::Input::GrabGesture(
+          Body::Hands::Input::GrabGesture::Fingers{ .index = index_curl_sensor,
+                                                    .middle = middle_curl_sensor,
+                                                    .ring = ring_curl_sensor,
+                                                    .pinky = pinky_curl_sensor },
+          GESTURE_GRAB_THRESHOLD
+        );
+        input_sensors.grab.press = grab;
+#elif BUTTON_GRAB_ENABLED
+        auto* grab = new BUTTON_CLASS(PIN_BUTTON_GRAB, BUTTON_GRAB_INVERT);
+#endif
+
+#if GESTURE_PINCH_ENABLED
+        auto* pinch = new Body::Hands::Input::PinchGesture(
+          Body::Hands::Input::PinchGesture::Fingers{ .thumb = thumb_curl_sensor, .index = index_curl_sensor },
+          GESTURE_PINCH_THRESHOLD
+        );
+        input_sensors.pinch.press = pinch;
+#elif BUTTON_PINCH_ENABLED
+        auto* pinch = new BUTTON_CLASS(PIN_BUTTON_PINCH, BUTTON_PINCH_INVERT);
+#endif
+
+        return input_sensors;
+    }
+
+    [[nodiscard]] auto createFfbOutputs() -> OutputWriters
+    {
+        OutputWriters output_writers;
+
+#if FFB_THUMB_ENABLED
+        auto* thumb_ffb_output = new ::SenseShift::Arduino::Output::ServoOutput(PIN_FFB_THUMB);
+        output_writers.ffb.thumb = thumb_ffb_output;
+#endif
+
+#if FFB_INDEX_ENABLED
+        auto* index_ffb_output = new ::SenseShift::Arduino::Output::ServoOutput(PIN_FFB_INDEX);
+        output_writers.ffb.index = index_ffb_output;
+#endif
+
+#if FFB_MIDDLE_ENABLED
+        auto* middle_ffb_output = new ::SenseShift::Arduino::Output::ServoOutput(PIN_FFB_MIDDLE);
+        output_writers.ffb.middle = middle_ffb_output;
+#endif
+
+#if FFB_RING_ENABLED
+        auto* ring_ffb_output = new ::SenseShift::Arduino::Output::ServoOutput(PIN_FFB_RING);
+        output_writers.ffb.ring = ring_ffb_output;
+#endif
+
+#if FFB_PINKY_ENABLED
+        auto* pinky_ffb_output = new ::SenseShift::Arduino::Output::ServoOutput(PIN_FFB_PINKY);
+        output_writers.ffb.pinky = pinky_ffb_output;
+#endif
+
+        return output_writers;
+    }
+
     /**
      * Setup the transport for the OpenGloves interface.
      */
-    [[nodiscard]] auto setupTransport() -> ITransport*
+    [[nodiscard]] auto createTransport() -> ITransport*
     {
 #if OPENGLOVES_COMMUNICATION == OPENGLOVES_COMM_SERIAL // Serial
         auto* pSerial = &SERIAL_PORT;

From 0628ff691aeec648fe58b7940a4a4b581460ef4f Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Mon, 19 Feb 2024 16:36:45 +0400
Subject: [PATCH 75/82] style: format INI files

---
 ini/bhaptics.ini               | 415 ++++++++++++++++++---------------
 ini/opengloves-indexer.ini     | 306 ++++++++++++------------
 ini/opengloves-lucidgloves.ini | 203 ++++++++--------
 ini/opengloves.ini             | 116 ++++-----
 platformio.ini                 |  71 +++---
 5 files changed, 581 insertions(+), 530 deletions(-)

diff --git a/ini/bhaptics.ini b/ini/bhaptics.ini
index f3289e11..0d3c4343 100644
--- a/ini/bhaptics.ini
+++ b/ini/bhaptics.ini
@@ -1,71 +1,78 @@
 [bhaptics]
-platform = platformio/espressif32@^6.1.0
+platform          = platformio/espressif32@^6.1.0
 platform_packages =
-	platformio/framework-arduinoespressif32@^3.20014.231204
-framework = arduino
-board = esp32doit-devkit-v1
-upload_speed = 921600
-monitor_speed = 115200
+    platformio/framework-arduinoespressif32@^3.20014.231204
+framework         = arduino
+board             = esp32doit-devkit-v1
+upload_speed      = 921600
+monitor_speed     = 115200
 
-build_flags = ${common.build_flags}
-			  -D BHAPTICS
-build_unflags = ${common.build_unflags}
+build_flags      =
+    ${common.build_flags}
+    -D BHAPTICS
+build_unflags    = ${common.build_unflags}
 build_src_filter = ${common.build_src_filter}
-lib_deps = ${common.lib_deps}
+lib_deps         = ${common.lib_deps}
 
 [env:bhaptics_tactsuit_x16]
-platform = ${bhaptics.platform}
+platform          = ${bhaptics.platform}
 platform_packages = ${bhaptics.platform_packages}
-framework = ${bhaptics.framework}
-board = ${bhaptics.board}
-upload_speed = ${bhaptics.upload_speed}
-monitor_speed = ${bhaptics.monitor_speed}
+framework         = ${bhaptics.framework}
+board             = ${bhaptics.board}
+upload_speed      = ${bhaptics.upload_speed}
+monitor_speed     = ${bhaptics.monitor_speed}
 
-build_flags = ${bhaptics.build_flags}
-			  -D BH_DEVICE_TACTSUITX16
-			  -D BH_BLE_APPEARANCE=510
-			  '-D BLUETOOTH_NAME="TactSuitX16"'
-			  '-D BH_SERIAL_NUMBER={ 0x0d, 0x3a, 0xeb, 0x77, 0xbe, 0xf8, 0x7a, 0x1e, 0x3b, 0x2a }'
-build_unflags = ${bhaptics.build_unflags}
-build_src_filter = ${bhaptics.build_src_filter}
-				   +<mode_configs/bhaptics/tactsuit_x16.cpp>
-lib_deps = ${bhaptics.lib_deps}
+build_flags      =
+    ${bhaptics.build_flags}
+    -D BH_DEVICE_TACTSUITX16
+    -D BH_BLE_APPEARANCE=510
+    '-D BLUETOOTH_NAME="TactSuitX16"'
+    '-D BH_SERIAL_NUMBER={ 0x0d, 0x3a, 0xeb, 0x77, 0xbe, 0xf8, 0x7a, 0x1e, 0x3b, 0x2a }'
+build_unflags    = ${bhaptics.build_unflags}
+build_src_filter =
+    ${bhaptics.build_src_filter}
+    +<mode_configs/bhaptics/tactsuit_x16.cpp>
+lib_deps         = ${bhaptics.lib_deps}
 
 [env:bhaptics_tactsuit_x16_pca9685]
-platform = ${bhaptics.platform}
+platform          = ${bhaptics.platform}
 platform_packages = ${bhaptics.platform_packages}
-framework = ${bhaptics.framework}
-board = ${bhaptics.board}
-upload_speed = ${bhaptics.upload_speed}
-monitor_speed = ${bhaptics.monitor_speed}
+framework         = ${bhaptics.framework}
+board             = ${bhaptics.board}
+upload_speed      = ${bhaptics.upload_speed}
+monitor_speed     = ${bhaptics.monitor_speed}
 
-build_flags = ${bhaptics.build_flags}
-			  -D BH_DEVICE_TACTSUITX16
-			  -D BH_BLE_APPEARANCE=510
-			  '-D BLUETOOTH_NAME="TactSuitX16"'
-			  '-D BH_SERIAL_NUMBER={ 0x0d, 0x3a, 0xeb, 0x77, 0xbe, 0xf8, 0x7a, 0x1e, 0x3b, 0x2a }'
-build_unflags = ${bhaptics.build_unflags}
-build_src_filter = ${bhaptics.build_src_filter}
-				   +<mode_configs/bhaptics/tactsuit_x16_pca9685.cpp>
-lib_deps = ${bhaptics.lib_deps}
+build_flags      =
+    ${bhaptics.build_flags}
+    -D BH_DEVICE_TACTSUITX16
+    -D BH_BLE_APPEARANCE=510
+    '-D BLUETOOTH_NAME="TactSuitX16"'
+    '-D BH_SERIAL_NUMBER={ 0x0d, 0x3a, 0xeb, 0x77, 0xbe, 0xf8, 0x7a, 0x1e, 0x3b, 0x2a }'
+build_unflags    = ${bhaptics.build_unflags}
+build_src_filter =
+    ${bhaptics.build_src_filter}
+    +<mode_configs/bhaptics/tactsuit_x16_pca9685.cpp>
+lib_deps         = ${bhaptics.lib_deps}
 
 [env:bhaptics_tactsuit_x40]
-platform = ${bhaptics.platform}
+platform          = ${bhaptics.platform}
 platform_packages = ${bhaptics.platform_packages}
-framework = ${bhaptics.framework}
-board = ${bhaptics.board}
-upload_speed = ${bhaptics.upload_speed}
-monitor_speed = ${bhaptics.monitor_speed}
+framework         = ${bhaptics.framework}
+board             = ${bhaptics.board}
+upload_speed      = ${bhaptics.upload_speed}
+monitor_speed     = ${bhaptics.monitor_speed}
 
-build_flags = ${bhaptics.build_flags}
-			  -D BH_DEVICE_TACTSUITX40
-			  -D BH_BLE_APPEARANCE=509
-			  '-D BLUETOOTH_NAME="TactSuitX40"'
-			  '-D BH_SERIAL_NUMBER={ 0xcf, 0xcb, 0x0d, 0x95, 0x5f, 0xf6, 0xee, 0x2c, 0xbd, 0x73 }'
-build_unflags = ${bhaptics.build_unflags}
-build_src_filter = ${bhaptics.build_src_filter}
-				   +<mode_configs/bhaptics/tactsuit_x40.cpp>
-lib_deps = ${bhaptics.lib_deps}
+build_flags      =
+    ${bhaptics.build_flags}
+    -D BH_DEVICE_TACTSUITX40
+    -D BH_BLE_APPEARANCE=509
+    '-D BLUETOOTH_NAME="TactSuitX40"'
+    '-D BH_SERIAL_NUMBER={ 0xcf, 0xcb, 0x0d, 0x95, 0x5f, 0xf6, 0xee, 0x2c, 0xbd, 0x73 }'
+build_unflags    = ${bhaptics.build_unflags}
+build_src_filter =
+    ${bhaptics.build_src_filter}
+    +<mode_configs/bhaptics/tactsuit_x40.cpp>
+lib_deps         = ${bhaptics.lib_deps}
 
 ; [env:bhaptics_tactbelt]
 ; platform			= ${bhaptics.platform}
@@ -86,183 +93,203 @@ lib_deps = ${bhaptics.lib_deps}
 ; lib_deps    		= ${bhaptics.lib_deps}
 
 [env:bhaptics_tactosy2_forearm_left]
-platform = ${bhaptics.platform}
+platform          = ${bhaptics.platform}
 platform_packages = ${bhaptics.platform_packages}
-framework = ${bhaptics.framework}
-board = ${bhaptics.board}
-upload_speed = ${bhaptics.upload_speed}
-monitor_speed = ${bhaptics.monitor_speed}
+framework         = ${bhaptics.framework}
+board             = ${bhaptics.board}
+upload_speed      = ${bhaptics.upload_speed}
+monitor_speed     = ${bhaptics.monitor_speed}
 
-build_flags = ${bhaptics.build_flags}
-			  -D BH_DEVICE_TACTOSY2
-			  -D BH_BLE_APPEARANCE=508
-			  '-D BLUETOOTH_NAME="Tactosy2_L"'
-			  '-D BH_SERIAL_NUMBER={ 0xa0, 0xba, 0x0a, 0xd1, 0xbf, 0x36, 0x11, 0x30, 0xa4, 0xff }'
-build_unflags = ${bhaptics.build_unflags}
-build_src_filter = ${bhaptics.build_src_filter}
-				   +<mode_configs/bhaptics/tactosy2.cpp>
-lib_deps = ${bhaptics.lib_deps}
+build_flags      =
+    ${bhaptics.build_flags}
+    -D BH_DEVICE_TACTOSY2
+    -D BH_BLE_APPEARANCE=508
+    '-D BLUETOOTH_NAME="Tactosy2_L"'
+    '-D BH_SERIAL_NUMBER={ 0xa0, 0xba, 0x0a, 0xd1, 0xbf, 0x36, 0x11, 0x30, 0xa4, 0xff }'
+build_unflags    = ${bhaptics.build_unflags}
+build_src_filter =
+    ${bhaptics.build_src_filter}
+    +<mode_configs/bhaptics/tactosy2.cpp>
+lib_deps         = ${bhaptics.lib_deps}
 
 [env:bhaptics_tactosy2_forearm_right]
-platform = ${bhaptics.platform}
+platform          = ${bhaptics.platform}
 platform_packages = ${bhaptics.platform_packages}
-framework = ${bhaptics.framework}
-board = ${bhaptics.board}
-upload_speed = ${bhaptics.upload_speed}
-monitor_speed = ${bhaptics.monitor_speed}
+framework         = ${bhaptics.framework}
+board             = ${bhaptics.board}
+upload_speed      = ${bhaptics.upload_speed}
+monitor_speed     = ${bhaptics.monitor_speed}
 
-build_flags = ${bhaptics.build_flags}
-			  -D BH_DEVICE_TACTOSY2
-			  -D BH_BLE_APPEARANCE=508
-			  '-D BLUETOOTH_NAME="Tactosy2_R"'
-			  '-D BH_SERIAL_NUMBER={ 0xb0, 0x1c, 0xc1, 0xf8, 0xec, 0x12, 0x18, 0x4e, 0x09, 0x77 }'
-build_unflags = ${bhaptics.build_unflags}
-build_src_filter = ${bhaptics.build_src_filter}
-				   +<mode_configs/bhaptics/tactosy2.cpp>
-lib_deps = ${bhaptics.lib_deps}
+build_flags      =
+    ${bhaptics.build_flags}
+    -D BH_DEVICE_TACTOSY2
+    -D BH_BLE_APPEARANCE=508
+    '-D BLUETOOTH_NAME="Tactosy2_R"'
+    '-D BH_SERIAL_NUMBER={ 0xb0, 0x1c, 0xc1, 0xf8, 0xec, 0x12, 0x18, 0x4e, 0x09, 0x77 }'
+build_unflags    = ${bhaptics.build_unflags}
+build_src_filter =
+    ${bhaptics.build_src_filter}
+    +<mode_configs/bhaptics/tactosy2.cpp>
+lib_deps         = ${bhaptics.lib_deps}
 
 [env:bhaptics_tactosyh_hand_left]
-platform = ${bhaptics.platform}
+platform          = ${bhaptics.platform}
 platform_packages = ${bhaptics.platform_packages}
-framework = ${bhaptics.framework}
-board = ${bhaptics.board}
-upload_speed = ${bhaptics.upload_speed}
-monitor_speed = ${bhaptics.monitor_speed}
+framework         = ${bhaptics.framework}
+board             = ${bhaptics.board}
+upload_speed      = ${bhaptics.upload_speed}
+monitor_speed     = ${bhaptics.monitor_speed}
 
-build_flags = ${bhaptics.build_flags}
-			  -D BH_DEVICE_TACTOSYH
-			  -D BH_BLE_APPEARANCE=508
-			  '-D BLUETOOTH_NAME="TactosyH_L"'
-			  '-D BH_SERIAL_NUMBER={ 0xc1, 0x36, 0xdc, 0x21, 0xc9, 0xd4, 0x17, 0x85, 0xbb, 0x90 }'
-build_unflags = ${bhaptics.build_unflags}
-build_src_filter = ${bhaptics.build_src_filter}
-				   +<mode_configs/bhaptics/tactosyh.cpp>
-lib_deps = ${bhaptics.lib_deps}
+build_flags      =
+    ${bhaptics.build_flags}
+    -D BH_DEVICE_TACTOSYH
+    -D BH_BLE_APPEARANCE=508
+    '-D BLUETOOTH_NAME="TactosyH_L"'
+    '-D BH_SERIAL_NUMBER={ 0xc1, 0x36, 0xdc, 0x21, 0xc9, 0xd4, 0x17, 0x85, 0xbb, 0x90 }'
+build_unflags    = ${bhaptics.build_unflags}
+build_src_filter =
+    ${bhaptics.build_src_filter}
+    +<mode_configs/bhaptics/tactosyh.cpp>
+lib_deps         = ${bhaptics.lib_deps}
 
 [env:bhaptics_tactosyh_hand_right]
-platform = ${bhaptics.platform}
+platform          = ${bhaptics.platform}
 platform_packages = ${bhaptics.platform_packages}
-framework = ${bhaptics.framework}
-board = ${bhaptics.board}
-upload_speed = ${bhaptics.upload_speed}
-monitor_speed = ${bhaptics.monitor_speed}
+framework         = ${bhaptics.framework}
+board             = ${bhaptics.board}
+upload_speed      = ${bhaptics.upload_speed}
+monitor_speed     = ${bhaptics.monitor_speed}
 
-build_flags = ${bhaptics.build_flags}
-			  -D BH_DEVICE_TACTOSYH
-			  -D BH_BLE_APPEARANCE=508
-			  '-D BLUETOOTH_NAME="TactosyH_R"'
-			  '-D BH_SERIAL_NUMBER={ 0xc7, 0x5f, 0x3b, 0x06, 0x38, 0xba, 0x34, 0xfa, 0x36, 0xc1 }'
-build_unflags = ${bhaptics.build_unflags}
-build_src_filter = ${bhaptics.build_src_filter}
-				   +<mode_configs/bhaptics/tactosyh.cpp>
-lib_deps = ${bhaptics.lib_deps}
+build_flags      =
+    ${bhaptics.build_flags}
+    -D BH_DEVICE_TACTOSYH
+    -D BH_BLE_APPEARANCE=508
+    '-D BLUETOOTH_NAME="TactosyH_R"'
+    '-D BH_SERIAL_NUMBER={ 0xc7, 0x5f, 0x3b, 0x06, 0x38, 0xba, 0x34, 0xfa, 0x36, 0xc1 }'
+build_unflags    = ${bhaptics.build_unflags}
+build_src_filter =
+    ${bhaptics.build_src_filter}
+    +<mode_configs/bhaptics/tactosyh.cpp>
+lib_deps         = ${bhaptics.lib_deps}
 
 [env:bhaptics_tactosyf_foot_left]
-platform = ${bhaptics.platform}
+platform          = ${bhaptics.platform}
 platform_packages = ${bhaptics.platform_packages}
-framework = ${bhaptics.framework}
-board = ${bhaptics.board}
-upload_speed = ${bhaptics.upload_speed}
-monitor_speed = ${bhaptics.monitor_speed}
+framework         = ${bhaptics.framework}
+board             = ${bhaptics.board}
+upload_speed      = ${bhaptics.upload_speed}
+monitor_speed     = ${bhaptics.monitor_speed}
 
-build_flags = ${bhaptics.build_flags}
-			  -D BH_DEVICE_TACTOSYF
-			  -D BH_BLE_APPEARANCE=508
-			  '-D BLUETOOTH_NAME="TactosyF_L"'
-			  '-D BH_SERIAL_NUMBER={ 0x1a, 0x45, 0x83, 0x44, 0x03, 0xc5, 0xf3, 0xc3, 0xf3, 0xb8 }'
-build_unflags = ${bhaptics.build_unflags}
-build_src_filter = ${bhaptics.build_src_filter}
-				   +<mode_configs/bhaptics/tactosyf.cpp>
-lib_deps = ${bhaptics.lib_deps}
+build_flags      =
+    ${bhaptics.build_flags}
+    -D BH_DEVICE_TACTOSYF
+    -D BH_BLE_APPEARANCE=508
+    '-D BLUETOOTH_NAME="TactosyF_L"'
+    '-D BH_SERIAL_NUMBER={ 0x1a, 0x45, 0x83, 0x44, 0x03, 0xc5, 0xf3, 0xc3, 0xf3, 0xb8 }'
+build_unflags    = ${bhaptics.build_unflags}
+build_src_filter =
+    ${bhaptics.build_src_filter}
+    +<mode_configs/bhaptics/tactosyf.cpp>
+lib_deps         = ${bhaptics.lib_deps}
 
 [env:bhaptics_tactosyf_foot_right]
-platform = ${bhaptics.platform}
+platform          = ${bhaptics.platform}
 platform_packages = ${bhaptics.platform_packages}
-framework = ${bhaptics.framework}
-board = ${bhaptics.board}
-upload_speed = ${bhaptics.upload_speed}
-monitor_speed = ${bhaptics.monitor_speed}
+framework         = ${bhaptics.framework}
+board             = ${bhaptics.board}
+upload_speed      = ${bhaptics.upload_speed}
+monitor_speed     = ${bhaptics.monitor_speed}
 
-build_flags = ${bhaptics.build_flags}
-			  -D BH_DEVICE_TACTOSYF
-			  -D BH_BLE_APPEARANCE=508
-			  '-D BLUETOOTH_NAME="TactosyF_R"'
-			  '-D BH_SERIAL_NUMBER={ 0x14, 0xb9, 0x02, 0x62, 0x41, 0xe4, 0x04, 0xb2, 0xc5, 0x11 }'
-build_unflags = ${bhaptics.build_unflags}
-build_src_filter = ${bhaptics.build_src_filter}
-				   +<mode_configs/bhaptics/tactosyf.cpp>
-lib_deps = ${bhaptics.lib_deps}
+build_flags      =
+    ${bhaptics.build_flags}
+    -D BH_DEVICE_TACTOSYF
+    -D BH_BLE_APPEARANCE=508
+    '-D BLUETOOTH_NAME="TactosyF_R"'
+    '-D BH_SERIAL_NUMBER={ 0x14, 0xb9, 0x02, 0x62, 0x41, 0xe4, 0x04, 0xb2, 0xc5, 0x11 }'
+build_unflags    = ${bhaptics.build_unflags}
+build_src_filter =
+    ${bhaptics.build_src_filter}
+    +<mode_configs/bhaptics/tactosyf.cpp>
+lib_deps         = ${bhaptics.lib_deps}
 
 [env:bhaptics_tactal]
-platform = ${bhaptics.platform}
+platform          = ${bhaptics.platform}
 platform_packages = ${bhaptics.platform_packages}
-framework = ${bhaptics.framework}
-board = ${bhaptics.board}
-upload_speed = ${bhaptics.upload_speed}
-monitor_speed = ${bhaptics.monitor_speed}
+framework         = ${bhaptics.framework}
+board             = ${bhaptics.board}
+upload_speed      = ${bhaptics.upload_speed}
+monitor_speed     = ${bhaptics.monitor_speed}
 
-build_flags = ${bhaptics.build_flags}
-			  -D BH_DEVICE_TACTAL
-			  -D BH_BLE_APPEARANCE=508
-			  '-D BLUETOOTH_NAME="Tactal_"'
-			  '-D BH_SERIAL_NUMBER={ 0xed, 0xcb, 0x55, 0x7c, 0xd7, 0xb9, 0x16, 0xc5, 0x18, 0x2a }'
-build_unflags = ${bhaptics.build_unflags}
-build_src_filter = ${bhaptics.build_src_filter}
-				   +<mode_configs/bhaptics/tactal.cpp>
-lib_deps = ${bhaptics.lib_deps}
+build_flags      =
+    ${bhaptics.build_flags}
+    -D BH_DEVICE_TACTAL
+    -D BH_BLE_APPEARANCE=508
+    '-D BLUETOOTH_NAME="Tactal_"'
+    '-D BH_SERIAL_NUMBER={ 0xed, 0xcb, 0x55, 0x7c, 0xd7, 0xb9, 0x16, 0xc5, 0x18, 0x2a }'
+build_unflags    = ${bhaptics.build_unflags}
+build_src_filter =
+    ${bhaptics.build_src_filter}
+    +<mode_configs/bhaptics/tactal.cpp>
+lib_deps         = ${bhaptics.lib_deps}
 
 [env:bhaptics_tactvisor]
-platform = ${bhaptics.platform}
+platform          = ${bhaptics.platform}
 platform_packages = ${bhaptics.platform_packages}
-framework = ${bhaptics.framework}
-board = ${bhaptics.board}
-upload_speed = ${bhaptics.upload_speed}
-monitor_speed = ${bhaptics.monitor_speed}
+framework         = ${bhaptics.framework}
+board             = ${bhaptics.board}
+upload_speed      = ${bhaptics.upload_speed}
+monitor_speed     = ${bhaptics.monitor_speed}
 
-build_flags = ${bhaptics.build_flags}
-			  -D BH_DEVICE_TACTAL
-			  -D BH_BLE_APPEARANCE=508
-			  '-D BLUETOOTH_NAME="TactVisor_V____"'
-			  '-D BH_SERIAL_NUMBER={ 0x5e, 0xa3, 0xdd, 0x12, 0x00, 0x01, 0x43, 0xc1, 0x26, 0x8a }'
-build_unflags = ${bhaptics.build_unflags}
-build_src_filter = ${bhaptics.build_src_filter}
-				   +<mode_configs/bhaptics/tactvisor.cpp>
-lib_deps = ${bhaptics.lib_deps}
+build_flags      =
+    ${bhaptics.build_flags}
+    -D BH_DEVICE_TACTAL
+    -D BH_BLE_APPEARANCE=508
+    '-D BLUETOOTH_NAME="TactVisor_V____"'
+    '-D BH_SERIAL_NUMBER={ 0x5e, 0xa3, 0xdd, 0x12, 0x00, 0x01, 0x43, 0xc1, 0x26, 0x8a }'
+build_unflags    = ${bhaptics.build_unflags}
+build_src_filter =
+    ${bhaptics.build_src_filter}
+    +<mode_configs/bhaptics/tactvisor.cpp>
+lib_deps         = ${bhaptics.lib_deps}
 
 [env:bhaptics_tactglove_left]
-platform = ${bhaptics.platform}
+platform          = ${bhaptics.platform}
 platform_packages = ${bhaptics.platform_packages}
-framework = ${bhaptics.framework}
-board = ${bhaptics.board}
-upload_speed = ${bhaptics.upload_speed}
-monitor_speed = ${bhaptics.monitor_speed}
+framework         = ${bhaptics.framework}
+board             = ${bhaptics.board}
+upload_speed      = ${bhaptics.upload_speed}
+monitor_speed     = ${bhaptics.monitor_speed}
 
-build_flags = ${bhaptics.build_flags}
-			  -D BH_DEVICE_TACTGLOVE
-			  -D SENSESHIFT_HAND_SIDE=Left
-			  -D BH_BLE_APPEARANCE=508
-			  '-D BLUETOOTH_NAME="TactGlove (L"'
-			  '-D BH_SERIAL_NUMBER={ 0xcd, 0x0b, 0x81, 0x45, 0x85, 0xf9, 0x2b, 0x6c, 0xed, 0x5b }'
-build_unflags = ${bhaptics.build_unflags}
-build_src_filter = ${bhaptics.build_src_filter}
-				   +<mode_configs/bhaptics/tactglove.cpp>
-lib_deps = ${bhaptics.lib_deps}
+build_flags      =
+    ${bhaptics.build_flags}
+    -D BH_DEVICE_TACTGLOVE
+    -D SENSESHIFT_HAND_SIDE=Left
+    -D BH_BLE_APPEARANCE=508
+    '-D BLUETOOTH_NAME="TactGlove (L"'
+    '-D BH_SERIAL_NUMBER={ 0xcd, 0x0b, 0x81, 0x45, 0x85, 0xf9, 0x2b, 0x6c, 0xed, 0x5b }'
+build_unflags    = ${bhaptics.build_unflags}
+build_src_filter =
+    ${bhaptics.build_src_filter}
+    +<mode_configs/bhaptics/tactglove.cpp>
+lib_deps         = ${bhaptics.lib_deps}
 
 [env:bhaptics_tactglove_right]
-platform = ${bhaptics.platform}
+platform          = ${bhaptics.platform}
 platform_packages = ${bhaptics.platform_packages}
-framework = ${bhaptics.framework}
-board = ${bhaptics.board}
-upload_speed = ${bhaptics.upload_speed}
-monitor_speed = ${bhaptics.monitor_speed}
+framework         = ${bhaptics.framework}
+board             = ${bhaptics.board}
+upload_speed      = ${bhaptics.upload_speed}
+monitor_speed     = ${bhaptics.monitor_speed}
 
-build_flags = ${bhaptics.build_flags}
-			  -D BH_DEVICE_TACTGLOVE
-			  -D SENSESHIFT_HAND_SIDE=Right
-			  -D BH_BLE_APPEARANCE=508
-			  '-D BLUETOOTH_NAME="TactGlove (R"'
-			  '-D BH_SERIAL_NUMBER={ 0x12, 0x0b, 0xae, 0xbf, 0xbc, 0x90, 0x3b, 0x0d, 0x84, 0xdd }'
-build_unflags = ${bhaptics.build_unflags}
-build_src_filter = ${bhaptics.build_src_filter}
-				   +<mode_configs/bhaptics/tactglove.cpp>
-lib_deps = ${bhaptics.lib_deps}
+build_flags      =
+    ${bhaptics.build_flags}
+    -D BH_DEVICE_TACTGLOVE
+    -D SENSESHIFT_HAND_SIDE=Right
+    -D BH_BLE_APPEARANCE=508
+    '-D BLUETOOTH_NAME="TactGlove (R"'
+    '-D BH_SERIAL_NUMBER={ 0x12, 0x0b, 0xae, 0xbf, 0xbc, 0x90, 0x3b, 0x0d, 0x84, 0xdd }'
+build_unflags    = ${bhaptics.build_unflags}
+build_src_filter =
+    ${bhaptics.build_src_filter}
+    +<mode_configs/bhaptics/tactglove.cpp>
+lib_deps         = ${bhaptics.lib_deps}
diff --git a/ini/opengloves-indexer.ini b/ini/opengloves-indexer.ini
index e9e8f5a8..08048903 100644
--- a/ini/opengloves-indexer.ini
+++ b/ini/opengloves-indexer.ini
@@ -3,173 +3,183 @@
 ; Wiring Diagram: https://github.com/Valsvirtuals/Indexer/wiki/wiring-and-pinout
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 [env:indexer-c]
-platform = ${opengloves.platform}
+platform          = ${opengloves.platform}
 platform_packages = ${opengloves.platform_packages}
-framework = ${opengloves.framework}
-board = wemos_d1_mini32
-upload_speed = ${opengloves.upload_speed}
-monitor_speed = ${opengloves.monitor_speed}
-
-build_flags = ${opengloves.build_flags} -D OG_ENCODE_FAST
-              ; Pins configuration
-              ; Comment out to disable
-              -D PIN_FINGER_THUMB=25
-              -D PIN_FINGER_INDEX=14
-              -D PIN_FINGER_MIDDLE=33
-              -D PIN_FINGER_RING=39
-              -D PIN_FINGER_PINKY=36
-
-              -D PIN_JOYSTICK_X=12
-              -D PIN_JOYSTICK_Y=4
-              -D PIN_BUTTON_JOYSTICK=0
-
-              -D PIN_BUTTON_A=2
-              -D PIN_BUTTON_B=11
-              ; -D PIN_BUTTON_MENU=5
-              -D PIN_BUTTON_CALIBRATE=27
-; -D PIN_BUTTON_TRIGGER=19 ; unused if GESTURE_TRIGGER is true
-; -D PIN_BUTTON_GRAB=18 ; unused if GESTURE_GRAB is true
-; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
-
-build_unflags = ${opengloves.build_unflags}
-build_src_filter = ${opengloves.build_src_filter}
-                   +<mode_configs/opengloves/opengloves.cpp>
-lib_deps = ${opengloves.lib_deps}
+framework         = ${opengloves.framework}
+board             = wemos_d1_mini32
+upload_speed      = ${opengloves.upload_speed}
+monitor_speed     = ${opengloves.monitor_speed}
+
+build_flags =
+    ${opengloves.build_flags}
+    -D OG_ENCODE_FAST
+    ;;;; Pins configuration
+    ;;;; Comment out to disable
+    -D PIN_FINGER_THUMB=25
+    -D PIN_FINGER_INDEX=14
+    -D PIN_FINGER_MIDDLE=33
+    -D PIN_FINGER_RING=39
+    -D PIN_FINGER_PINKY=36
+
+    -D PIN_JOYSTICK_X=12
+    -D PIN_JOYSTICK_Y=4
+    -D PIN_BUTTON_JOYSTICK=0
+
+    -D PIN_BUTTON_A=2
+    -D PIN_BUTTON_B=11
+    ; -D PIN_BUTTON_MENU=5
+    -D PIN_BUTTON_CALIBRATE=27
+    ; -D PIN_BUTTON_TRIGGER=19 ; unused if GESTURE_TRIGGER is true
+    ; -D PIN_BUTTON_GRAB=18 ; unused if GESTURE_GRAB is true
+    ; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
+
+build_unflags    = ${opengloves.build_unflags}
+build_src_filter =
+    ${opengloves.build_src_filter}
+    +<mode_configs/opengloves/opengloves.cpp>
+lib_deps         = ${opengloves.lib_deps}
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ; Indexer CF
 ; Wiring Diagram: https://github.com/Valsvirtuals/Indexer/wiki/wiring-and-pinout
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 [env:indexer-cf]
-platform = ${opengloves.platform}
+platform          = ${opengloves.platform}
 platform_packages = ${opengloves.platform_packages}
-framework = ${opengloves.framework}
-board = wemos_d1_mini32
-upload_speed = ${opengloves.upload_speed}
-monitor_speed = ${opengloves.monitor_speed}
-
-build_flags = ${opengloves.build_flags} -D OG_ENCODE_FAST
-              ; Pins configuration
-              ; Comment out to disable
-              -D PIN_FINGER_THUMB=25
-              -D PIN_FINGER_INDEX=14
-              -D PIN_FINGER_MIDDLE=33
-              -D PIN_FINGER_RING=39
-              -D PIN_FINGER_PINKY=36
-
-              -D PIN_JOYSTICK_X=12
-              -D PIN_JOYSTICK_Y=4
-              -D PIN_BUTTON_JOYSTICK=0
-
-              -D PIN_BUTTON_A=2
-              -D PIN_BUTTON_B=11
-              ; -D PIN_BUTTON_MENU=5
-              -D PIN_BUTTON_CALIBRATE=27
-              ; -D PIN_BUTTON_TRIGGER=19 ; unused if GESTURE_TRIGGER is true
-              ; -D PIN_BUTTON_GRAB=18 ; unused if GESTURE_GRAB is true
-              ; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
-
-              -D PIN_FFB_THUMB=16
-              -D PIN_FFB_INDEX=17
-              -D PIN_FFB_MIDDLE=21
-              -D PIN_FFB_RING=22
-              -D PIN_FFB_PINKY=1
-
-build_unflags = ${opengloves.build_unflags}
-build_src_filter = ${opengloves.build_src_filter}
-                   +<mode_configs/opengloves/opengloves.cpp>
-lib_deps = ${opengloves.lib_deps}
+framework         = ${opengloves.framework}
+board             = wemos_d1_mini32
+upload_speed      = ${opengloves.upload_speed}
+monitor_speed     = ${opengloves.monitor_speed}
+
+build_flags =
+    ${opengloves.build_flags}
+    -D OG_ENCODE_FAST
+    ;;;; Pins configuration
+    ;;;; Comment out to disable
+    -D PIN_FINGER_THUMB=25
+    -D PIN_FINGER_INDEX=14
+    -D PIN_FINGER_MIDDLE=33
+    -D PIN_FINGER_RING=39
+    -D PIN_FINGER_PINKY=36
+
+    -D PIN_JOYSTICK_X=12
+    -D PIN_JOYSTICK_Y=4
+    -D PIN_BUTTON_JOYSTICK=0
+
+    -D PIN_BUTTON_A=2
+    -D PIN_BUTTON_B=11
+    ; -D PIN_BUTTON_MENU=5
+    -D PIN_BUTTON_CALIBRATE=27
+    ; -D PIN_BUTTON_TRIGGER=19 ; unused if GESTURE_TRIGGER is true
+    ; -D PIN_BUTTON_GRAB=18 ; unused if GESTURE_GRAB is true
+    ; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
+
+    -D PIN_FFB_THUMB=16
+    -D PIN_FFB_INDEX=17
+    -D PIN_FFB_MIDDLE=21
+    -D PIN_FFB_RING=22
+    -D PIN_FFB_PINKY=1
+
+build_unflags    = ${opengloves.build_unflags}
+build_src_filter =
+    ${opengloves.build_src_filter}
+    +<mode_configs/opengloves/opengloves.cpp>
+lib_deps         = ${opengloves.lib_deps}
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ; Indexer CS
 ; Wiring Diagram: https://github.com/Valsvirtuals/Indexer/wiki/wiring-and-pinout
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 [env:indexer-cs]
-platform = ${opengloves.platform}
+platform          = ${opengloves.platform}
 platform_packages = ${opengloves.platform_packages}
-framework = ${opengloves.framework}
-board = wemos_d1_mini32
-upload_speed = ${opengloves.upload_speed}
-monitor_speed = ${opengloves.monitor_speed}
-
-build_flags = ${opengloves.build_flags}
-              ; Pins configuration
-              ; Comment out to disable
-              -D PIN_FINGER_THUMB=25
-              -D PIN_FINGER_INDEX=14
-              -D PIN_FINGER_MIDDLE=33
-              -D PIN_FINGER_RING=39
-              -D PIN_FINGER_PINKY=36
-
-              -D PIN_FINGER_THUMB_SPLAY=32
-              -D PIN_FINGER_INDEX_SPLAY=13
-              -D PIN_FINGER_MIDDLE_SPLAY=34
-              -D PIN_FINGER_RING_SPLAY=35
-              -D PIN_FINGER_PINKY_SPLAY=26
-
-              -D PIN_JOYSTICK_X=12
-              -D PIN_JOYSTICK_Y=4
-              -D PIN_BUTTON_JOYSTICK=0
-
-              -D PIN_BUTTON_A=2
-              -D PIN_BUTTON_B=11
-              ; -D PIN_BUTTON_MENU=5
-              -D PIN_BUTTON_CALIBRATE=27
-; -D PIN_BUTTON_TRIGGER=19 ; unused if GESTURE_TRIGGER is true
-; -D PIN_BUTTON_GRAB=18 ; unused if GESTURE_GRAB is true
-; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
-
-build_unflags = ${opengloves.build_unflags}
-build_src_filter = ${opengloves.build_src_filter}
-                   +<mode_configs/opengloves/opengloves.cpp>
-lib_deps = ${opengloves.lib_deps}
+framework         = ${opengloves.framework}
+board             = wemos_d1_mini32
+upload_speed      = ${opengloves.upload_speed}
+monitor_speed     = ${opengloves.monitor_speed}
+
+build_flags =
+    ${opengloves.build_flags}
+    ;;;; Pins configuration
+    ;;;; Comment out to disable
+    -D PIN_FINGER_THUMB=25
+    -D PIN_FINGER_INDEX=14
+    -D PIN_FINGER_MIDDLE=33
+    -D PIN_FINGER_RING=39
+    -D PIN_FINGER_PINKY=36
+
+    -D PIN_FINGER_THUMB_SPLAY=32
+    -D PIN_FINGER_INDEX_SPLAY=13
+    -D PIN_FINGER_MIDDLE_SPLAY=34
+    -D PIN_FINGER_RING_SPLAY=35
+    -D PIN_FINGER_PINKY_SPLAY=26
+
+    -D PIN_JOYSTICK_X=12
+    -D PIN_JOYSTICK_Y=4
+    -D PIN_BUTTON_JOYSTICK=0
+
+    -D PIN_BUTTON_A=2
+    -D PIN_BUTTON_B=11
+    ; -D PIN_BUTTON_MENU=5
+    -D PIN_BUTTON_CALIBRATE=27
+    ; -D PIN_BUTTON_TRIGGER=19 ; unused if GESTURE_TRIGGER is true
+    ; -D PIN_BUTTON_GRAB=18 ; unused if GESTURE_GRAB is true
+    ; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
+
+build_unflags    = ${opengloves.build_unflags}
+build_src_filter =
+    ${opengloves.build_src_filter}
+    +<mode_configs/opengloves/opengloves.cpp>
+lib_deps         = ${opengloves.lib_deps}
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ; Indexer CSF
 ; Wiring Diagram: https://github.com/Valsvirtuals/Indexer/wiki/wiring-and-pinout
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 [env:indexer-csf]
-platform = ${opengloves.platform}
+platform          = ${opengloves.platform}
 platform_packages = ${opengloves.platform_packages}
-framework = ${opengloves.framework}
-board = wemos_d1_mini32
-upload_speed = ${opengloves.upload_speed}
-monitor_speed = ${opengloves.monitor_speed}
-
-build_flags = ${opengloves.build_flags}
-              ; Pins configuration
-              ; Comment out to disable
-              -D PIN_FINGER_THUMB=25
-              -D PIN_FINGER_INDEX=14
-              -D PIN_FINGER_MIDDLE=33
-              -D PIN_FINGER_RING=39
-              -D PIN_FINGER_PINKY=36
-
-              -D PIN_FINGER_THUMB_SPLAY=32
-              -D PIN_FINGER_INDEX_SPLAY=13
-              -D PIN_FINGER_MIDDLE_SPLAY=34
-              -D PIN_FINGER_RING_SPLAY=35
-              -D PIN_FINGER_PINKY_SPLAY=26
-
-              -D PIN_JOYSTICK_X=12
-              -D PIN_JOYSTICK_Y=4
-              -D PIN_BUTTON_JOYSTICK=0
-
-              -D PIN_BUTTON_A=2
-              -D PIN_BUTTON_B=11
-              ; -D PIN_BUTTON_MENU=5
-              -D PIN_BUTTON_CALIBRATE=27
-              ; -D PIN_BUTTON_TRIGGER=19 ; unused if GESTURE_TRIGGER is true
-              ; -D PIN_BUTTON_GRAB=18 ; unused if GESTURE_GRAB is true
-              ; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
-
-              -D PIN_FFB_THUMB=16
-              -D PIN_FFB_INDEX=17
-              -D PIN_FFB_MIDDLE=21
-              -D PIN_FFB_RING=22
-              -D PIN_FFB_PINKY=1
-
-build_unflags = ${opengloves.build_unflags}
-build_src_filter = ${opengloves.build_src_filter}
-                   +<mode_configs/opengloves/opengloves.cpp>
-lib_deps = ${opengloves.lib_deps}
+framework         = ${opengloves.framework}
+board             = wemos_d1_mini32
+upload_speed      = ${opengloves.upload_speed}
+monitor_speed     = ${opengloves.monitor_speed}
+
+build_flags =
+    ${opengloves.build_flags}
+    ;;;; Pins configuration
+    ;;;; Comment out to disable
+    -D PIN_FINGER_THUMB=25
+    -D PIN_FINGER_INDEX=14
+    -D PIN_FINGER_MIDDLE=33
+    -D PIN_FINGER_RING=39
+    -D PIN_FINGER_PINKY=36
+
+    -D PIN_FINGER_THUMB_SPLAY=32
+    -D PIN_FINGER_INDEX_SPLAY=13
+    -D PIN_FINGER_MIDDLE_SPLAY=34
+    -D PIN_FINGER_RING_SPLAY=35
+    -D PIN_FINGER_PINKY_SPLAY=26
+
+    -D PIN_JOYSTICK_X=12
+    -D PIN_JOYSTICK_Y=4
+    -D PIN_BUTTON_JOYSTICK=0
+
+    -D PIN_BUTTON_A=2
+    -D PIN_BUTTON_B=11
+    ; -D PIN_BUTTON_MENU=5
+    -D PIN_BUTTON_CALIBRATE=27
+    ; -D PIN_BUTTON_TRIGGER=19 ; unused if GESTURE_TRIGGER is true
+    ; -D PIN_BUTTON_GRAB=18 ; unused if GESTURE_GRAB is true
+    ; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
+
+    -D PIN_FFB_THUMB=16
+    -D PIN_FFB_INDEX=17
+    -D PIN_FFB_MIDDLE=21
+    -D PIN_FFB_RING=22
+    -D PIN_FFB_PINKY=1
+
+build_unflags    = ${opengloves.build_unflags}
+build_src_filter =
+    ${opengloves.build_src_filter}
+    +<mode_configs/opengloves/opengloves.cpp>
+lib_deps         = ${opengloves.lib_deps}
diff --git a/ini/opengloves-lucidgloves.ini b/ini/opengloves-lucidgloves.ini
index 1c50b00a..b777cee8 100644
--- a/ini/opengloves-lucidgloves.ini
+++ b/ini/opengloves-lucidgloves.ini
@@ -3,115 +3,124 @@
 ; Wiring Diagram: https://github.com/LucidVR/lucidgloves/wiki/Prototype-3-Wiring-Diagram
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 [env:lucidgloves-prototype3]
-platform = ${opengloves.platform}
+platform          = ${opengloves.platform}
 platform_packages = ${opengloves.platform_packages}
-framework = ${opengloves.framework}
-board = ${opengloves.board}
-upload_speed = ${opengloves.upload_speed}
-monitor_speed = ${opengloves.monitor_speed}
-
-build_flags = ${opengloves.build_flags} -D OG_ENCODE_FAST
-
-			  ; Pins configuration
-			  ; Comment out to disable
-			  -D PIN_FINGER_THUMB=32
-			  -D PIN_FINGER_INDEX=35
-			  -D PIN_FINGER_MIDDLE=34
-			  -D PIN_FINGER_RING=39
-			  -D PIN_FINGER_PINKY=36
-
-			  -D PIN_JOYSTICK_X=33
-			  -D PIN_JOYSTICK_Y=25
-			  -D PIN_BUTTON_JOYSTICK=26
-
-			  -D PIN_BUTTON_A=27
-			  -D PIN_BUTTON_B=14
-
-			  -D CALIBRATION_ALWAYS_CALIBRATE=true
-
-build_unflags = ${opengloves.build_unflags}
-build_src_filter = ${opengloves.build_src_filter}
-				   +<mode_configs/opengloves/opengloves.cpp>
-lib_deps = ${opengloves.lib_deps}
+framework         = ${opengloves.framework}
+board             = ${opengloves.board}
+upload_speed      = ${opengloves.upload_speed}
+monitor_speed     = ${opengloves.monitor_speed}
+
+build_flags =
+    ${opengloves.build_flags}
+    -D OG_ENCODE_FAST
+
+    ;;;; Pins configuration
+    ;;;; Comment out to disable
+    -D PIN_FINGER_THUMB=32
+    -D PIN_FINGER_INDEX=35
+    -D PIN_FINGER_MIDDLE=34
+    -D PIN_FINGER_RING=39
+    -D PIN_FINGER_PINKY=36
+
+    -D PIN_JOYSTICK_X=33
+    -D PIN_JOYSTICK_Y=25
+    -D PIN_BUTTON_JOYSTICK=26
+
+    -D PIN_BUTTON_A=27
+    -D PIN_BUTTON_B=14
+
+    -D CALIBRATION_ALWAYS_CALIBRATE=true
+
+build_unflags    = ${opengloves.build_unflags}
+build_src_filter =
+    ${opengloves.build_src_filter}
+    +<mode_configs/opengloves/opengloves.cpp>
+lib_deps         = ${opengloves.lib_deps}
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ; LucidGloves Prototype 4
 ; Wiring Diagram: https://github.com/LucidVR/lucidgloves/wiki/Prototype-4-Wiring-Diagram
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 [env:lucidgloves-prototype4]
-platform = ${opengloves.platform}
+platform          = ${opengloves.platform}
 platform_packages = ${opengloves.platform_packages}
-framework = ${opengloves.framework}
-board = ${opengloves.board}
-upload_speed = ${opengloves.upload_speed}
-monitor_speed = ${opengloves.monitor_speed}
-
-build_flags = ${opengloves.build_flags} -D OG_ENCODE_FAST
-			  ; Pins configuration
-			  ; Comment out to disable
-			  -D PIN_FINGER_THUMB=32
-			  -D PIN_FINGER_INDEX=35
-			  -D PIN_FINGER_MIDDLE=34
-			  -D PIN_FINGER_RING=39
-			  -D PIN_FINGER_PINKY=36
-
-			  -D PIN_JOYSTICK_X=33
-			  -D PIN_JOYSTICK_Y=25
-			  -D PIN_BUTTON_JOYSTICK=26
-
-			  -D PIN_BUTTON_A=27
-			  -D PIN_BUTTON_B=14
-			  ; -D PIN_BUTTON_MENU=27
-			  -D PIN_BUTTON_CALIBRATE=12
-; -D PIN_BUTTON_TRIGGER=12 ; unused if GESTURE_TRIGGER is true
-; -D PIN_BUTTON_GRAB=13 ; unused if GESTURE_GRAB is true
-; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
-
-build_unflags = ${opengloves.build_unflags}
-build_src_filter = ${opengloves.build_src_filter}
-				   +<mode_configs/opengloves/opengloves.cpp>
-lib_deps = ${opengloves.lib_deps}
+framework         = ${opengloves.framework}
+board             = ${opengloves.board}
+upload_speed      = ${opengloves.upload_speed}
+monitor_speed     = ${opengloves.monitor_speed}
+
+build_flags =
+    ${opengloves.build_flags}
+    -D OG_ENCODE_FAST
+    ;;;; Pins configuration
+    ;;;; Comment out to disable
+    -D PIN_FINGER_THUMB=32
+    -D PIN_FINGER_INDEX=35
+    -D PIN_FINGER_MIDDLE=34
+    -D PIN_FINGER_RING=39
+    -D PIN_FINGER_PINKY=36
+
+    -D PIN_JOYSTICK_X=33
+    -D PIN_JOYSTICK_Y=25
+    -D PIN_BUTTON_JOYSTICK=26
+
+    -D PIN_BUTTON_A=27
+    -D PIN_BUTTON_B=14
+    ; -D PIN_BUTTON_MENU=27
+    -D PIN_BUTTON_CALIBRATE=12
+    ; -D PIN_BUTTON_TRIGGER=12 ; unused if GESTURE_TRIGGER is true
+    ; -D PIN_BUTTON_GRAB=13 ; unused if GESTURE_GRAB is true
+    ; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
+
+build_unflags    = ${opengloves.build_unflags}
+build_src_filter =
+    ${opengloves.build_src_filter}
+    +<mode_configs/opengloves/opengloves.cpp>
+lib_deps         = ${opengloves.lib_deps}
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ; LucidGloves Prototype 4 + Force Feedback
 ; Wiring Diagram: https://github.com/LucidVR/lucidgloves/wiki/Prototype-4-Wiring-Diagram
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 [env:lucidgloves-prototype4-ffb]
-platform = ${opengloves.platform}
+platform          = ${opengloves.platform}
 platform_packages = ${opengloves.platform_packages}
-framework = ${opengloves.framework}
-board = ${opengloves.board}
-upload_speed = ${opengloves.upload_speed}
-monitor_speed = ${opengloves.monitor_speed}
-
-build_flags = ${opengloves.build_flags}
-			  ; Pins configuration
-			  ; Comment out to disable
-			  -D PIN_FINGER_THUMB=32
-			  -D PIN_FINGER_INDEX=35
-			  -D PIN_FINGER_MIDDLE=34
-			  -D PIN_FINGER_RING=39
-			  -D PIN_FINGER_PINKY=36
-
-			  -D PIN_JOYSTICK_X=33
-			  -D PIN_JOYSTICK_Y=25
-			  -D PIN_BUTTON_JOYSTICK=26
-
-			  -D PIN_BUTTON_A=27
-			  -D PIN_BUTTON_B=14
-			  ; -D PIN_BUTTON_MENU=27
-			  -D PIN_BUTTON_CALIBRATE=12
-			  ; -D PIN_BUTTON_TRIGGER=12 ; unused if GESTURE_TRIGGER is true
-			  ; -D PIN_BUTTON_GRAB=13 ; unused if GESTURE_GRAB is true
-			  ; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
-
-			  -D PIN_FFB_THUMB=17
-			  -D PIN_FFB_INDEX=21
-			  -D PIN_FFB_MIDDLE=19
-			  -D PIN_FFB_RING=18
-			  -D PIN_FFB_PINKY=5
-
-build_unflags = ${opengloves.build_unflags}
-build_src_filter = ${opengloves.build_src_filter}
-				   +<mode_configs/opengloves/opengloves.cpp>
-lib_deps = ${opengloves.lib_deps}
+framework         = ${opengloves.framework}
+board             = ${opengloves.board}
+upload_speed      = ${opengloves.upload_speed}
+monitor_speed     = ${opengloves.monitor_speed}
+
+build_flags =
+    ${opengloves.build_flags}
+    -D OG_ENCODE_FAST
+    ;;;; Pins configuration
+    ;;;; Comment out to disable
+    -D PIN_FINGER_THUMB=32
+    -D PIN_FINGER_INDEX=35
+    -D PIN_FINGER_MIDDLE=34
+    -D PIN_FINGER_RING=39
+    -D PIN_FINGER_PINKY=36
+
+    -D PIN_JOYSTICK_X=33
+    -D PIN_JOYSTICK_Y=25
+    -D PIN_BUTTON_JOYSTICK=26
+
+    -D PIN_BUTTON_A=27
+    -D PIN_BUTTON_B=14
+    ; -D PIN_BUTTON_MENU=27
+    -D PIN_BUTTON_CALIBRATE=12
+    ; -D PIN_BUTTON_TRIGGER=12 ; unused if GESTURE_TRIGGER is true
+    ; -D PIN_BUTTON_GRAB=13 ; unused if GESTURE_GRAB is true
+    ; -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true
+
+    -D PIN_FFB_THUMB=17
+    -D PIN_FFB_INDEX=21
+    -D PIN_FFB_MIDDLE=19
+    -D PIN_FFB_RING=18
+    -D PIN_FFB_PINKY=5
+
+build_unflags    = ${opengloves.build_unflags}
+build_src_filter =
+    ${opengloves.build_src_filter}
+    +<mode_configs/opengloves/opengloves.cpp>
+lib_deps         = ${opengloves.lib_deps}
diff --git a/ini/opengloves.ini b/ini/opengloves.ini
index 12819792..ab5c8333 100644
--- a/ini/opengloves.ini
+++ b/ini/opengloves.ini
@@ -1,60 +1,62 @@
 [opengloves]
-platform = platformio/espressif32@^6.1.0
+platform          = platformio/espressif32@^6.1.0
 platform_packages =
-	platformio/framework-arduinoespressif32@^3.20014.231204
-framework = arduino
-board = esp32doit-devkit-v1
-upload_speed = 921600
-monitor_speed = 115200
-
-build_flags = ${common.build_flags}
-			  -D OPENGLOVES
-
-			  ; Communication
-			  ; -D OPENGLOVES_COMMUNICATION=OPENGLOVES_COMM_BLESERIAL ; OPENGLOVES_COMM_SERIAL, OPENGLOVES_COMM_BTSERIAL, OPENGLOVES_COMM_BLESERIAL
-			  ; Serial
-			  -D SERIAL_BAUDRATE=115200
-			  -D SERIAL_PORT=Serial ; Serial, Serial1, Serial2, Serial3
-			  ; BTSerial
-			  '-D BTSERIAL_PREFIX="SenseShift_OG"'
-			  ; '-D BTSERIAL_NAME="SenseShift_OG_Left"'
-
-			  ; Sensors
-			  -D FINGER_THUMB_INVERT=false
-			  -D FINGER_INDEX_INVERT=false
-			  -D FINGER_MIDDLE_INVERT=false
-			  -D FINGER_RING_INVERT=false
-			  -D FINGER_PINKY_INVERT=false
-
-			  -D FINGER_THUMB_SPLAY_INVERT=false
-			  -D FINGER_INDEX_SPLAY_INVERT=false
-			  -D FINGER_MIDDLE_SPLAY_INVERT=false
-			  -D FINGER_RING_SPLAY_INVERT=false
-			  -D FINGER_PINKY_SPLAY_INVERT=false
-
-			  -D JOYSTICK_X_INVERT=false
-			  -D JOYSTICK_Y_INVERT=false
-			  -D JOYSTICK_DEADZONE=0.1
-			  -D BUTTON_JOYSTICK_INVERT=false
-
-			  -D BUTTON_A_INVERT=false
-			  -D BUTTON_B_INVERT=false
-			  -D BUTTON_MENU_INVERT=false
-			  -D BUTTON_CALIBRATE_INVERT=false
-			  -D BUTTON_TRIGGER_INVERT=false
-			  -D BUTTON_GRAB_INVERT=false
-			  -D BUTTON_PINCH_INVERT=false
-
-			  -D GESTURE_TRIGGER_ENABLED=true
-			  -D GESTURE_GRAB_ENABLED=true
-			  -D GESTURE_PINCH_ENABLED=true
-
-			  ; Calibration
-			  -D CALIBRATION_ALWAYS_CALIBRATE=false
-			  -D CALIBRATION_DURATION=2000 ; in ms
-
-			  -D UPDATE_RATE=90 ; sensors update rate in Hz
-
-build_unflags = ${common.build_unflags}
+    platformio/framework-arduinoespressif32@^3.20014.231204
+framework         = arduino
+board             = esp32doit-devkit-v1
+upload_speed      = 921600
+monitor_speed     = 115200
+
+build_flags =
+    ${common.build_flags}
+    -D OPENGLOVES
+
+    ;;;; Communication
+    ; -D OPENGLOVES_COMMUNICATION=OPENGLOVES_COMM_BLESERIAL ; OPENGLOVES_COMM_SERIAL, OPENGLOVES_COMM_BTSERIAL, OPENGLOVES_COMM_BLESERIAL
+    ;;;; Serial
+    -D SERIAL_BAUDRATE=115200
+    ; Options: Serial, Serial1, Serial2, Serial3
+    -D SERIAL_PORT=Serial
+    ;;;; BTSerial
+    '-D BTSERIAL_PREFIX="SenseShift_OG"'
+    ; '-D BTSERIAL_NAME="SenseShift_OG_Left"'
+
+    ; Sensors
+    -D FINGER_THUMB_INVERT=false
+    -D FINGER_INDEX_INVERT=false
+    -D FINGER_MIDDLE_INVERT=false
+    -D FINGER_RING_INVERT=false
+    -D FINGER_PINKY_INVERT=false
+
+    -D FINGER_THUMB_SPLAY_INVERT=false
+    -D FINGER_INDEX_SPLAY_INVERT=false
+    -D FINGER_MIDDLE_SPLAY_INVERT=false
+    -D FINGER_RING_SPLAY_INVERT=false
+    -D FINGER_PINKY_SPLAY_INVERT=false
+
+    -D JOYSTICK_X_INVERT=false
+    -D JOYSTICK_Y_INVERT=false
+    -D JOYSTICK_DEADZONE=0.1
+    -D BUTTON_JOYSTICK_INVERT=false
+
+    -D BUTTON_A_INVERT=false
+    -D BUTTON_B_INVERT=false
+    -D BUTTON_MENU_INVERT=false
+    -D BUTTON_CALIBRATE_INVERT=false
+    -D BUTTON_TRIGGER_INVERT=false
+    -D BUTTON_GRAB_INVERT=false
+    -D BUTTON_PINCH_INVERT=false
+
+    -D GESTURE_TRIGGER_ENABLED=true
+    -D GESTURE_GRAB_ENABLED=true
+    -D GESTURE_PINCH_ENABLED=true
+
+    ;;;; Calibration
+    -D CALIBRATION_ALWAYS_CALIBRATE=false
+    -D CALIBRATION_DURATION=2000 ; in ms
+
+    -D UPDATE_RATE=90 ; sensors update rate in Hz
+
+build_unflags    = ${common.build_unflags}
 build_src_filter = ${common.build_src_filter}
-lib_deps = ${common.lib_deps}
+lib_deps         = ${common.lib_deps}
diff --git a/platformio.ini b/platformio.ini
index ea55d804..71400ed6 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -9,58 +9,61 @@
 ; https://docs.platformio.org/page/projectconf.html
 
 [platformio]
-description = Open Source Haptic-feedback device firmware
-lib_dir = ./lib
-src_dir = ./firmware
+description   = Open Source Haptic-feedback device firmware
+lib_dir       = ./lib
+src_dir       = ./firmware
 extra_configs =
-	ini/bhaptics.ini
-	ini/opengloves.ini
-	ini/opengloves-lucidgloves.ini
-	ini/opengloves-indexer.ini
-default_envs = bhaptics_tactsuit_x16, bhaptics_tactal, bhaptics_tactglove_left, lucidgloves-prototype3
+    ini/bhaptics.ini
+    ini/opengloves.ini
+    ini/opengloves-lucidgloves.ini
+    ini/opengloves-indexer.ini
+default_envs  = bhaptics_tactsuit_x16, bhaptics_tactal, bhaptics_tactglove_left, lucidgloves-prototype3
 
 [common]
 build_unflags =
-	-std=gnu++11
-build_flags =
-	-std=gnu++17
-	-D __OH_FIRMWARE__
-	-D CORE_DEBUG_LEVEL=3
-;	-D DEBUG_MODE=0
-;	-D DEBUG_ESP_PORT=Serial
-;	-D SENSESHIFT_SERIAL_PLOTTER=true
-;	-D SENSESHIFT_BATTERY_ENABLED=true
-;	-D SENSESHIFT_BLE_USE_NIMBLE=true
+    -std=gnu++11
+build_flags   =
+    -std=gnu++17
+    -D __OH_FIRMWARE__
+    -D CORE_DEBUG_LEVEL=3
+;   -D DEBUG_MODE=0
+;   -D DEBUG_ESP_PORT=Serial
+;   -D SENSESHIFT_SERIAL_PLOTTER=true
+;   -D SENSESHIFT_BATTERY_ENABLED=true
+;   -D SENSESHIFT_BLE_USE_NIMBLE=true
 
 build_src_filter =
-	+<*>
-	-<mode_configs>
+    +<*>
+    -<mode_configs>
 
 lib_deps =
 
 [env]
-build_flags = ${common.build_flags}
-build_unflags = ${common.build_unflags}
+build_flags      = ${common.build_flags}
+build_unflags    = ${common.build_unflags}
 build_src_filter = ${common.build_src_filter}
-lib_deps = ${common.lib_deps}
-lib_ldf_mode = deep+
+lib_deps         = ${common.lib_deps}
+lib_ldf_mode     = deep+
 
-check_tool = clangtidy
+check_tool  = clangtidy
 check_flags =
-	clangtidy: --config-file=./.clang-tidy --fix
+    clangtidy: --config-file=./.clang-tidy --fix
 
 debug_build_flags = -Os
 
 [env:native]
 platform = native
 
-build_unflags = ${common.build_unflags}
-build_flags = ${common.build_flags}
-			  -lgcov
-			  --coverage
-build_src_filter = ${common.build_src_filter}
-				   +<mode_configs/test.cpp>
-lib_deps = ${common.lib_deps}
-		   fabiobatsilva/ArduinoFake@^0.4
+build_unflags    = ${common.build_unflags}
+build_flags      =
+    ${common.build_flags}
+    -lgcov
+    --coverage
+build_src_filter =
+    ${common.build_src_filter}
+    +<mode_configs/test.cpp>
+lib_deps         =
+    ${common.lib_deps}
+    fabiobatsilva/ArduinoFake@^0.4
 
 test_ignore = test_embedded

From 1d42b34a407293904fe7426d79bde7e4a3ee9d99 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Mon, 19 Feb 2024 19:43:14 +0400
Subject: [PATCH 76/82] fix(OpenGloves): manually initialize sensor pointers

---
 lib/opengloves/opengloves/opengloves.hpp     |  40 ++++
 test/test_opengloves_alpha_encoding/main.cpp | 238 ++++++++-----------
 2 files changed, 142 insertions(+), 136 deletions(-)

diff --git a/lib/opengloves/opengloves/opengloves.hpp b/lib/opengloves/opengloves/opengloves.hpp
index 2d9fd3b4..e6d708c7 100644
--- a/lib/opengloves/opengloves/opengloves.hpp
+++ b/lib/opengloves/opengloves/opengloves.hpp
@@ -3,8 +3,10 @@
 #include <array>
 #include <cstddef>
 #include <cstdint>
+#include <initializer_list>
 #include <map>
 #include <string>
+#include <type_traits>
 #include <variant>
 #include <vector>
 
@@ -108,6 +110,44 @@ namespace og {
     /// I know, it is not the prettiest one, but we need this type of punning to efficiently encode/decode the data
     template<typename Tf = float, typename Tb = bool>
     struct InputPeripheral {
+        template<
+          typename U = Tf,
+          typename V = Tb,
+          std::enable_if_t<std::is_floating_point_v<U> && std::is_same_v<V, bool>, bool> = true>
+        InputPeripheral()
+        {
+            this->curl.fingers = { {
+              { 0.0F, 0.0F, 0.0F, 0.0F },
+              { 0.0F, 0.0F, 0.0F, 0.0F },
+              { 0.0F, 0.0F, 0.0F, 0.0F },
+              { 0.0F, 0.0F, 0.0F, 0.0F },
+              { 0.0F, 0.0F, 0.0F, 0.0F },
+            } };
+            this->splay.fingers = { 0.0F, 0.0F, 0.0F, 0.0F, 0.0F };
+            this->joystick = { 0.0F, 0.0F, false };
+            this->buttons = { { false, false, false, false, false } };
+            this->analog_buttons = { { { false, 0.0F }, { false, 0.0F } } };
+        }
+
+        template<
+          typename U = Tf,
+          typename V = Tb,
+          std::enable_if_t<std::is_pointer_v<U> && std::is_pointer_v<V>, bool> = true>
+        InputPeripheral()
+        {
+            this->curl.fingers = { {
+              { nullptr, nullptr, nullptr, nullptr },
+              { nullptr, nullptr, nullptr, nullptr },
+              { nullptr, nullptr, nullptr, nullptr },
+              { nullptr, nullptr, nullptr, nullptr },
+              { nullptr, nullptr, nullptr, nullptr },
+            } };
+            this->splay.fingers = { nullptr, nullptr, nullptr, nullptr, nullptr };
+            this->joystick = { nullptr, nullptr, nullptr };
+            this->buttons = { { nullptr, nullptr, nullptr, nullptr, nullptr } };
+            this->analog_buttons = { { { nullptr, nullptr }, { nullptr, nullptr } } };
+        }
+
         InputFinger<InputFingerCurl<Tf>> curl;
         InputFinger<Tf> splay;
 
diff --git a/test/test_opengloves_alpha_encoding/main.cpp b/test/test_opengloves_alpha_encoding/main.cpp
index 9467b45c..7fd164d0 100644
--- a/test/test_opengloves_alpha_encoding/main.cpp
+++ b/test/test_opengloves_alpha_encoding/main.cpp
@@ -7,169 +7,135 @@ void test_encode_input_peripherals(void)
 {
     const IEncoder* encoder = new AlphaEncoder();
 
+    const auto defaultEmpty = InputPeripheralData();
+
+    auto halfCurl = InputPeripheralData();
+    halfCurl.curl = {
+        .thumb = { .curl_total = 0.5 },
+        .index = { .curl_total = 0.5 },
+        .middle = { .curl_total = 0.5 },
+        .ring = { .curl_total = 0.5 },
+        .pinky = { .curl_total = 0.5 },
+    };
+
+    auto zeroCurl = InputPeripheralData();
+    zeroCurl.curl = {
+        .thumb = { .curl_total = 0.0 },
+        .index = { .curl_total = 0.0 },
+        .middle = { .curl_total = 0.0 },
+        .ring = { .curl_total = 0.0 },
+        .pinky = { .curl_total = 0.0 },
+    };
+
+    auto fullCurl = InputPeripheralData();
+    fullCurl.curl = {
+        .thumb = { .curl_total = 1.0 },
+        .index = { .curl_total = 1.0 },
+        .middle = { .curl_total = 1.0 },
+        .ring = { .curl_total = 1.0 },
+        .pinky = { .curl_total = 1.0 },
+    };
+
+    auto halfSplay = InputPeripheralData();
+    halfSplay.splay = {
+        .thumb = 0.5,
+        .index = 0.5,
+        .middle = 0.5,
+        .ring = 0.5,
+        .pinky = 0.5,
+    };
+
+    auto increasingJoints = InputPeripheralData();
+    increasingJoints.curl = {
+        .thumb = { .curl = { 0.25f, 0.5f, 0.75f, 1.0f } },
+        .index = { .curl = { 0.25f, 0.5f, 0.75f, 1.0f } },
+        .middle = { .curl = { 0.25f, 0.5f, 0.75f, 1.0f } },
+        .ring = { .curl = { 0.25f, 0.5f, 0.75f, 1.0f } },
+        .pinky = { .curl = { 0.25f, 0.5f, 0.75f, 1.0f } },
+    };
+
+    auto joystick = InputPeripheralData();
+    joystick.joystick = {
+        .x = 0.5,
+        .y = 0.5,
+        .press = true,
+    };
+
+    auto buttons = InputPeripheralData();
+    buttons.button_a = { .press = true };
+    buttons.button_calibrate = { .press = true };
+
+    auto gesture = InputPeripheralData();
+    gesture.pinch = { true };
+    gesture.grab = { true };
+
+    auto halfCurlSplay = InputPeripheralData(halfCurl);
+    halfCurlSplay.curl = {
+        .thumb = { .curl_total = 0.5 },
+        .index = { .curl_total = 0.5 },
+        .middle = { .curl_total = 0.5 },
+        .ring = { .curl_total = 0.5 },
+        .pinky = { .curl_total = 0.5 },
+    };
+    halfCurlSplay.splay = {
+        .thumb = 0.5,
+        .index = 0.5,
+        .middle = 0.5,
+        .ring = 0.5,
+        .pinky = 0.5,
+    };
+
     const std::vector<std::tuple<InputPeripheralData, std::string>> cases = {
         {
-          InputPeripheralData{},
+          defaultEmpty,
           "A0B0C0D0E0\n",
         },
         {
-            InputPeripheralData({
-                .curl = {
-                    .thumb = {
-                        .curl_total = 0.5,
-                    },
-                    .index = {
-                        .curl_total = 0.5,
-                    },
-                    .middle = {
-                        .curl_total = 0.5,
-                    },
-                    .ring = {
-                        .curl_total = 0.5,
-                    },
-                    .pinky = {
-                        .curl_total = 0.5,
-                    },
-                },
-            }),
-            "A2047B2047C2047D2047E2047\n",
+          halfCurl,
+          "A2047B2047C2047D2047E2047\n",
         },
         {
-            InputPeripheralData({
-                .curl = {
-                    .thumb = {
-                        .curl_total = 0.0,
-                    },
-                    .index = {
-                        .curl_total = 0.0,
-                    },
-                    .middle = {
-                        .curl_total = 0.0,
-                    },
-                    .ring = {
-                        .curl_total = 0.0,
-                    },
-                    .pinky = {
-                        .curl_total = 0.0,
-                    },
-                },
-            }),
-            "A0B0C0D0E0\n",
+          zeroCurl,
+          "A0B0C0D0E0\n",
         },
         {
-            InputPeripheralData({
-                .curl = {
-                    .thumb = {
-                        .curl_total = 1.0,
-                    },
-                    .index = {
-                        .curl_total = 1.0,
-                    },
-                    .middle = {
-                        .curl_total = 1.0,
-                    },
-                    .ring = {
-                        .curl_total = 1.0,
-                    },
-                    .pinky = {
-                        .curl_total = 1.0,
-                    },
-                },
-            }),
-            "A4095B4095C4095D4095E4095\n",
+          fullCurl,
+          "A4095B4095C4095D4095E4095\n",
         },
         {
-          InputPeripheralData({
-                .splay = {
-                    .thumb = 0.5,
-                    .index = 0.5,
-                    .middle = 0.5,
-                    .ring = 0.5,
-                    .pinky = 0.5,
-                },
-            }),
-            "A0(AB)2047B0(BB)2047C0(CB)2047D0(DB)2047E0(EB)2047\n",
+          halfSplay,
+          "A0(AB)2047B0(BB)2047C0(CB)2047D0(DB)2047E0(EB)2047\n",
         },
         {
-          InputPeripheralData({
-            .curl = {
-              .thumb = {
-                .curl = { 0.25f, 0.5f, 0.75f, 1.0f },
-              },
-              .index = {
-                .curl = { 0.25f, 0.5f, 0.75f, 1.0f },
-              },
-              .middle = {
-                .curl = { 0.25f, 0.5f, 0.75f, 1.0f },
-              },
-              .ring = {
-                .curl = { 0.25f, 0.5f, 0.75f, 1.0f },
-              },
-              .pinky = {
-                .curl = { 0.25f, 0.5f, 0.75f, 1.0f },
-              },
-            },
-          }),
+          increasingJoints,
           "A1023(AAB)2047(AAC)3071(AAD)4095B1023(BAB)2047(BAC)3071(BAD)4095C1023(CAB)2047(CAC)3071(CAD)4095D1023(DAB)2047(DAC)3071(DAD)4095E1023(EAB)2047(EAC)3071(EAD)4095\n",
         },
         {
-          InputPeripheralData({
-            .joystick = {
-              .x = 0.5,
-              .y = 0.5,
-              .press = true,
-            },
-          }),
-          "A0B0C0D0E0F2047G2047H\n"
+          joystick,
+          "A0B0C0D0E0F2047G2047H\n",
         },
         {
-          InputPeripheralData{
-            .button_a = { .press = true },
-            .button_calibrate = { .press = true },
-          },
-          "A0B0C0D0E0JO\n"
+          buttons,
+          "A0B0C0D0E0JO\n",
         },
         {
-          InputPeripheralData{
-            .pinch = { true },
-            .grab = { true },
-          },
-          "A0B0C0D0E0ML\n"
+          gesture,
+          "A0B0C0D0E0ML\n",
         },
         {
-          InputPeripheralData({
-                .curl = {
-                    .thumb = {
-                        .curl_total = 0.5,
-                    },
-                    .index = {
-                        .curl_total = 0.5,
-                    },
-                    .middle = {
-                        .curl_total = 0.5,
-                    },
-                    .ring = {
-                        .curl_total = 0.5,
-                    },
-                    .pinky = {
-                        .curl_total = 0.5,
-                    },
-                },
-                .splay = {
-                    .thumb = 0.5,
-                    .index = 0.5,
-                    .middle = 0.5,
-                    .ring = 0.5,
-                    .pinky = 0.5,
-                },
-          }),
+          halfCurlSplay,
           "A2047(AB)2047B2047(BB)2047C2047(CB)2047D2047(DB)2047E2047(EB)2047\n",
         }
     };
 
-    for (const auto& [data, expected] : cases) {
+    for (auto i = 0; i < cases.size(); i++) {
+        const auto [data, expected] = cases[i];
         const auto encoded = encoder->encode_input(data);
-        TEST_ASSERT_EQUAL_STRING(expected.c_str(), encoded.c_str());
+        TEST_ASSERT_EQUAL_STRING_MESSAGE(
+          expected.c_str(),
+          encoded.c_str(),
+          ("Failed case " + std::to_string(i)).c_str()
+        );
     }
 }
 

From dc59f8721b0133b081d39d3fda5234049c5a44c3 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Mon, 19 Feb 2024 21:18:49 +0400
Subject: [PATCH 77/82] feat(Arduino): bring back INA219 Current Sensor

---
 .../arduino/input/sensor/ina219.hpp           | 25 +++++++++++++++++++
 1 file changed, 25 insertions(+)
 create mode 100644 lib/arduino/senseshift/arduino/input/sensor/ina219.hpp

diff --git a/lib/arduino/senseshift/arduino/input/sensor/ina219.hpp b/lib/arduino/senseshift/arduino/input/sensor/ina219.hpp
new file mode 100644
index 00000000..a2f4e6eb
--- /dev/null
+++ b/lib/arduino/senseshift/arduino/input/sensor/ina219.hpp
@@ -0,0 +1,25 @@
+#pragma once
+
+#include <cstdint>
+
+#include <senseshift/input/sensor.hpp>
+
+#include <Adafruit_INA219.h>
+
+namespace SenseShift::Arduino::Input {
+    /// INA219 Current sensor
+    /// TODO: refactor to component, that updates the value in multiple sensors (ESPHome style)
+    class INA219CurrentSimpleSensor : public ::SenseShift::Input::IFloatSimpleSensor {
+        Adafruit_INA219 ina219_;
+
+      public:
+        void init() override
+        {
+            if (!ina219_.success()) {
+                log_e("Failed to find INA219 sensor");
+            }
+        }
+
+        [[nodiscard]] inline auto getValue() -> float override { return ina219_.getCurrent_mA(); }
+    };
+} // namespace SenseShift::Arduino::Input
\ No newline at end of file

From 78431bbc41bd9959c83f9ee93719ecd12b03c3b7 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Mon, 19 Feb 2024 21:19:08 +0400
Subject: [PATCH 78/82] chore: remove references of output serial plotter

---
 .github/scripts/get_firmware_name.sh  |   5 -
 .github/workflows/ci.yml              |  10 +-
 .github/workflows/codeql-analysis.yml | 165 +++++++++++++-------------
 platformio.ini                        |   1 -
 4 files changed, 83 insertions(+), 98 deletions(-)

diff --git a/.github/scripts/get_firmware_name.sh b/.github/scripts/get_firmware_name.sh
index 36b1bfb9..120fafe3 100755
--- a/.github/scripts/get_firmware_name.sh
+++ b/.github/scripts/get_firmware_name.sh
@@ -18,11 +18,6 @@ getBhapticsName() {
         target="$target+battery"
     fi
 
-    if [[ $flags =~ SENSESHIFT_SERIAL_PLOTTER=true ]]; then
-        echo "::debug::Serial Plotter is enabled, appending +serialplotter to the target"
-        target="$target+serialplotter"
-    fi
-
     echo "firmware=$target"
     if [[ -n "$GITHUB_ACTIONS" ]]; then
         echo "firmware=$target" >> "$GITHUB_OUTPUT"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6a12df0c..85d5c815 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -16,7 +16,7 @@ on:
 
 jobs:
   build-bhaptics:
-    name: Build ${{ matrix.target }} ${{ matrix.coverage && 'with coverage' || 'without coverage' }}, -D ${{ matrix.battery_flag }} -D ${{ matrix.serial_plotter_flag }} -D ${{ matrix.nimble_flag }}
+    name: Build ${{ matrix.target }} ${{ matrix.coverage && 'with coverage' || 'without coverage' }}, -D ${{ matrix.battery_flag }} -D ${{ matrix.nimble_flag }}
     runs-on: ${{ matrix.os }}
     strategy:
       fail-fast: false
@@ -33,7 +33,6 @@ jobs:
           - bhaptics_tactvisor
           - bhaptics_tactglove_right
         battery_flag: [ SENSESHIFT_BATTERY_ENABLED=true ]
-        serial_plotter_flag: [ SENSESHIFT_SERIAL_PLOTTER=false ]
         nimble_flag: [ SENSESHIFT_BLE_USE_NIMBLE=false ]
         coverage: [ false ]
 
@@ -43,19 +42,16 @@ jobs:
             os: ubuntu-latest
             coverage: true
             battery_flag: SENSESHIFT_BATTERY_ENABLED=true
-            serial_plotter_flag: SENSESHIFT_SERIAL_PLOTTER=true
             nimble_flag: SENSESHIFT_BLE_USE_NIMBLE=true
           # - target: bhaptics_tactsuit_x40
           #   os: ubuntu-latest
           #   coverage: true
           #   battery_flag: SENSESHIFT_BATTERY_ENABLED=true
-          #   serial_plotter_flag: SENSESHIFT_SERIAL_PLOTTER=true
           #   nimble_flag: SENSESHIFT_BLE_USE_NIMBLE=false
           - target: bhaptics_tactsuit_x40
             os: ubuntu-latest
             coverage: false
             battery_flag: SENSESHIFT_BATTERY_ENABLED=true
-            serial_plotter_flag: SENSESHIFT_SERIAL_PLOTTER=false
             nimble_flag: SENSESHIFT_BLE_USE_NIMBLE=true
 
     steps:
@@ -66,7 +62,7 @@ jobs:
       - name: Get firmware name
         id: firmware_name
         run: |
-          ./.github/scripts/get_firmware_name.sh ${{ matrix.target }} ${{ matrix.serial_plotter_flag }} ${{ matrix.battery_flag }} ${{ matrix.nimble_flag }}
+          ./.github/scripts/get_firmware_name.sh ${{ matrix.target }} ${{ matrix.battery_flag }} ${{ matrix.nimble_flag }}
 
       - name: Enable coverage (non-macOS)
         if: runner.os != 'macOS' && matrix.coverage
@@ -81,13 +77,11 @@ jobs:
         if: runner.os != 'macOS'
         run: |
           sed -i '/__OH_FIRMWARE__/p; s/__OH_FIRMWARE__/${{ matrix.battery_flag }}/' platformio.ini
-          sed -i '/__OH_FIRMWARE__/p; s/__OH_FIRMWARE__/${{ matrix.serial_plotter_flag }}/' platformio.ini
           sed -i '/__OH_FIRMWARE__/p; s/__OH_FIRMWARE__/${{ matrix.nimble_flag }}/' platformio.ini
       - name: Update build flags (macOS)
         if: runner.os == 'macOS'
         run: |
           sed -i '' '/__OH_FIRMWARE__/p; s/__OH_FIRMWARE__/${{ matrix.battery_flag }}/' platformio.ini
-          sed -i '' '/__OH_FIRMWARE__/p; s/__OH_FIRMWARE__/${{ matrix.serial_plotter_flag }}/' platformio.ini
           sed -i '' '/__OH_FIRMWARE__/p; s/__OH_FIRMWARE__/${{ matrix.nimble_flag }}/' platformio.ini
 
       - name: Speedup package installation
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index ed8f6731..8cfbff2c 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -32,89 +32,86 @@ jobs:
         # Enabling all flags to test build for every feature
         battery_flag:
           - SENSESHIFT_BATTERY_ENABLED=true
-        serial_plotter_flag:
-          - SENSESHIFT_SERIAL_PLOTTER=true
 
     steps:
-    - name: Checkout repository
-      uses: actions/checkout@v3
-      with:
-        # We must fetch at least the immediate parents so that if this is
-        # a pull request then we can checkout the head.
-        fetch-depth: 2
-        submodules: 'recursive'
-
-    # If this run was triggered by a pull request event, then checkout
-    # the head of the pull request instead of the merge commit.
-    # - run: git checkout HEAD^2
-    #   if: ${{ github.event_name == 'pull_request' }}
-
-    # Initializes the CodeQL tools for scanning.
-    - name: Initialize CodeQL
-      uses: github/codeql-action/init@v2
-      with:
-        languages: ${{ matrix.language }}
-        config-file: ./.github/codeql/codeql-config.yml
-        # If you wish to specify custom queries, you can do so here or in a config file.
-        # By default, queries listed here will override any specified in a config file.
-        # Prefix the list here with "+" to use these queries and those in the config file.
-
-        # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
-        # queries: security-extended,security-and-quality
-
-
-    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
-    # If this step fails, then you should remove it and run the build manually (see below)
-    # - name: Autobuild
-    #   uses: github/codeql-action/autobuild@v2
-
-    # ℹ️ Command-line programs to run using the OS shell.
-    # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
-
-    #   If the Autobuild fails above, remove it and uncomment the following three lines.
-    #   modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
-
-    # - run: |
-    #   echo "Run, Build Application using script"
-    #   ./location_of_script_within_repo/buildscript.sh
-
-    - name: Cache pip
-      uses: actions/cache@v3
-      with:
-        path: ~/.cache/pip
-        key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
-        restore-keys: |
-          ${{ runner.os }}-pip-
-    - name: Cache PlatformIO
-      uses: actions/cache@v3
-      with:
-        path: ~/.platformio
-        key: ${{ runner.os }}-pio-${{ hashFiles('**/lockfiles') }}
-        restore-keys: |
-          ${{ runner.os }}-pio-
-    - name: Set up Python
-      uses: actions/setup-python@v4
-      with:
-        python-version: '3.9'
-
-    - name: Update build flags (non-macOS)
-      if: runner.os != 'macOS'
-      run: |
-        sed -i '/__OH_FIRMWARE__/p; s/__OH_FIRMWARE__/${{ matrix.battery_flag }}/' platformio.ini
-        sed -i '/__OH_FIRMWARE__/p; s/__OH_FIRMWARE__/${{ matrix.serial_plotter_flag }}/' platformio.ini
-
-    - name: Install PlatformIO
-      run: |
-        python -m pip install --upgrade pip
-        pip install --upgrade platformio
-        pio upgrade --dev
-        pio pkg update --global
-
-    - name: Build
-      run: |
-        pio run --environment bhaptics_tactsuit_x40
-
-    - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@v2
-      with:
-        category: "/language:${{matrix.language}}"
+      - name: Checkout repository
+        uses: actions/checkout@v3
+        with:
+          # We must fetch at least the immediate parents so that if this is
+          # a pull request then we can checkout the head.
+          fetch-depth: 2
+          submodules: 'recursive'
+
+      # If this run was triggered by a pull request event, then checkout
+      # the head of the pull request instead of the merge commit.
+      # - run: git checkout HEAD^2
+      #   if: ${{ github.event_name == 'pull_request' }}
+
+      # Initializes the CodeQL tools for scanning.
+      - name: Initialize CodeQL
+        uses: github/codeql-action/init@v2
+        with:
+          languages: ${{ matrix.language }}
+          config-file: ./.github/codeql/codeql-config.yml
+          # If you wish to specify custom queries, you can do so here or in a config file.
+          # By default, queries listed here will override any specified in a config file.
+          # Prefix the list here with "+" to use these queries and those in the config file.
+
+          # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
+          # queries: security-extended,security-and-quality
+
+
+      # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
+      # If this step fails, then you should remove it and run the build manually (see below)
+      # - name: Autobuild
+      #   uses: github/codeql-action/autobuild@v2
+
+      # ℹ️ Command-line programs to run using the OS shell.
+      # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
+
+      #   If the Autobuild fails above, remove it and uncomment the following three lines.
+      #   modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
+
+      # - run: |
+      #   echo "Run, Build Application using script"
+      #   ./location_of_script_within_repo/buildscript.sh
+
+      - name: Cache pip
+        uses: actions/cache@v3
+        with:
+          path: ~/.cache/pip
+          key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
+          restore-keys: |
+            ${{ runner.os }}-pip-
+      - name: Cache PlatformIO
+        uses: actions/cache@v3
+        with:
+          path: ~/.platformio
+          key: ${{ runner.os }}-pio-${{ hashFiles('**/lockfiles') }}
+          restore-keys: |
+            ${{ runner.os }}-pio-
+      - name: Set up Python
+        uses: actions/setup-python@v4
+        with:
+          python-version: '3.9'
+
+      - name: Update build flags (non-macOS)
+        if: runner.os != 'macOS'
+        run: |
+          sed -i '/__OH_FIRMWARE__/p; s/__OH_FIRMWARE__/${{ matrix.battery_flag }}/' platformio.ini
+
+      - name: Install PlatformIO
+        run: |
+          python -m pip install --upgrade pip
+          pip install --upgrade platformio
+          pio upgrade --dev
+          pio pkg update --global
+
+      - name: Build
+        run: |
+          pio run --environment bhaptics_tactsuit_x40
+
+      - name: Perform CodeQL Analysis
+        uses: github/codeql-action/analyze@v2
+        with:
+          category: "/language:${{matrix.language}}"
diff --git a/platformio.ini b/platformio.ini
index 7262c036..e6d2c7c4 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -28,7 +28,6 @@ build_flags   =
     -D CORE_DEBUG_LEVEL=3
 ;   -D DEBUG_MODE=0
 ;   -D DEBUG_ESP_PORT=Serial
-;   -D SENSESHIFT_SERIAL_PLOTTER=true
 ;   -D SENSESHIFT_BATTERY_ENABLED=true
 ;   -D SENSESHIFT_BLE_USE_NIMBLE=true
 

From bf53c10b7ae08ead2d4d6578108e977eb26b33cc Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Thu, 22 Feb 2024 17:21:59 +0400
Subject: [PATCH 79/82] perf(OpenGloves): remove unoptimized float casting
 (#96)

* perf(OpenGloves): remove unoptimized float casting

* style: lint-fix
---
 examples/float_benchmark/float_benchmark.ino | 129 +++++++++++++++++++
 lib/opengloves/opengloves/opengloves.cpp     |  32 +----
 2 files changed, 130 insertions(+), 31 deletions(-)
 create mode 100644 examples/float_benchmark/float_benchmark.ino

diff --git a/examples/float_benchmark/float_benchmark.ino b/examples/float_benchmark/float_benchmark.ino
new file mode 100644
index 00000000..99a7da2e
--- /dev/null
+++ b/examples/float_benchmark/float_benchmark.ino
@@ -0,0 +1,129 @@
+#include <Arduino.h>
+
+#ifdef ESP32
+#include <WiFi.h>
+#else
+#include <ESP8266WiFi.h>
+#endif
+
+#include <inttypes.h>
+
+#define TIMES_TO_LOOP 1000000
+
+/** by Vlad Kaipetsky
+portable assuming FP24 set to nearest rounding mode
+efficient on x86 platform
+*/
+inline int toInt(float fval)
+{
+    static const float Snapper = 3 << 22;
+
+    union UFloatInt {
+        int i;
+        float f;
+    };
+
+    assert(fabs(fval) <= 0x003fffff); // only 23 bit values handled
+    UFloatInt& fi = *(UFloatInt*) &fval;
+    fi.f += Snapper;
+    return ((fi.i) & 0x007fffff) - 0x00400000;
+}
+
+inline auto ifloor_toInt(float x) -> int
+{
+    const auto casted = toInt(x);
+    return casted - (x < casted);
+}
+
+inline int i_cast(float value)
+{
+    return static_cast<int>(value);
+}
+
+inline auto ifloor_cast(float x) -> int
+{
+    const auto casted = i_cast(x);
+    return casted - (x < casted);
+}
+
+inline int ifloor_std_cast(float x)
+{
+    return static_cast<int>(std::floor(x));
+}
+
+volatile int xi, yi, resulti;
+volatile float xf, yf, resultf;
+
+uint32_t seed32()
+{
+    return random(0, 0xffffffff);
+}
+
+float seedfloat()
+{
+    // float x, y;
+
+    // x = seed32();
+    // y = seed32();
+
+    // return x / y;
+    return 4094.8492342342932034F;
+}
+
+void setup()
+{
+    Serial.begin(115200);
+    Serial.println("Preparing system...");
+
+    // Turn WiFi off for consistency
+    WiFi.mode(WIFI_OFF);
+
+    delay(1000);
+    Serial.println("Starting benchmark");
+
+    uint32_t i;
+    uint64_t micros_start, micros_end;
+
+    xf = seedfloat();
+    yf = seedfloat();
+
+    micros_start = micros();
+    for (i = 0; i < TIMES_TO_LOOP; i++)
+        resulti = i_cast(xf);
+    micros_end = micros();
+    Serial.printf("i_cast(%f) => %i\t\tmicros: ", xf, resulti);
+    Serial.println(micros_end - micros_start);
+
+    micros_start = micros();
+    for (i = 0; i < TIMES_TO_LOOP; i++)
+        resulti = ifloor_cast(xf);
+    micros_end = micros();
+    Serial.printf("ifloor_cast(%f) => %i\tmicros: ", xf, resulti);
+    Serial.println(micros_end - micros_start);
+
+    micros_start = micros();
+    for (i = 0; i < TIMES_TO_LOOP; i++)
+        resulti = ifloor_std_cast(xf);
+    micros_end = micros();
+    Serial.printf("ifloor_std_cast(%f) => %i\tmicros: ", xf, resulti);
+    Serial.println(micros_end - micros_start);
+
+    micros_start = micros();
+    for (i = 0; i < TIMES_TO_LOOP; i++)
+        resulti = toInt(xf);
+    micros_end = micros();
+    Serial.printf("toInt(%f) => %i\t\tmicros: ", xf, resulti);
+    Serial.println(micros_end - micros_start);
+
+    micros_start = micros();
+    for (i = 0; i < TIMES_TO_LOOP; i++)
+        resulti = ifloor_toInt(xf);
+    micros_end = micros();
+    Serial.printf("ifloor_toInt(%f) => %i\tmicros: ", xf, resulti);
+    Serial.println(micros_end - micros_start);
+}
+
+void loop()
+{
+    // put your main code here, to run repeatedly:
+}
diff --git a/lib/opengloves/opengloves/opengloves.cpp b/lib/opengloves/opengloves/opengloves.cpp
index 8f3f0c45..010c0204 100644
--- a/lib/opengloves/opengloves/opengloves.cpp
+++ b/lib/opengloves/opengloves/opengloves.cpp
@@ -9,40 +9,10 @@
 
 namespace og {
 
-#ifdef OG_ENCODE_FASTER
-    inline auto ifloor(float d) -> int
-    {
-        union Cast {
-            double d;
-            long l;
-        };
-        volatile Cast c;
-        c.d = d + 6755399441055743.5;
-        return c.l;
-    }
-#elifdef OG_ENCODE_FAST
-    /// Source: https://stackoverflow.com/questions/429632/429812#429812
-    inline int float2int(double d)
-    {
-        union Cast {
-            double d;
-            long l;
-        };
-        volatile Cast c;
-        c.d = d + 6755399441055744;
-        return c.l;
-    }
-
     inline auto ifloor(float x) -> int
     {
-        return float2int(x) - (x < float2int(x));
+        return static_cast<int>(x);
     }
-#else
-    inline auto ifloor(float x) -> int
-    {
-        return static_cast<int>(std::floor(x));
-    }
-#endif
 
     auto AlphaEncoder::encode_input(const InputData& input, char* buffer, size_t length) const -> size_t
     {

From 47ba284bff723b43daf9badbf3568be4ad340739 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Thu, 22 Feb 2024 20:09:29 +0400
Subject: [PATCH 80/82] chore(IDE): stage CLion workspace config (#97)

---
 .gitignore                                   |   1 -
 .idea/.gitignore                             |   8 ++
 .idea/codeStyles/Project.xml                 | 122 +++++++++++++++++++
 .idea/codeStyles/codeStyleConfig.xml         |   5 +
 .idea/editor.xml                             | 102 ++++++++++++++++
 .idea/inspectionProfiles/Project_Default.xml |   6 +
 .idea/misc.xml                               |  17 +++
 .idea/senseshift-firmware.iml                |   2 +
 .idea/vcs.xml                                |   6 +
 9 files changed, 268 insertions(+), 1 deletion(-)
 create mode 100644 .idea/.gitignore
 create mode 100644 .idea/codeStyles/Project.xml
 create mode 100644 .idea/codeStyles/codeStyleConfig.xml
 create mode 100644 .idea/editor.xml
 create mode 100644 .idea/inspectionProfiles/Project_Default.xml
 create mode 100644 .idea/misc.xml
 create mode 100644 .idea/senseshift-firmware.iml
 create mode 100644 .idea/vcs.xml

diff --git a/.gitignore b/.gitignore
index ef82776d..320eb8b6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,7 +9,6 @@
 *.bak
 *.code-workspace
 
-.idea
 cmake-build-debug
 
 # PlatformIO files/dirs
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 00000000..13566b81
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 00000000..9afdb1ca
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,122 @@
+<component name="ProjectCodeStyleConfiguration">
+  <code_scheme name="Project" version="173">
+    <option name="OTHER_INDENT_OPTIONS">
+      <value />
+    </option>
+    <RiderCodeStyleSettings>
+      <option name="/Default/CodeStyle/CodeFormatting/CppClangFormat/EnableClangFormatSupport/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/EditorConfig/EnableClangFormatSupport/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_BINARY_EXPRESSIONS_CHAIN/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_CALLS_CHAIN/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_EXPRESSION/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_FOR_STMT/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTIPLE_DECLARATION/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_TERNARY/@EntryValue" value="ALIGN_ALL" type="string" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_CLASS_DEFINITION/@EntryValue" value="1" type="int" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue" value="2" type="int" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_BLANK_LINES_IN_CODE/@EntryValue" value="2" type="int" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_USER_LINEBREAKS/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_CASE_FROM_SWITCH/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_COMMENT/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INT_ALIGN_EQ/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SIMPLE_BLOCK_STYLE/@EntryValue" value="DO_NOT_CHANGE" type="string" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_COMMA_IN_TEMPLATE_ARGS/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_COMMA_IN_TEMPLATE_PARAMS/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_FOR_SEMICOLON/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_FOR_SEMICOLON/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_UNARY_OPERATOR/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_ARRAY_ACCESS_BRACKETS/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_CAST_EXPRESSION_PARENTHESES/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_INITIALIZER_BRACES/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_METHOD_PARENTHESES/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_INITIALIZER_BRACES/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPECIAL_ELSE_IF_TREATMENT/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_CAST_EXPRESSION_PARENTHESES/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_AFTER_BINARY_OPSIGN/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_TERNARY_OPSIGNS/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/TYPE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/OTHER_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/CASE_BLOCK_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_FUNCTION_DECLARATION/@EntryValue" value="1" type="int" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_FUNCTION_DEFINITION/@EntryValue" value="1" type="int" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_WHILE_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_ELSE_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_CATCH_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/NAMESPACE_INDENTATION/@EntryValue" value="All" type="string" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_ARGUMENT/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_EXTENDS_LIST/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_PARAMETER/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_TYPE_ARGUMENT/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_TYPE_PARAMETER/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_DECLARATIONS/@EntryValue" value="0" type="int" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_ACCESS_SPECIFIERS_FROM_CLASS/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_CLASS_MEMBERS_FROM_ACCESS_SPECIFIERS/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/LINE_BREAK_AFTER_COLON_IN_MEMBER_INITIALIZER_LISTS/@EntryValue" value="ON_SINGLE_LINE" type="string" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/MEMBER_INITIALIZER_LIST_STYLE/@EntryValue" value="DO_NOT_CHANGE" type="string" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_NAMESPACE_DEFINITIONS_ON_SAME_LINE/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_COLON_IN_BITFIELD_DECLARATOR/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_COLON_IN_BITFIELD_DECLARATOR/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_EXTENDS_COLON/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_EXTENDS_COLON/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_FOR_COLON/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_FOR_COLON/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_DATA_MEMBER/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_DATA_MEMBERS/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_METHOD/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_NESTED_DECLARATOR/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_REF_IN_DATA_MEMBER/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_REF_IN_DATA_MEMBERS/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_REF_IN_METHOD/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_ABSTRACT_DECL/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_DATA_MEMBER/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_DATA_MEMBERS/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_METHOD/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_ABSTRACT_DECL/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_DATA_MEMBER/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_DATA_MEMBERS/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_METHOD/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_TEMPLATE_ARGS/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_TEMPLATE_PARAMS/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BETWEEN_CLOSING_ANGLE_BRACKETS_IN_TEMPLATE_ARGS/@EntryValue" value="true" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_TEMPLATE_PARAMS/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_TEMPLATE_ARGS/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_TEMPLATE_PARAMS/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_DECLARATION_PARENTHESES/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_BLOCKS/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_INVOCATION_LPAR/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_AFTER_INVOCATION_LPAR/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_INVOCATION_RPAR/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_DECLARATION_LPAR/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_AFTER_DECLARATION_LPAR/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_DECLARATION_RPAR/@EntryValue" value="false" type="bool" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_ARGUMENTS_STYLE/@EntryValue" value="WRAP_IF_LONG" type="string" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_PARAMETERS_STYLE/@EntryValue" value="WRAP_IF_LONG" type="string" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BREAK_TEMPLATE_DECLARATION/@EntryValue" value="LINE_BREAK" type="string" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/NAMESPACE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/FREE_BLOCK_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INVOCABLE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INITIALIZER_BRACES/@EntryValue" value="END_OF_LINE_NO_SPACE" type="string" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_STYLE/@EntryValue" value="Space" type="string" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_SIZE/@EntryValue" value="4" type="int" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/CONTINUOUS_LINE_INDENT/@EntryValue" value="Double" type="string" />
+      <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/TAB_WIDTH/@EntryValue" value="4" type="int" />
+    </RiderCodeStyleSettings>
+    <clangFormatSettings>
+      <option name="ENABLED" value="true" />
+    </clangFormatSettings>
+    <files>
+      <extensions>
+        <pair source="cpp" header="hpp" fileNamingConvention="NONE" />
+        <pair source="c" header="h" fileNamingConvention="NONE" />
+        <pair source="cu" header="cuh" fileNamingConvention="NONE" />
+        <pair source="ixx" header="" fileNamingConvention="NONE" />
+        <pair source="mxx" header="" fileNamingConvention="NONE" />
+        <pair source="cppm" header="" fileNamingConvention="NONE" />
+        <pair source="ccm" header="" fileNamingConvention="NONE" />
+        <pair source="cxxm" header="" fileNamingConvention="NONE" />
+        <pair source="c++m" header="" fileNamingConvention="NONE" />
+      </extensions>
+    </files>
+  </code_scheme>
+</component>
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 00000000..79ee123c
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+<component name="ProjectCodeStyleConfiguration">
+  <state>
+    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
+  </state>
+</component>
\ No newline at end of file
diff --git a/.idea/editor.xml b/.idea/editor.xml
new file mode 100644
index 00000000..66c86790
--- /dev/null
+++ b/.idea/editor.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="BackendCodeEditorSettings">
+    <option name="/Default/CodeStyle/CodeFormatting/CppClangFormat/EnableClangFormatSupport/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/EditorConfig/EnableClangFormatSupport/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_BINARY_EXPRESSIONS_CHAIN/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_CALLS_CHAIN/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_EXPRESSION/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_FOR_STMT/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTIPLE_DECLARATION/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_TERNARY/@EntryValue" value="ALIGN_ALL" type="string" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_CLASS_DEFINITION/@EntryValue" value="1" type="int" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue" value="2" type="int" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_BLANK_LINES_IN_CODE/@EntryValue" value="2" type="int" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_USER_LINEBREAKS/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_CASE_FROM_SWITCH/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_COMMENT/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INT_ALIGN_EQ/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SIMPLE_BLOCK_STYLE/@EntryValue" value="DO_NOT_CHANGE" type="string" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_COMMA_IN_TEMPLATE_ARGS/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_COMMA_IN_TEMPLATE_PARAMS/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_FOR_SEMICOLON/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_FOR_SEMICOLON/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_UNARY_OPERATOR/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_ARRAY_ACCESS_BRACKETS/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_CAST_EXPRESSION_PARENTHESES/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_INITIALIZER_BRACES/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_METHOD_PARENTHESES/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_INITIALIZER_BRACES/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPECIAL_ELSE_IF_TREATMENT/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_CAST_EXPRESSION_PARENTHESES/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_AFTER_BINARY_OPSIGN/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_TERNARY_OPSIGNS/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/TYPE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/OTHER_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/CASE_BLOCK_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_FUNCTION_DECLARATION/@EntryValue" value="1" type="int" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_FUNCTION_DEFINITION/@EntryValue" value="1" type="int" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_WHILE_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_ELSE_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_CATCH_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/NAMESPACE_INDENTATION/@EntryValue" value="All" type="string" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_ARGUMENT/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_EXTENDS_LIST/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_PARAMETER/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_TYPE_ARGUMENT/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_TYPE_PARAMETER/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_DECLARATIONS/@EntryValue" value="0" type="int" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_ACCESS_SPECIFIERS_FROM_CLASS/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_CLASS_MEMBERS_FROM_ACCESS_SPECIFIERS/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/LINE_BREAK_AFTER_COLON_IN_MEMBER_INITIALIZER_LISTS/@EntryValue" value="ON_SINGLE_LINE" type="string" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/MEMBER_INITIALIZER_LIST_STYLE/@EntryValue" value="DO_NOT_CHANGE" type="string" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_NAMESPACE_DEFINITIONS_ON_SAME_LINE/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_COLON_IN_BITFIELD_DECLARATOR/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_COLON_IN_BITFIELD_DECLARATOR/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_EXTENDS_COLON/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_EXTENDS_COLON/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_FOR_COLON/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_FOR_COLON/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_DATA_MEMBER/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_DATA_MEMBERS/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_METHOD/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_NESTED_DECLARATOR/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_REF_IN_DATA_MEMBER/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_REF_IN_DATA_MEMBERS/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_REF_IN_METHOD/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_ABSTRACT_DECL/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_DATA_MEMBER/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_DATA_MEMBERS/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_METHOD/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_ABSTRACT_DECL/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_DATA_MEMBER/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_DATA_MEMBERS/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_METHOD/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_TEMPLATE_ARGS/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_TEMPLATE_PARAMS/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BETWEEN_CLOSING_ANGLE_BRACKETS_IN_TEMPLATE_ARGS/@EntryValue" value="true" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_TEMPLATE_PARAMS/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_TEMPLATE_ARGS/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_TEMPLATE_PARAMS/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_DECLARATION_PARENTHESES/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_BLOCKS/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_INVOCATION_LPAR/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_AFTER_INVOCATION_LPAR/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_INVOCATION_RPAR/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_DECLARATION_LPAR/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_AFTER_DECLARATION_LPAR/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_DECLARATION_RPAR/@EntryValue" value="false" type="bool" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_ARGUMENTS_STYLE/@EntryValue" value="WRAP_IF_LONG" type="string" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_PARAMETERS_STYLE/@EntryValue" value="WRAP_IF_LONG" type="string" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BREAK_TEMPLATE_DECLARATION/@EntryValue" value="LINE_BREAK" type="string" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/NAMESPACE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/FREE_BLOCK_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INVOCABLE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INITIALIZER_BRACES/@EntryValue" value="END_OF_LINE_NO_SPACE" type="string" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_STYLE/@EntryValue" value="Space" type="string" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_SIZE/@EntryValue" value="4" type="int" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/CONTINUOUS_LINE_INDENT/@EntryValue" value="Double" type="string" />
+    <option name="/Default/CodeStyle/CodeFormatting/CppFormatting/TAB_WIDTH/@EntryValue" value="4" type="int" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 00000000..146e386f
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="IncorrectFormatting" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
+  </profile>
+</component>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 00000000..d858eb18
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ExternalStorageConfigurationManager" enabled="true" />
+  <component name="PlatformIOSettings">
+    <option name="linkedExternalProjectsSettings">
+      <PlatformioProjectSettings>
+        <option name="externalProjectPath" value="$PROJECT_DIR$" />
+        <option name="modules">
+          <set>
+            <option value="$PROJECT_DIR$" />
+          </set>
+        </option>
+      </PlatformioProjectSettings>
+    </option>
+  </component>
+  <component name="PlatformIOWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
+</project>
\ No newline at end of file
diff --git a/.idea/senseshift-firmware.iml b/.idea/senseshift-firmware.iml
new file mode 100644
index 00000000..d986c559
--- /dev/null
+++ b/.idea/senseshift-firmware.iml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module classpath="External" external.linked.project.id="senseshift-firmware" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="PlatformIO" type="CPP_MODULE" version="4" />
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 00000000..35eb1ddf
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>
\ No newline at end of file

From 433793b7f3be3dfe6569a06ecc38d21e4383df5e Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Thu, 22 Feb 2024 22:04:34 +0400
Subject: [PATCH 81/82] feat: deduct interpolation template

---
 .../battery/input/battery_sensor.hpp          |  5 ++-
 lib/core/senseshift/core/helpers.hpp          | 26 ++++++++-------
 lib/io/senseshift/input/filter.hpp            |  8 ++---
 test/test_core_helpers/main.cpp               | 29 ++++++++---------
 test/test_io_filter/main.cpp                  | 32 ++++++++++++++++++-
 5 files changed, 67 insertions(+), 33 deletions(-)

diff --git a/lib/battery/senseshift/battery/input/battery_sensor.hpp b/lib/battery/senseshift/battery/input/battery_sensor.hpp
index d0314a03..0ac6a840 100644
--- a/lib/battery/senseshift/battery/input/battery_sensor.hpp
+++ b/lib/battery/senseshift/battery/input/battery_sensor.hpp
@@ -46,7 +46,10 @@ namespace SenseShift::Battery::Input {
       protected:
         [[nodiscard]] auto lookupInterpolateLevel(VoltageType voltage) -> float
         {
-            return ::SenseShift::lookup_table_interpolate<VoltageType, float, Container>(*this->lookup_table_, voltage);
+            return ::SenseShift::lookup_table_interpolate_linear<Container, VoltageType, float>(
+              *this->lookup_table_,
+              voltage
+            );
         }
 
       private:
diff --git a/lib/core/senseshift/core/helpers.hpp b/lib/core/senseshift/core/helpers.hpp
index 1d4fb8b1..2ce47264 100644
--- a/lib/core/senseshift/core/helpers.hpp
+++ b/lib/core/senseshift/core/helpers.hpp
@@ -65,21 +65,25 @@ namespace SenseShift {
 
     /// Lookup a value in a table and interpolate between the two closest values.
     ///
-    /// \tparam Tp
-    /// \tparam To
-    /// \tparam Container
+    /// \tparam Container The type of the lookup table.
+    /// \tparam Tp The type of the lookup table keys.
+    /// \tparam To The type of the lookup table values.
     ///
     /// \param lookup_table Lookup table to use in the format of std::map<Tp, Tp> in descending order.
     /// \param value
     ///
     /// \return
-    template<typename Tp, typename To, typename Container>
-    auto lookup_table_interpolate(Container const& lookup_table, Tp value) -> To
+    template<
+      typename Container,
+      typename Tp = typename Container::key_type,
+      typename To = typename Container::mapped_type>
+    [[nodiscard]] constexpr auto lookup_table_interpolate_linear(Container const& lookup_table, Tp value) -> To
     {
-        static_assert(std::is_same_v<typename Container::key_type, Tp>);
-        static_assert(std::is_same_v<typename Container::mapped_type, To>);
-        static_assert(std::is_arithmetic_v<Tp>, "lookup_table_interpolate only supports arithmetic types");
-        static_assert(std::is_arithmetic_v<To>, "lookup_table_interpolate only supports arithmetic types");
+        static_assert(std::is_same_v<typename Container::key_type, Tp> && std::is_same_v<typename Container::mapped_type, To>);
+        static_assert(
+          std::is_arithmetic_v<Tp> && std::is_arithmetic_v<To>,
+          "lookup_table_interpolate_linear only supports arithmetic types"
+        );
 
         // If the value is outside the range of the lookup table, return the closest value
         if (value <= lookup_table.begin()->first) {
@@ -111,7 +115,7 @@ namespace SenseShift {
         using CallbackType = std::function<void(Ts...)>;
 
         /// Add a callback to the list.
-        void add(std::function<void(Ts...)>&& callback) { this->callbacks_.push_back(std::move(callback)); }
+        void add(CallbackType&& callback) { this->callbacks_.push_back(std::move(callback)); }
 
         /// Call all callbacks in this manager.
         void call(Ts... args)
@@ -126,6 +130,6 @@ namespace SenseShift {
         void operator()(Ts... args) { call(args...); }
 
       private:
-        std::vector<std::function<void(Ts...)>> callbacks_;
+        std::vector<CallbackType> callbacks_;
     };
 } // namespace SenseShift
diff --git a/lib/io/senseshift/input/filter.hpp b/lib/io/senseshift/input/filter.hpp
index b13bc857..fe534ee4 100644
--- a/lib/io/senseshift/input/filter.hpp
+++ b/lib/io/senseshift/input/filter.hpp
@@ -265,7 +265,7 @@ namespace SenseShift::Input::Filter {
     /// Usually used to filter out noise in the joystick.
     class CenterDeadzoneFilter : public IFilter<float> {
       public:
-        explicit CenterDeadzoneFilter(float deadzone, float center = 0.5f) : deadzone_(deadzone), center_(center){};
+        explicit CenterDeadzoneFilter(float deadzone, float center = 0.5F) : deadzone_(deadzone), center_(center){};
 
         auto filter(ISimpleSensor<float>* /*sensor*/, float value) -> float override
         {
@@ -284,9 +284,9 @@ namespace SenseShift::Input::Filter {
     ///
     /// \tparam Tp Type of the lookup table values.
     /// \tparam Container Type of the lookup table container.
-    template<typename Tp, typename Container>
+    template<typename Container, typename Tp = typename Container::mapped_type>
     class LookupTableInterpolationFilter : public IFilter<Tp> {
-        static_assert(std::is_same_v<typename Container::value_type, Tp>);
+        static_assert(std::is_same_v<typename Container::key_type, Tp>);
         static_assert(std::is_arithmetic_v<Tp>, "LookupTableInterpolationFilter only supports arithmetic types");
 
       public:
@@ -294,7 +294,7 @@ namespace SenseShift::Input::Filter {
 
         auto filter(ISimpleSensor<float>* /*sensor*/, Tp value) -> Tp override
         {
-            return SenseShift::lookup_table_interpolate<Tp, Container>(this->lookup_table_, value);
+            return SenseShift::lookup_table_interpolate_linear<Container, Tp, Tp>(this->lookup_table_, value);
         }
 
       private:
diff --git a/test/test_core_helpers/main.cpp b/test/test_core_helpers/main.cpp
index 5dbfd888..e1bcdd29 100644
--- a/test/test_core_helpers/main.cpp
+++ b/test/test_core_helpers/main.cpp
@@ -66,9 +66,6 @@ void test_remap_simple_float(void)
     TEST_ASSERT_EQUAL_FLOAT(255.0f, remap_simple(1.0f, 1.0f, 255.0f));
 }
 
-// Wtf? https://stackoverflow.com/questions/4295890
-#define COMMAE ,
-
 void test_lookup_table_interpolate_float(void)
 {
     const std::map<float, float> table = {
@@ -78,19 +75,19 @@ void test_lookup_table_interpolate_float(void)
         { 1.0f, 18.0f },
     };
 
-    TEST_ASSERT_EQUAL_FLOAT(13.0f, lookup_table_interpolate<float COMMAE float>(table, 0.0f));
-    TEST_ASSERT_EQUAL_FLOAT(13.6f, lookup_table_interpolate<float COMMAE float>(table, 0.1f));
-    TEST_ASSERT_EQUAL_FLOAT(14.2f, lookup_table_interpolate<float COMMAE float>(table, 0.2f));
-    TEST_ASSERT_EQUAL_FLOAT(14.5f, lookup_table_interpolate<float COMMAE float>(table, 0.25f));
-    TEST_ASSERT_EQUAL_FLOAT(14.8f, lookup_table_interpolate<float COMMAE float>(table, 0.3f));
-    TEST_ASSERT_EQUAL_FLOAT(15.4f, lookup_table_interpolate<float COMMAE float>(table, 0.4f));
-    TEST_ASSERT_EQUAL_FLOAT(16.0f, lookup_table_interpolate<float COMMAE float>(table, 0.5f));
-    TEST_ASSERT_EQUAL_FLOAT(16.5f, lookup_table_interpolate<float COMMAE float>(table, 0.55f));
-    TEST_ASSERT_EQUAL_FLOAT(17.0f, lookup_table_interpolate<float COMMAE float>(table, 0.6f));
-    TEST_ASSERT_EQUAL_FLOAT(17.25f, lookup_table_interpolate<float COMMAE float>(table, 0.7f));
-    TEST_ASSERT_EQUAL_FLOAT(17.5f, lookup_table_interpolate<float COMMAE float>(table, 0.8f));
-    TEST_ASSERT_EQUAL_FLOAT(17.75f, lookup_table_interpolate<float COMMAE float>(table, 0.9f));
-    TEST_ASSERT_EQUAL_FLOAT(18.0f, lookup_table_interpolate<float COMMAE float>(table, 1.0f));
+    TEST_ASSERT_EQUAL_FLOAT(13.0f, lookup_table_interpolate_linear(table, 0.0f));
+    TEST_ASSERT_EQUAL_FLOAT(13.6f, lookup_table_interpolate_linear(table, 0.1f));
+    TEST_ASSERT_EQUAL_FLOAT(14.2f, lookup_table_interpolate_linear(table, 0.2f));
+    TEST_ASSERT_EQUAL_FLOAT(14.5f, lookup_table_interpolate_linear(table, 0.25f));
+    TEST_ASSERT_EQUAL_FLOAT(14.8f, lookup_table_interpolate_linear(table, 0.3f));
+    TEST_ASSERT_EQUAL_FLOAT(15.4f, lookup_table_interpolate_linear(table, 0.4f));
+    TEST_ASSERT_EQUAL_FLOAT(16.0f, lookup_table_interpolate_linear(table, 0.5f));
+    TEST_ASSERT_EQUAL_FLOAT(16.5f, lookup_table_interpolate_linear(table, 0.55f));
+    TEST_ASSERT_EQUAL_FLOAT(17.0f, lookup_table_interpolate_linear(table, 0.6f));
+    TEST_ASSERT_EQUAL_FLOAT(17.25f, lookup_table_interpolate_linear(table, 0.7f));
+    TEST_ASSERT_EQUAL_FLOAT(17.5f, lookup_table_interpolate_linear(table, 0.8f));
+    TEST_ASSERT_EQUAL_FLOAT(17.75f, lookup_table_interpolate_linear(table, 0.9f));
+    TEST_ASSERT_EQUAL_FLOAT(18.0f, lookup_table_interpolate_linear(table, 1.0f));
 }
 
 int process(void)
diff --git a/test/test_io_filter/main.cpp b/test/test_io_filter/main.cpp
index f5002158..dc3e77f2 100644
--- a/test/test_io_filter/main.cpp
+++ b/test/test_io_filter/main.cpp
@@ -2,6 +2,8 @@
 #include <senseshift/input/sensor.hpp>
 #include <unity.h>
 
+#include <map>
+
 #define ASSERT_EQUAL_FLOAT_ROUNDED(expected, actual, precision)                \
     TEST_ASSERT_EQUAL_FLOAT(                                                   \
       std::round(expected* std::pow(10, precision)) / std::pow(10, precision), \
@@ -134,6 +136,33 @@ void test_center_deadzone_filter(void)
     TEST_ASSERT_EQUAL_FLOAT(1.0f, filter->filter(nullptr, 1.0f));
 }
 
+void test_lookup_table_interpolate_linear_filter(void)
+{
+    const std::map<float, float> lookup_table = {
+        { 0.0f, 0.0f }, { 1.0f, 3.5f }, { 2.0f, 7.0f }, { 3.0f, 10.5f }, { 4.0f, 14.0f }, { 5.0f, 17.5f },
+    };
+    IFilter<float>* filter = new LookupTableInterpolationFilter(lookup_table);
+
+    // test existing values
+    TEST_ASSERT_EQUAL_FLOAT(0.0f, filter->filter(nullptr, 0.0f));
+    TEST_ASSERT_EQUAL_FLOAT(3.5f, filter->filter(nullptr, 1.0f));
+    TEST_ASSERT_EQUAL_FLOAT(7.0f, filter->filter(nullptr, 2.0f));
+    TEST_ASSERT_EQUAL_FLOAT(10.5f, filter->filter(nullptr, 3.0f));
+    TEST_ASSERT_EQUAL_FLOAT(14.0f, filter->filter(nullptr, 4.0f));
+    TEST_ASSERT_EQUAL_FLOAT(17.5f, filter->filter(nullptr, 5.0f));
+
+    // test values in between
+    TEST_ASSERT_EQUAL_FLOAT(1.75f, filter->filter(nullptr, 0.5f));
+    TEST_ASSERT_EQUAL_FLOAT(5.25f, filter->filter(nullptr, 1.5f));
+    TEST_ASSERT_EQUAL_FLOAT(8.75f, filter->filter(nullptr, 2.5f));
+    TEST_ASSERT_EQUAL_FLOAT(12.25f, filter->filter(nullptr, 3.5f));
+    TEST_ASSERT_EQUAL_FLOAT(15.75f, filter->filter(nullptr, 4.5f));
+
+    // test out of range
+    TEST_ASSERT_EQUAL_FLOAT(0.0f, filter->filter(nullptr, -1.0f));
+    TEST_ASSERT_EQUAL_FLOAT(17.5f, filter->filter(nullptr, 6.0f));
+}
+
 int process(void)
 {
     UNITY_BEGIN();
@@ -147,6 +176,7 @@ int process(void)
     RUN_TEST(test_sliding_window_moving_average_filter);
     RUN_TEST(test_exponential_moving_average_filter);
     RUN_TEST(test_center_deadzone_filter);
+    RUN_TEST(test_lookup_table_interpolate_linear_filter);
 
     return UNITY_END();
 }
@@ -169,4 +199,4 @@ int main(int argc, char** argv)
     return process();
 }
 
-#endif
\ No newline at end of file
+#endif

From c7afbe4aad62bf96137e40ae4ba8d0fc5b4e6c75 Mon Sep 17 00:00:00 2001
From: Leonid Meleshin <hello@leon0399.ru>
Date: Thu, 22 Feb 2024 22:25:26 +0400
Subject: [PATCH 82/82] chore: shorten project defines prefix

---
 .github/scripts/get_firmware_name.sh             |  4 ++--
 .github/workflows/ci.yml                         | 16 ++++++++--------
 .github/workflows/codeql-analysis.yml            |  2 +-
 .github/workflows/release.yml                    |  4 ++--
 firmware/mode_configs/bhaptics/tactal.cpp        |  6 +++---
 firmware/mode_configs/bhaptics/tactglove.cpp     |  8 ++++----
 firmware/mode_configs/bhaptics/tactosy2.cpp      |  6 +++---
 firmware/mode_configs/bhaptics/tactosyf.cpp      |  6 +++---
 firmware/mode_configs/bhaptics/tactosyh.cpp      |  6 +++---
 firmware/mode_configs/bhaptics/tactsuit_x16.cpp  |  6 +++---
 .../bhaptics/tactsuit_x16_pca9685.cpp            |  6 +++---
 firmware/mode_configs/bhaptics/tactsuit_x40.cpp  |  6 +++---
 firmware/mode_configs/bhaptics/tactvisor.cpp     |  6 +++---
 include/config/battery.h                         | 16 ++++++++--------
 include/config/bluetooth.h                       |  4 ++--
 ini/bhaptics.ini                                 |  4 ++--
 .../senseshift/bh/ble/connection.cpp             |  8 ++++----
 .../senseshift/bh/ble/connection.hpp             |  2 +-
 platformio.ini                                   |  4 ++--
 19 files changed, 60 insertions(+), 60 deletions(-)

diff --git a/.github/scripts/get_firmware_name.sh b/.github/scripts/get_firmware_name.sh
index 120fafe3..b759981f 100755
--- a/.github/scripts/get_firmware_name.sh
+++ b/.github/scripts/get_firmware_name.sh
@@ -8,12 +8,12 @@ getBhapticsName() {
     echo "::debug::Getting bHaptics name for $target"
     echo "::debug::Flags are $flags"
 
-    if [[ $flags =~ SENSESHIFT_BLE_USE_NIMBLE=true ]]; then
+    if [[ $flags =~ SS_BLE_USE_NIMBLE=true ]]; then
         echo "::debug::Nimble is enabled, appending +nimble to the target"
         target="$target+nimble"
     fi
 
-    if [[ $flags =~ SENSESHIFT_BATTERY_ENABLED=true ]]; then
+    if [[ $flags =~ SS_BATTERY_ENABLED=true ]]; then
         echo "::debug::Battery is enabled, appending +battery to the target"
         target="$target+battery"
     fi
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 85d5c815..0531d546 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -32,8 +32,8 @@ jobs:
           - bhaptics_tactal
           - bhaptics_tactvisor
           - bhaptics_tactglove_right
-        battery_flag: [ SENSESHIFT_BATTERY_ENABLED=true ]
-        nimble_flag: [ SENSESHIFT_BLE_USE_NIMBLE=false ]
+        battery_flag: [ SS_BATTERY_ENABLED=true ]
+        nimble_flag: [ SS_BLE_USE_NIMBLE=false ]
         coverage: [ false ]
 
         include:
@@ -41,18 +41,18 @@ jobs:
           - target: bhaptics_tactsuit_x40
             os: ubuntu-latest
             coverage: true
-            battery_flag: SENSESHIFT_BATTERY_ENABLED=true
-            nimble_flag: SENSESHIFT_BLE_USE_NIMBLE=true
+            battery_flag: SS_BATTERY_ENABLED=true
+            nimble_flag: SS_BLE_USE_NIMBLE=true
           # - target: bhaptics_tactsuit_x40
           #   os: ubuntu-latest
           #   coverage: true
-          #   battery_flag: SENSESHIFT_BATTERY_ENABLED=true
-          #   nimble_flag: SENSESHIFT_BLE_USE_NIMBLE=false
+          #   battery_flag: SS_BATTERY_ENABLED=true
+          #   nimble_flag: SS_BLE_USE_NIMBLE=false
           - target: bhaptics_tactsuit_x40
             os: ubuntu-latest
             coverage: false
-            battery_flag: SENSESHIFT_BATTERY_ENABLED=true
-            nimble_flag: SENSESHIFT_BLE_USE_NIMBLE=true
+            battery_flag: SS_BATTERY_ENABLED=true
+            nimble_flag: SS_BLE_USE_NIMBLE=true
 
     steps:
       - uses: actions/checkout@v3
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 8cfbff2c..423dd5d6 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -31,7 +31,7 @@ jobs:
 
         # Enabling all flags to test build for every feature
         battery_flag:
-          - SENSESHIFT_BATTERY_ENABLED=true
+          - SS_BATTERY_ENABLED=true
 
     steps:
       - name: Checkout repository
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index d2eecde2..9d042345 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -33,9 +33,9 @@ jobs:
           - bhaptics_tactglove_left
           - bhaptics_tactglove_right
         battery_flag:
-          - SENSESHIFT_BATTERY_ENABLED=true
+          - SS_BATTERY_ENABLED=true
         nimble_flag:
-          - SENSESHIFT_BLE_USE_NIMBLE=true
+          - SS_BLE_USE_NIMBLE=true
 
     steps:
       - uses: actions/checkout@v3
diff --git a/firmware/mode_configs/bhaptics/tactal.cpp b/firmware/mode_configs/bhaptics/tactal.cpp
index ec8ea898..cfdd4e9c 100644
--- a/firmware/mode_configs/bhaptics/tactal.cpp
+++ b/firmware/mode_configs/bhaptics/tactal.cpp
@@ -55,7 +55,7 @@ void setupMode()
     );
     bhBleConnection->begin();
 
-#if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
+#if defined(SS_BATTERY_ENABLED) && SS_BATTERY_ENABLED == true
     auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
     batteryVoltageSensor->addFilters({
       new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
@@ -63,8 +63,8 @@ void setupMode()
     });
     auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask<SimpleSensorDecorator<float>>(
       batteryVoltageSensor,
-      SENSESHIFT_BATTERY_SAMPLE_RATE,
-      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+      SS_BATTERY_SAMPLE_RATE,
+      { "ADC Battery", 4096, SS_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     batteryTask->begin();
 
diff --git a/firmware/mode_configs/bhaptics/tactglove.cpp b/firmware/mode_configs/bhaptics/tactglove.cpp
index d67f58e6..d1397408 100644
--- a/firmware/mode_configs/bhaptics/tactglove.cpp
+++ b/firmware/mode_configs/bhaptics/tactglove.cpp
@@ -28,7 +28,7 @@ using namespace SenseShift::Body::Haptics;
 extern Application App;
 Application* app = &App;
 
-static constexpr Body::Hands::HandSide handSide = Body::Hands::HandSide::SENSESHIFT_HAND_SIDE;
+static constexpr Body::Hands::HandSide handSide = Body::Hands::HandSide::SS_HAND_SIDE;
 // clang-format off
 static const auto& bhLayout = handSide == Body::Hands::HandSide::Left ? BH::TactGloveLeftLayout : BH::TactGloveRightLayout;
 // clang-format on
@@ -63,7 +63,7 @@ void setupMode()
     );
     bhBleConnection->begin();
 
-#if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
+#if defined(SS_BATTERY_ENABLED) && SS_BATTERY_ENABLED == true
     auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
     batteryVoltageSensor->addFilters({
       new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
@@ -71,8 +71,8 @@ void setupMode()
     });
     auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask<SimpleSensorDecorator<float>>(
       batteryVoltageSensor,
-      SENSESHIFT_BATTERY_SAMPLE_RATE,
-      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+      SS_BATTERY_SAMPLE_RATE,
+      { "ADC Battery", 4096, SS_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     batteryTask->begin();
 
diff --git a/firmware/mode_configs/bhaptics/tactosy2.cpp b/firmware/mode_configs/bhaptics/tactosy2.cpp
index c3053bd3..ea6373b4 100644
--- a/firmware/mode_configs/bhaptics/tactosy2.cpp
+++ b/firmware/mode_configs/bhaptics/tactosy2.cpp
@@ -56,7 +56,7 @@ void setupMode()
     );
     bhBleConnection->begin();
 
-#if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
+#if defined(SS_BATTERY_ENABLED) && SS_BATTERY_ENABLED == true
     auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
     batteryVoltageSensor->addFilters({
       new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
@@ -64,8 +64,8 @@ void setupMode()
     });
     auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask<SimpleSensorDecorator<float>>(
       batteryVoltageSensor,
-      SENSESHIFT_BATTERY_SAMPLE_RATE,
-      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+      SS_BATTERY_SAMPLE_RATE,
+      { "ADC Battery", 4096, SS_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     batteryTask->begin();
 
diff --git a/firmware/mode_configs/bhaptics/tactosyf.cpp b/firmware/mode_configs/bhaptics/tactosyf.cpp
index b2c47e26..63860af4 100644
--- a/firmware/mode_configs/bhaptics/tactosyf.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyf.cpp
@@ -57,7 +57,7 @@ void setupMode()
     );
     bhBleConnection->begin();
 
-#if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
+#if defined(SS_BATTERY_ENABLED) && SS_BATTERY_ENABLED == true
     auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
     batteryVoltageSensor->addFilters({
       new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
@@ -65,8 +65,8 @@ void setupMode()
     });
     auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask<SimpleSensorDecorator<float>>(
       batteryVoltageSensor,
-      SENSESHIFT_BATTERY_SAMPLE_RATE,
-      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+      SS_BATTERY_SAMPLE_RATE,
+      { "ADC Battery", 4096, SS_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     batteryTask->begin();
 
diff --git a/firmware/mode_configs/bhaptics/tactosyh.cpp b/firmware/mode_configs/bhaptics/tactosyh.cpp
index 11bbe0a4..be8bfd79 100644
--- a/firmware/mode_configs/bhaptics/tactosyh.cpp
+++ b/firmware/mode_configs/bhaptics/tactosyh.cpp
@@ -57,7 +57,7 @@ void setupMode()
     );
     bhBleConnection->begin();
 
-#if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
+#if defined(SS_BATTERY_ENABLED) && SS_BATTERY_ENABLED == true
     auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
     batteryVoltageSensor->addFilters({
       new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
@@ -65,8 +65,8 @@ void setupMode()
     });
     auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask<SimpleSensorDecorator<float>>(
       batteryVoltageSensor,
-      SENSESHIFT_BATTERY_SAMPLE_RATE,
-      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+      SS_BATTERY_SAMPLE_RATE,
+      { "ADC Battery", 4096, SS_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     batteryTask->begin();
 
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
index ae6011f2..bdc9521b 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp
@@ -66,7 +66,7 @@ void setupMode()
     );
     bhBleConnection->begin();
 
-#if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
+#if defined(SS_BATTERY_ENABLED) && SS_BATTERY_ENABLED == true
     auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
     batteryVoltageSensor->addFilters({
       new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
@@ -74,8 +74,8 @@ void setupMode()
     });
     auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask<SimpleSensorDecorator<float>>(
       batteryVoltageSensor,
-      SENSESHIFT_BATTERY_SAMPLE_RATE,
-      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+      SS_BATTERY_SAMPLE_RATE,
+      { "ADC Battery", 4096, SS_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     batteryTask->begin();
 
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
index b8b05c77..2042b582 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp
@@ -71,7 +71,7 @@ void setupMode()
     );
     bhBleConnection->begin();
 
-#if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
+#if defined(SS_BATTERY_ENABLED) && SS_BATTERY_ENABLED == true
     auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
     batteryVoltageSensor->addFilters({
       new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
@@ -79,8 +79,8 @@ void setupMode()
     });
     auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask<SimpleSensorDecorator<float>>(
       batteryVoltageSensor,
-      SENSESHIFT_BATTERY_SAMPLE_RATE,
-      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+      SS_BATTERY_SAMPLE_RATE,
+      { "ADC Battery", 4096, SS_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     batteryTask->begin();
 
diff --git a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
index a3d77b06..09e0dab1 100644
--- a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
+++ b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp
@@ -80,7 +80,7 @@ void setupMode()
     );
     bhBleConnection->begin();
 
-#if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
+#if defined(SS_BATTERY_ENABLED) && SS_BATTERY_ENABLED == true
     auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
     batteryVoltageSensor->addFilters({
       new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
@@ -88,8 +88,8 @@ void setupMode()
     });
     auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask<SimpleSensorDecorator<float>>(
       batteryVoltageSensor,
-      SENSESHIFT_BATTERY_SAMPLE_RATE,
-      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+      SS_BATTERY_SAMPLE_RATE,
+      { "ADC Battery", 4096, SS_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     batteryTask->begin();
 
diff --git a/firmware/mode_configs/bhaptics/tactvisor.cpp b/firmware/mode_configs/bhaptics/tactvisor.cpp
index 38ece6af..b6c618bc 100644
--- a/firmware/mode_configs/bhaptics/tactvisor.cpp
+++ b/firmware/mode_configs/bhaptics/tactvisor.cpp
@@ -55,7 +55,7 @@ void setupMode()
     );
     bhBleConnection->begin();
 
-#if defined(SENSESHIFT_BATTERY_ENABLED) && SENSESHIFT_BATTERY_ENABLED == true
+#if defined(SS_BATTERY_ENABLED) && SS_BATTERY_ENABLED == true
     auto* batteryVoltageSensor = new SimpleSensorDecorator(new AnalogSimpleSensor(36));
     batteryVoltageSensor->addFilters({
       new MultiplyFilter(3.3F),                      // Convert to raw pin voltage
@@ -63,8 +63,8 @@ void setupMode()
     });
     auto* batteryTask = new ::SenseShift::FreeRTOS::ComponentUpdateTask<SimpleSensorDecorator<float>>(
       batteryVoltageSensor,
-      SENSESHIFT_BATTERY_SAMPLE_RATE,
-      { "ADC Battery", 4096, SENSESHIFT_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
+      SS_BATTERY_SAMPLE_RATE,
+      { "ADC Battery", 4096, SS_BATTERY_TASK_PRIORITY, tskNO_AFFINITY }
     );
     batteryTask->begin();
 
diff --git a/include/config/battery.h b/include/config/battery.h
index 67747fa3..219b57f1 100644
--- a/include/config/battery.h
+++ b/include/config/battery.h
@@ -1,17 +1,17 @@
 #pragma once
 
-#ifndef SENSESHIFT_BATTERY_ENABLED
-#define SENSESHIFT_BATTERY_ENABLED false
+#ifndef SS_BATTERY_ENABLED
+#define SS_BATTERY_ENABLED false
 #endif
 
-#ifndef SENSESHIFT_BATTERY_SAMPLE_RATE
-#define SENSESHIFT_BATTERY_SAMPLE_RATE 10000
+#ifndef SS_BATTERY_SAMPLE_RATE
+#define SS_BATTERY_SAMPLE_RATE 10000
 #endif
 
-#ifndef SENSESHIFT_BATTERY_THRESHOLD_PERCENTAGE
-#define SENSESHIFT_BATTERY_THRESHOLD_PERCENTAGE 20
+#ifndef SS_BATTERY_THRESHOLD_PERCENTAGE
+#define SS_BATTERY_THRESHOLD_PERCENTAGE 20
 #endif
 
-#ifndef SENSESHIFT_BATTERY_TASK_PRIORITY
-#define SENSESHIFT_BATTERY_TASK_PRIORITY 1
+#ifndef SS_BATTERY_TASK_PRIORITY
+#define SS_BATTERY_TASK_PRIORITY 1
 #endif
diff --git a/include/config/bluetooth.h b/include/config/bluetooth.h
index 3de034d4..79ac1c95 100644
--- a/include/config/bluetooth.h
+++ b/include/config/bluetooth.h
@@ -11,6 +11,6 @@
     }
 #endif
 
-#ifndef SENSESHIFT_BLE_USE_NIMBLE
-#define SENSESHIFT_BLE_USE_NIMBLE false
+#ifndef SS_BLE_USE_NIMBLE
+#define SS_BLE_USE_NIMBLE false
 #endif
diff --git a/ini/bhaptics.ini b/ini/bhaptics.ini
index 0d3c4343..a9dc121b 100644
--- a/ini/bhaptics.ini
+++ b/ini/bhaptics.ini
@@ -263,7 +263,7 @@ monitor_speed     = ${bhaptics.monitor_speed}
 build_flags      =
     ${bhaptics.build_flags}
     -D BH_DEVICE_TACTGLOVE
-    -D SENSESHIFT_HAND_SIDE=Left
+    -D SS_HAND_SIDE=Left
     -D BH_BLE_APPEARANCE=508
     '-D BLUETOOTH_NAME="TactGlove (L"'
     '-D BH_SERIAL_NUMBER={ 0xcd, 0x0b, 0x81, 0x45, 0x85, 0xf9, 0x2b, 0x6c, 0xed, 0x5b }'
@@ -284,7 +284,7 @@ monitor_speed     = ${bhaptics.monitor_speed}
 build_flags      =
     ${bhaptics.build_flags}
     -D BH_DEVICE_TACTGLOVE
-    -D SENSESHIFT_HAND_SIDE=Right
+    -D SS_HAND_SIDE=Right
     -D BH_BLE_APPEARANCE=508
     '-D BLUETOOTH_NAME="TactGlove (R"'
     '-D BH_SERIAL_NUMBER={ 0x12, 0x0b, 0xae, 0xbf, 0xbc, 0x90, 0x3b, 0x0d, 0x84, 0xdd }'
diff --git a/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp b/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
index db642a43..2e05de3a 100644
--- a/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
+++ b/lib/bhaptics_ble/senseshift/bh/ble/connection.cpp
@@ -6,7 +6,7 @@
 
 #include <Arduino.h>
 
-#if defined(SENSESHIFT_BLE_USE_NIMBLE) && SENSESHIFT_BLE_USE_NIMBLE == true
+#if defined(SS_BLE_USE_NIMBLE) && SS_BLE_USE_NIMBLE == true
 // BLE2902 not needed: https://github.com/h2zero/NimBLE-Arduino/blob/release/1.4/docs/Migration_guide.md#descriptors
 
 #define PROPERTY_READ NIMBLE_PROPERTY::READ
@@ -77,7 +77,7 @@ namespace SenseShift::BH::BLE {
             );
         };
 
-#if defined(SENSESHIFT_BLE_USE_NIMBLE) && SENSESHIFT_BLE_USE_NIMBLE == true
+#if defined(SS_BLE_USE_NIMBLE) && SS_BLE_USE_NIMBLE == true
         void onStatus(BLECharacteristic* pCharacteristic, Status s, int code) override
 #else
         void onStatus(BLECharacteristic* pCharacteristic, Status s, uint32_t code) override
@@ -182,7 +182,7 @@ namespace SenseShift::BH::BLE {
                 | PROPERTY_NOTIFY // for whatever reason, it have to be writable, otherwise Desktop app crashes
             );
 
-#if !defined(SENSESHIFT_BLE_USE_NIMBLE) || SENSESHIFT_BLE_USE_NIMBLE != true
+#if !defined(SS_BLE_USE_NIMBLE) || SS_BLE_USE_NIMBLE != true
             batteryChar->addDescriptor(new BLE2902());
 #endif
 
@@ -209,7 +209,7 @@ namespace SenseShift::BH::BLE {
             );
             monitorChar->setCallbacks(new LogOutputCharCallbacks());
 
-#if !defined(SENSESHIFT_BLE_USE_NIMBLE) || SENSESHIFT_BLE_USE_NIMBLE != true
+#if !defined(SS_BLE_USE_NIMBLE) || SS_BLE_USE_NIMBLE != true
             monitorChar->addDescriptor(new BLE2902());
 #endif
 
diff --git a/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp b/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
index e5033fb1..c35810b4 100644
--- a/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
+++ b/lib/bhaptics_ble/senseshift/bh/ble/connection.hpp
@@ -10,7 +10,7 @@
 #include <Arduino.h>
 #include <esp_wifi.h>
 
-#if defined(SENSESHIFT_BLE_USE_NIMBLE) && SENSESHIFT_BLE_USE_NIMBLE == true
+#if defined(SS_BLE_USE_NIMBLE) && SS_BLE_USE_NIMBLE == true
 #include <NimBLEDevice.h>
 #else
 #include <BLEDevice.h>
diff --git a/platformio.ini b/platformio.ini
index e6d2c7c4..fb8423bb 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -28,8 +28,8 @@ build_flags   =
     -D CORE_DEBUG_LEVEL=3
 ;   -D DEBUG_MODE=0
 ;   -D DEBUG_ESP_PORT=Serial
-;   -D SENSESHIFT_BATTERY_ENABLED=true
-;   -D SENSESHIFT_BLE_USE_NIMBLE=true
+;   -D SS_BATTERY_ENABLED=true
+;   -D SS_BLE_USE_NIMBLE=true
 
 build_src_filter =
     +<*>