From 765f39283df14de8951b42f6be404a6d055dc668 Mon Sep 17 00:00:00 2001 From: Leonid Meleshin Date: Wed, 10 May 2023 14:23:36 +0400 Subject: [PATCH 01/23] feat(bHaptics): add TactVisor config --- .github/workflows/ci.yml | 1 + .github/workflows/release.yml | 1 + firmware/mode_configs/bhaptics/tactvisor.cpp | 59 ++++++++++++++++++++ ini/bhaptics.ini | 38 ++++++++++++- lib/bhaptics/bh_constants.hpp | 18 +++++- 5 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 firmware/mode_configs/bhaptics/tactvisor.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0298c543..86fa91ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,7 @@ jobs: - bhaptics_tactosyh_hand_right - bhaptics_tactosyf_foot_right - bhaptics_tactal + - bhaptics_tactvisor - bhaptics_tactglove_right battery_flag: [ BATTERY_ENABLED=true ] serial_plotter_flag: [ SERIAL_PLOTTER=false ] diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3cae90cd..af4b6c9c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,6 +25,7 @@ jobs: - bhaptics_tactosyf_foot_left - bhaptics_tactosyf_foot_right - bhaptics_tactal + - bhaptics_tactvisor - bhaptics_tactglove_left - bhaptics_tactglove_right battery_flag: diff --git a/firmware/mode_configs/bhaptics/tactvisor.cpp b/firmware/mode_configs/bhaptics/tactvisor.cpp new file mode 100644 index 00000000..8127ae21 --- /dev/null +++ b/firmware/mode_configs/bhaptics/tactvisor.cpp @@ -0,0 +1,59 @@ +// Override you configs in this file (Ctrl+Click) +#include "config/all.h" + +#include +#include + +#include "senseshift.h" + +#include +#include +#include + +#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true +#include "battery/adc_naive.hpp" +#endif + +using namespace OH; +using namespace BH; + +extern SenseShift App; +SenseShift* app = &App; + +static const size_t bhLayoutSize = BH_LAYOUT_TACTVISOR_SIZE; +static const oh_output_point_t* bhLayout[bhLayoutSize] = BH_LAYOUT_TACTVISOR; + +void setupMode() { + // Configure PWM pins to their positions on the face + auto faceOutputs = PlaneMapper_Margin::mapMatrixCoordinates({ + // clang-format off + {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()->setup(); + + uint8_t serialNumber[BH_SERIAL_NUMBER_LENGTH] = BH_SERIAL_NUMBER; + ConnectionBHBLE_Config config = { + .deviceName = BLUETOOTH_NAME, + .appearance = BH_BLE_APPEARANCE, + .serialNumber = serialNumber, + }; + auto* bhBleConnection = new ConnectionBHBLE(config, [](std::string& value)->void { + plainOutputTransformer(app->getHapticBody(), value, bhLayout, bhLayoutSize, OUTPUT_PATH_ACCESSORY); + }, app); + bhBleConnection->begin(); + +#if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true + auto* battery = new ADCNaiveBattery(36, { .sampleRate = BATTERY_SAMPLE_RATE }, &App, tskNO_AFFINITY); + battery->begin(); +#endif +} + +void loopMode() { + // Free up the Arduino loop task + vTaskDelete(NULL); +} diff --git a/ini/bhaptics.ini b/ini/bhaptics.ini index 7b94115c..ee45ae1e 100644 --- a/ini/bhaptics.ini +++ b/ini/bhaptics.ini @@ -67,6 +67,24 @@ build_src_filter = ${bhaptics.build_src_filter} + lib_deps = ${bhaptics.lib_deps} +; [env:bhaptics_tactbelt] +; 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="TactBelt"' +; '-D BH_SERIAL_NUMBER={ 0x86, 0x4b, 0x19, 0xe9, 0x66, 0xab, 0x53, 0xb0, 0xc7, 0xd3 }' +; build_unflags = ${bhaptics.build_unflags} +; build_src_filter = ${bhaptics.build_src_filter} +; + +; lib_deps = ${bhaptics.lib_deps} + [env:bhaptics_tactosy2_forearm_left] platform = ${bhaptics.platform} platform_packages = ${bhaptics.platform_packages} @@ -186,13 +204,31 @@ monitor_speed = ${bhaptics.monitor_speed} build_flags = ${bhaptics.build_flags} -D BH_DEVICE_TACTAL -D BH_BLE_APPEARANCE=508 - '-D BLUETOOTH_NAME="Tactal"' + '-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} + 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} + +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} + + +lib_deps = ${bhaptics.lib_deps} + [env:bhaptics_tactglove_left] platform = ${bhaptics.platform} platform_packages = ${bhaptics.platform_packages} diff --git a/lib/bhaptics/bh_constants.hpp b/lib/bhaptics/bh_constants.hpp index a93c5be2..181a4f8a 100644 --- a/lib/bhaptics/bh_constants.hpp +++ b/lib/bhaptics/bh_constants.hpp @@ -155,7 +155,23 @@ BH_LAYOUT_TACTAL_MAKE_POINT(5, 0), \ } -#pragma endregion BH_DEVICE_TACTAL +#pragma endregion BH_DEVICE_TACTVISOR + +#pragma region BH_DEVICE_TACTAL + +#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(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) +#define BH_LAYOUT_TACTVISOR { \ + BH_LAYOUT_TACTVISOR_MAKE_POINT(0, 0), \ + BH_LAYOUT_TACTVISOR_MAKE_POINT(1, 0), \ + BH_LAYOUT_TACTVISOR_MAKE_POINT(2, 0), \ + BH_LAYOUT_TACTVISOR_MAKE_POINT(3, 0), \ +} + +#pragma endregion BH_DEVICE_TACTVISOR #pragma region BH_DEVICE_TACTOSY2 From 196985888cb2230d4093d9382843c58cbc09c1a5 Mon Sep 17 00:00:00 2001 From: Leonid Meleshin Date: Thu, 8 Jun 2023 00:12:39 +0400 Subject: [PATCH 02/23] refactor(Sensor): use decorators --- 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 +- .../mode_configs/bhaptics/tactsuit_x16.cpp | 7 +- .../bhaptics/tactsuit_x16_pca9685.cpp | 7 +- .../mode_configs/bhaptics/tactsuit_x40.cpp | 7 +- firmware/mode_configs/bhaptics/tactvisor.cpp | 7 +- include/senseshift.h | 2 +- lib/arduino/battery/adc_naive.cpp | 17 ++-- lib/arduino/battery/adc_naive.hpp | 13 +-- lib/battery/abstract_battery.cpp | 9 -- lib/battery/abstract_battery.hpp | 83 +++++++++++++------ lib/bhaptics_ble/connection_bhble.hpp | 2 +- lib/ina219/battery/ina219.cpp | 26 +++--- lib/ina219/battery/ina219.hpp | 13 +-- lib/max17048/battery/max17048.cpp | 41 +++++---- lib/max17048/battery/max17048.hpp | 12 +-- lib/sensor/sensor.hpp | 63 +++++++++----- test/test_sensor/main.cpp | 63 ++++++++++++++ 21 files changed, 279 insertions(+), 128 deletions(-) create mode 100644 test/test_sensor/main.cpp diff --git a/firmware/mode_configs/bhaptics/tactal.cpp b/firmware/mode_configs/bhaptics/tactal.cpp index 7f0561e8..63c4f5b2 100644 --- a/firmware/mode_configs/bhaptics/tactal.cpp +++ b/firmware/mode_configs/bhaptics/tactal.cpp @@ -48,7 +48,12 @@ void setupMode() { bhBleConnection->begin(); #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true - auto* battery = new ADCNaiveBattery(36, { .sampleRate = BATTERY_SAMPLE_RATE }, &App, tskNO_AFFINITY); + auto* battery = new BatterySensor( + new ADCNaiveBattery(36), + &App, + { .sampleRate = BATTERY_SAMPLE_RATE }, + { "ADC Battery", 4096, 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 e365ddc5..00ab0e3d 100644 --- a/firmware/mode_configs/bhaptics/tactglove.cpp +++ b/firmware/mode_configs/bhaptics/tactglove.cpp @@ -77,7 +77,12 @@ void setupMode() { bhBleConnection->begin(); #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true - auto* battery = new ADCNaiveBattery(36, { .sampleRate = BATTERY_SAMPLE_RATE }, app, tskNO_AFFINITY); + auto* battery = new BatterySensor( + new ADCNaiveBattery(36), + &App, + { .sampleRate = BATTERY_SAMPLE_RATE }, + { "ADC Battery", 4096, 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 5846b08f..38ef204b 100644 --- a/firmware/mode_configs/bhaptics/tactosy2.cpp +++ b/firmware/mode_configs/bhaptics/tactosy2.cpp @@ -49,7 +49,12 @@ void setupMode() { bhBleConnection->begin(); #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true - auto* battery = new ADCNaiveBattery(36, { .sampleRate = BATTERY_SAMPLE_RATE }, app, tskNO_AFFINITY); + auto* battery = new BatterySensor( + new ADCNaiveBattery(36), + &App, + { .sampleRate = BATTERY_SAMPLE_RATE }, + { "ADC Battery", 4096, 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 94b68a8c..16e415ec 100644 --- a/firmware/mode_configs/bhaptics/tactosyf.cpp +++ b/firmware/mode_configs/bhaptics/tactosyf.cpp @@ -50,7 +50,12 @@ void setupMode() { bhBleConnection->begin(); #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true - auto* battery = new ADCNaiveBattery(36, { .sampleRate = BATTERY_SAMPLE_RATE }, app, tskNO_AFFINITY); + auto* battery = new BatterySensor( + new ADCNaiveBattery(36), + &App, + { .sampleRate = BATTERY_SAMPLE_RATE }, + { "ADC Battery", 4096, 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 95a31b4c..8db52f8c 100644 --- a/firmware/mode_configs/bhaptics/tactosyh.cpp +++ b/firmware/mode_configs/bhaptics/tactosyh.cpp @@ -50,7 +50,12 @@ void setupMode() { bhBleConnection->begin(); #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true - auto* battery = new ADCNaiveBattery(36, { .sampleRate = BATTERY_SAMPLE_RATE }, app, tskNO_AFFINITY); + auto* battery = new BatterySensor( + new ADCNaiveBattery(36), + &App, + { .sampleRate = BATTERY_SAMPLE_RATE }, + { "ADC Battery", 4096, 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 3b60dc0b..0e1f8685 100644 --- a/firmware/mode_configs/bhaptics/tactsuit_x16.cpp +++ b/firmware/mode_configs/bhaptics/tactsuit_x16.cpp @@ -62,7 +62,12 @@ void setupMode() { bhBleConnection->begin(); #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true - auto* battery = new ADCNaiveBattery(36, { .sampleRate = BATTERY_SAMPLE_RATE }, app, tskNO_AFFINITY); + auto* battery = new BatterySensor( + new ADCNaiveBattery(36), + &App, + { .sampleRate = BATTERY_SAMPLE_RATE }, + { "ADC Battery", 4096, 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 34163680..4cdefcf3 100644 --- a/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp +++ b/firmware/mode_configs/bhaptics/tactsuit_x16_pca9685.cpp @@ -67,7 +67,12 @@ void setupMode() { bhBleConnection->begin(); #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true - auto* battery = new ADCNaiveBattery(36, { .sampleRate = BATTERY_SAMPLE_RATE }, app, tskNO_AFFINITY); + auto* battery = new BatterySensor( + new ADCNaiveBattery(36), + &App, + { .sampleRate = BATTERY_SAMPLE_RATE }, + { "ADC Battery", 4096, 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 b8a52446..e6409035 100644 --- a/firmware/mode_configs/bhaptics/tactsuit_x40.cpp +++ b/firmware/mode_configs/bhaptics/tactsuit_x40.cpp @@ -75,7 +75,12 @@ void setupMode() { bhBleConnection->begin(); #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true - auto* battery = new ADCNaiveBattery(36, { .sampleRate = BATTERY_SAMPLE_RATE }, app, tskNO_AFFINITY); + auto* battery = new BatterySensor( + new ADCNaiveBattery(36), + &App, + { .sampleRate = BATTERY_SAMPLE_RATE }, + { "ADC Battery", 4096, 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 8127ae21..8e5cf34e 100644 --- a/firmware/mode_configs/bhaptics/tactvisor.cpp +++ b/firmware/mode_configs/bhaptics/tactvisor.cpp @@ -48,7 +48,12 @@ void setupMode() { bhBleConnection->begin(); #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true - auto* battery = new ADCNaiveBattery(36, { .sampleRate = BATTERY_SAMPLE_RATE }, &App, tskNO_AFFINITY); + auto* battery = new BatterySensor( + new ADCNaiveBattery(36), + &App, + { .sampleRate = BATTERY_SAMPLE_RATE }, + { "ADC Battery", 4096, BATTERY_TASK_PRIORITY, tskNO_AFFINITY } + ); battery->begin(); #endif } diff --git a/include/senseshift.h b/include/senseshift.h index 12b0bf9d..4439960f 100644 --- a/include/senseshift.h +++ b/include/senseshift.h @@ -19,7 +19,7 @@ class SenseShift final : public OH::IEventDispatcher OH::HapticBody* pHapticBody; #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true - OH::AbstractBattery* battery; + OH::BatterySensor* battery; #endif public: diff --git a/lib/arduino/battery/adc_naive.cpp b/lib/arduino/battery/adc_naive.cpp index 7d396997..22f7233c 100644 --- a/lib/arduino/battery/adc_naive.cpp +++ b/lib/arduino/battery/adc_naive.cpp @@ -2,10 +2,15 @@ #include -void OH::ADCNaiveBattery::setup() { - pinMode(this->pin, INPUT); -} +namespace OH +{ + void ADCNaiveBattery::setup() { + pinMode(this->pin, INPUT); + } -uint8_t OH::ADCNaiveBattery::updateValue() { - return map(analogRead(this->pin), 0.0f, 4095.0f, 0, 255); -} + BatteryState ADCNaiveBattery::getValue() { + return { + .level = map(analogRead(this->pin), 0.0f, 4095.0f, 0, 255) + }; + } +} // namespace OH diff --git a/lib/arduino/battery/adc_naive.hpp b/lib/arduino/battery/adc_naive.hpp index 886292ed..ce6b6f43 100644 --- a/lib/arduino/battery/adc_naive.hpp +++ b/lib/arduino/battery/adc_naive.hpp @@ -3,19 +3,14 @@ #include namespace OH { - class ADCNaiveBattery : public OH::AbstractBattery { + class ADCNaiveBattery : public OH::IBatterySensor { private: uint8_t pin; - protected: - uint8_t updateValue() override; - public: - ADCNaiveBattery(const uint8_t pin, OH::BatteryConfig config, OH::IEventDispatcher* eventDispatcher, TaskConfig taskConfig = { "ADC Battery", 4096, BATTERY_TASK_PRIORITY, tskNO_AFFINITY }) - : AbstractBattery(config, eventDispatcher, taskConfig), pin(pin) {}; - ADCNaiveBattery(const uint8_t pin, OH::BatteryConfig config, OH::IEventDispatcher* eventDispatcher, const BaseType_t coreId = tskNO_AFFINITY) - : AbstractBattery(config, eventDispatcher, { "ADC Battery", 4096, BATTERY_TASK_PRIORITY, coreId }), pin(pin) {}; + ADCNaiveBattery(const uint8_t pin) : pin(pin) {}; - void setup() override; + BatteryState getValue() override;; + void setup(); }; } diff --git a/lib/battery/abstract_battery.cpp b/lib/battery/abstract_battery.cpp index 76c6d31a..326b3507 100644 --- a/lib/battery/abstract_battery.cpp +++ b/lib/battery/abstract_battery.cpp @@ -1,10 +1 @@ #include "abstract_battery.hpp" - -void OH::AbstractBattery::run() { - while (true) { - this->value = this->updateValue(); - this->eventDispatcher->postEvent(new BatteryLevelEvent(this->value)); - - delay(this->rate); - } -}; diff --git a/lib/battery/abstract_battery.hpp b/lib/battery/abstract_battery.hpp index d70973c7..ecb15dbc 100644 --- a/lib/battery/abstract_battery.hpp +++ b/lib/battery/abstract_battery.hpp @@ -1,7 +1,8 @@ #pragma once -#include "sensor.hpp" -#include "events.hpp" +#include +#include +#include #include @@ -10,42 +11,70 @@ #endif namespace OH { + /** + * Tasked sensor decorator + */ + template + class TaskedSensor : public Task>, public MemoizedSensor<_Tp> { + friend class Task>; + + private: + virtual void run(void) { + while (true) { + this->updateValue(); + delay(this->rate); + } + }; + + protected: + uint32_t rate; + + public: + TaskedSensor(ISensor<_Tp>* sensor, TaskConfig taskConfig, uint32_t rate) + : MemoizedSensor<_Tp>(sensor), Task>(taskConfig), rate(rate) {}; + + void begin() override { + this->setup(); + this->Task>::begin(); + }; + }; + + struct BatteryState { + uint8_t level; + }; + class BatteryLevelEvent : public IEvent { public: - const uint8_t level; - BatteryLevelEvent(const uint8_t level): IEvent(OH_EVENT_BATTERY_LEVEL), level(level) {}; + const BatteryState state; + BatteryLevelEvent(const BatteryState state): IEvent(OH_EVENT_BATTERY_LEVEL), state(state) {}; }; struct BatteryConfig { uint sampleRate; }; - class AbstractBattery : public ThrottledSensor { - friend class Task>; - friend class ThrottledSensor; + /** + * Abstract battery sensor + */ + typedef ISensor IBatterySensor; - private: - void run(void) override; - protected: - IEventDispatcher* eventDispatcher; + class BatterySensor : public TaskedSensor { + friend class Task>; + friend class TaskedSensor; - virtual void setup(void) {}; - uint8_t updateValue() override; + private: + IEventDispatcher* eventDispatcher; + public: + BatterySensor(IBatterySensor* sensor, IEventDispatcher* eventDispatcher, BatteryConfig config, TaskConfig taskConfig) + : TaskedSensor(sensor, taskConfig, config.sampleRate), eventDispatcher(eventDispatcher) {}; - public: - AbstractBattery(BatteryConfig config, IEventDispatcher* eventDispatcher, TaskConfig taskConfig) : ThrottledSensor(taskConfig, config.sampleRate), eventDispatcher(eventDispatcher) {}; - void begin() override { - this->setup(); - ThrottledSensor::begin(); - }; - }; - - /** - * Interface for components, that are connected to a battery - */ - class IBatteryConnected { - public: - virtual AbstractBattery* getBattery() = 0; + void run() override { + while (true) { + this->updateValue(); + this->eventDispatcher->postEvent(new BatteryLevelEvent(this->value)); + delay(this->rate); + } + } }; } // namespace OH diff --git a/lib/bhaptics_ble/connection_bhble.hpp b/lib/bhaptics_ble/connection_bhble.hpp index 7e9978e0..06d45e71 100644 --- a/lib/bhaptics_ble/connection_bhble.hpp +++ b/lib/bhaptics_ble/connection_bhble.hpp @@ -65,7 +65,7 @@ namespace BH void handleEvent(const OH::IEvent* event) const override { #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true if (event->eventName == OH_EVENT_BATTERY_LEVEL) { - uint16_t level = map(static_cast(event)->level, 0, 255, 0, 100); + uint16_t level = map(static_cast(event)->state.level, 0, 255, 0, 100); this->batteryChar->setValue(level); this->batteryChar->notify(); diff --git a/lib/ina219/battery/ina219.cpp b/lib/ina219/battery/ina219.cpp index ed882f00..b0439a81 100644 --- a/lib/ina219/battery/ina219.cpp +++ b/lib/ina219/battery/ina219.cpp @@ -1,17 +1,21 @@ #include "battery/ina219.hpp" -void OH::INA219_Battery::setup() { - this->active = this->sensor->begin(); -} - -uint8_t OH::INA219_Battery::updateValue() { - if (!this->active) { - return 0; +namespace OH { + void OH::INA219_Battery::setup() { + this->active = this->sensor->begin(); } - auto batteryVoltage = this->sensor->getBusVoltage_V(); - // TODO: change this linear transformation to smth more useful - auto batteryPercentage = (batteryVoltage - 3.0) / 0.96; + BatteryState OH::INA219_Battery::getValue() { + if (!this->active) { + return { 0 }; + } - return map(batteryPercentage, 0.0f, 1.0f, 0, 255); + auto batteryVoltage = this->sensor->getBusVoltage_V(); + // TODO: change this linear transformation to smth more useful + auto batteryPercentage = (batteryVoltage - 3.0) / 0.96; + + return { + map(batteryPercentage, 0.0f, 1.0f, 0, 255) + }; + } } diff --git a/lib/ina219/battery/ina219.hpp b/lib/ina219/battery/ina219.hpp index d6d305cc..4257d44d 100644 --- a/lib/ina219/battery/ina219.hpp +++ b/lib/ina219/battery/ina219.hpp @@ -5,22 +5,15 @@ #include namespace OH { - class INA219_Battery : public OH::AbstractBattery { + class INA219_Battery : public OH::IBatterySensor { private: bool active = false; Adafruit_INA219* sensor; - protected: - uint8_t updateValue() override; - public: - INA219_Battery(Adafruit_INA219* sensor, OH::BatteryConfig config, OH::IEventDispatcher* eventDispatcher, const BaseType_t coreId = tskNO_AFFINITY) - : AbstractBattery(config, eventDispatcher, { "INA219 Battery", 1024, BATTERY_TASK_PRIORITY, coreId }), - sensor(sensor) {}; - INA219_Battery(Adafruit_INA219* sensor, OH::BatteryConfig config, OH::IEventDispatcher* eventDispatcher, TaskConfig taskConfig = { "INA219 Battery", 1024, BATTERY_TASK_PRIORITY, tskNO_AFFINITY }) - : AbstractBattery(config, eventDispatcher, taskConfig), - sensor(sensor) {}; + INA219_Battery(Adafruit_INA219* sensor) : sensor(sensor) {}; void setup() override; + BatteryState getValue() override; }; } diff --git a/lib/max17048/battery/max17048.cpp b/lib/max17048/battery/max17048.cpp index 04216506..36b47488 100644 --- a/lib/max17048/battery/max17048.cpp +++ b/lib/max17048/battery/max17048.cpp @@ -1,26 +1,31 @@ #include "battery/max17048.hpp" -// @see -// https://github.com/sparkfun/SparkFun_MAX1704x_Fuel_Gauge_Arduino_Library/blob/main/examples/Example1_Simple/Example1_Simple.ino -void OH::MAX1704_Battery::setup() { - // Set up the MAX17043 LiPo fuel gauge: - this->active = this->gauge->begin(); +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(); + 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); + // 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); + } } -} -uint8_t OH::MAX1704_Battery::updateValue() { - if (!this->active) { - return 0; - } + BatteryState MAX1704_Battery::getValue() { + if (!this->active) { + return { 0 }; + } - return this->gauge->getSOC(); + return { + .level = this->gauge->getSOC() + }; + } } diff --git a/lib/max17048/battery/max17048.hpp b/lib/max17048/battery/max17048.hpp index 4558f6c3..eed96bf9 100644 --- a/lib/max17048/battery/max17048.hpp +++ b/lib/max17048/battery/max17048.hpp @@ -5,21 +5,17 @@ #include namespace OH { - class MAX1704_Battery : public OH::AbstractBattery { + class MAX1704_Battery : public OH::IBatterySensor { private: bool active = false; SFE_MAX1704X* gauge; protected: - uint8_t updateValue() override; public: - MAX1704_Battery(SFE_MAX1704X* gauge, OH::BatteryConfig config, OH::IEventDispatcher* eventDispatcher, const BaseType_t coreId) - : AbstractBattery(config, eventDispatcher, { "MAX1704 Battery", 1024, BATTERY_TASK_PRIORITY, coreId }), - gauge(gauge) {}; - MAX1704_Battery(SFE_MAX1704X* gauge, OH::BatteryConfig config, OH::IEventDispatcher* eventDispatcher, TaskConfig taskConfig = { "MAX1704 Battery", 1024, BATTERY_TASK_PRIORITY, tskNO_AFFINITY }) - : AbstractBattery(config, eventDispatcher, taskConfig), - gauge(gauge) {}; + MAX1704_Battery(SFE_MAX1704X* gauge) : gauge(gauge) {}; + void setup() override; + BatteryState getValue() override; }; } diff --git a/lib/sensor/sensor.hpp b/lib/sensor/sensor.hpp index fe70ed29..a8e360ff 100644 --- a/lib/sensor/sensor.hpp +++ b/lib/sensor/sensor.hpp @@ -1,35 +1,60 @@ #pragma once -#include "task.hpp" - namespace OH { + /** + * Abstract hardware sensor (e.g. potentiometer, flex sensor, etc.) + * @tparam _Tp Type of the sensor value + */ template class ISensor { public: + /** + * Setup the sensor hardware + */ + virtual void setup() {}; + + /** + * Get the current sensor value + */ virtual _Tp getValue() = 0; }; + /** + * Memoized sensor decorator + * @tparam _Tp Type of the sensor value + */ template - class ThrottledSensor : public Task>, public ISensor<_Tp> { - template friend class RatePollingComponent; - friend class Task>; + class MemoizedSensor : public ISensor<_Tp> { + protected: + ISensor<_Tp>* sensor; + _Tp value; + + public: + /** + * @param sensor Sensor to be decorated + */ + MemoizedSensor(ISensor<_Tp>* sensor) : sensor(sensor) {}; - private: - virtual void run(void) { - while (true) { - this->value = this->updateValue(); - delay(this->rate); - } - }; + /** + * Setup the sensor hardware + */ + void setup() override { + this->sensor->setup(); + }; - protected: - _Tp value; - uint32_t rate; - virtual _Tp updateValue(void) = 0; + /** + * Get the current memoized value + */ + _Tp getValue() override { + return this->value; + }; - public: - ThrottledSensor(TaskConfig taskConfig, uint32_t rate) : Task>(taskConfig), rate(rate) {}; - _Tp getValue() override { return this->value; }; + /** + * Read actual value from the hardware and memoize it + */ + void updateValue() { + this->value = this->sensor->getValue(); + }; }; } // namespace OH diff --git a/test/test_sensor/main.cpp b/test/test_sensor/main.cpp new file mode 100644 index 00000000..dd033ad8 --- /dev/null +++ b/test/test_sensor/main.cpp @@ -0,0 +1,63 @@ +#include +#include + +using namespace OH; + +class TestSensor : public ISensor { + private: + int count = 0; + + public: + int setupCounter = 0; + + void setup() override { + this->setupCounter++; + }; + + int getValue() override { + return ++this->count; + }; +}; + +void test_memoized_sensor(void) { + auto inner = new TestSensor(); + auto sensor = new MemoizedSensor(inner); + + TEST_ASSERT_EQUAL_INT(0, inner->setupCounter); + sensor->setup(); + 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()); +} + +int process(void) { + UNITY_BEGIN(); + + RUN_TEST(test_memoized_sensor); + + return UNITY_END(); +} + +#ifdef ARDUINO + +#include + +void setup(void) { + process(); +} + +void loop(void) {} + +#else + +int main(int argc, char** argv) { + return process(); +} + +#endif From b41704d5fdb6a97d27f3835d54cfedd22cd499da Mon Sep 17 00:00:00 2001 From: Leonid Meleshin Date: Thu, 8 Jun 2023 23:18:17 +0400 Subject: [PATCH 03/23] ci(GitHub): optimize deps caching --- .github/workflows/ci.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 86fa91ea..6d0c59c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,11 +77,13 @@ jobs: restore-keys: | ${{ runner.os }}-pip- - - uses: actions/cache@v3 + - name: Cache PlatformIO + uses: actions/cache@v3 with: - path: ~/.platformio/.cache - key: ${{ runner.os }}-pio-${{ matrix.target }} + path: ~/.platformio + key: ${{ runner.os }}-pio-${{ matrix.target }}-${{ hashFiles('platformio.ini') }} restore-keys: | + ${{ runner.os }}-pio-${{ matrix.target }}- ${{ runner.os }}-pio- - name: Set up Python @@ -176,8 +178,9 @@ jobs: uses: actions/cache@v3 with: path: ~/.platformio - key: ${{ runner.os }}-pio-${{ hashFiles('**/lockfiles') }} + key: ${{ runner.os }}-pio-${{ matrix.target }}-${{ hashFiles('platformio.ini') }} restore-keys: | + ${{ runner.os }}-pio-${{ matrix.target }} ${{ runner.os }}-pio- - name: Set up Python @@ -192,6 +195,9 @@ jobs: pio upgrade --dev pio pkg update --global + - name: Install libs + run: pio lib -e ${{matrix.target}} install + - name: Run Unit Tests run: pio test -e ${{matrix.target}} From 3c71effd151d43a9d7e169fe857d91295d7fdda2 Mon Sep 17 00:00:00 2001 From: Leonid Meleshin Date: Thu, 8 Jun 2023 23:47:46 +0400 Subject: [PATCH 04/23] feat: add simple mapping util --- lib/arduino/battery/adc_naive.cpp | 3 ++- lib/arduino/output_writers/pwm.cpp | 37 ++++++++++++++------------ lib/arduino/output_writers/pwm.hpp | 26 +++++++++--------- lib/bhaptics/bh_constants.hpp | 2 +- lib/bhaptics/bh_utils.cpp | 8 +++--- lib/bhaptics_ble/connection_bhble.hpp | 3 ++- lib/core/logging.hpp | 2 +- lib/core/utility.hpp | 14 ++++++++-- lib/freertos/task.hpp | 2 +- lib/haptics/haptic_plane.hpp | 4 +-- lib/ina219/battery/ina219.cpp | 3 ++- lib/pca9685/output_writers/pca9685.cpp | 3 ++- test/test_core_utility/main.cpp | 33 ++++++++++++++--------- 13 files changed, 83 insertions(+), 57 deletions(-) diff --git a/lib/arduino/battery/adc_naive.cpp b/lib/arduino/battery/adc_naive.cpp index 22f7233c..90e54d85 100644 --- a/lib/arduino/battery/adc_naive.cpp +++ b/lib/arduino/battery/adc_naive.cpp @@ -1,5 +1,6 @@ #include "battery/adc_naive.hpp" +#include #include namespace OH @@ -10,7 +11,7 @@ namespace OH BatteryState ADCNaiveBattery::getValue() { return { - .level = map(analogRead(this->pin), 0.0f, 4095.0f, 0, 255) + .level = simpleMap(analogRead(this->pin), 4095, 255) }; } } // namespace OH diff --git a/lib/arduino/output_writers/pwm.cpp b/lib/arduino/output_writers/pwm.cpp index 693b03af..b5394d9f 100644 --- a/lib/arduino/output_writers/pwm.cpp +++ b/lib/arduino/output_writers/pwm.cpp @@ -1,24 +1,27 @@ #include +#include #include -uint8_t PWMOutputWriter::CHANNELS = 0; +namespace OH { + uint8_t PWMOutputWriter::CHANNELS = 0; -void PWMOutputWriter::setup() { - this->chan = PWMOutputWriter::CHANNELS++; + void PWMOutputWriter::setup() { + this->chan = PWMOutputWriter::CHANNELS++; -#if defined(ARDUINO_ARCH_ESP32) - ledcSetup(this->chan, this->freq, this->resolution); - ledcAttachPin(this->pin, this->chan); -#else - pinMode(this->pin, OUTPUT); -#endif -}; + #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(ARDUINO_ARCH_ESP32) - ledcWrite(chan, (uint16_t)map(intensity, 0, OH_OUTPUT_INTENSITY_MAX, 0, 4096)); -#else - analogWrite(this->pin, (uint16_t)map(intensity, 0, OH_OUTPUT_INTENSITY_MAX, 0, 255)); -#endif -} + void PWMOutputWriter::writeOutput(oh_output_intensity_t intensity) { + #if defined(ESP32) + ledcWrite(chan, simpleMap(intensity, OH_OUTPUT_INTENSITY_MAX, 4096)); + #else + analogWrite(this->pin, simpleMap(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 index 7c19ac4e..6c6f6e2d 100644 --- a/lib/arduino/output_writers/pwm.hpp +++ b/lib/arduino/output_writers/pwm.hpp @@ -2,17 +2,19 @@ #include -class PWMOutputWriter : public OH::AbstractActuator { - private: - static uint8_t CHANNELS; - uint8_t pin, chan; - double freq; - uint8_t resolution; +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) {}; + 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; -}; + void setup() override; + void writeOutput(oh_output_intensity_t intensity) override; + }; +}; // namespace OH diff --git a/lib/bhaptics/bh_constants.hpp b/lib/bhaptics/bh_constants.hpp index 181a4f8a..c3a41e96 100644 --- a/lib/bhaptics/bh_constants.hpp +++ b/lib/bhaptics/bh_constants.hpp @@ -1,6 +1,6 @@ #pragma once -#if defined(ARDUINO_ARCH_ESP32) +#if defined(ESP32) #include #endif diff --git a/lib/bhaptics/bh_utils.cpp b/lib/bhaptics/bh_utils.cpp index 223ea4e0..fc672d52 100644 --- a/lib/bhaptics/bh_utils.cpp +++ b/lib/bhaptics/bh_utils.cpp @@ -9,7 +9,7 @@ void BH::plainOutputTransformer(OH::HapticBody* output, std::string& value, cons oh_output_data_t outputData{ .point = *layout[i], // TODO: optimize generic type - .intensity = static_cast(OH::map(byte, 0, 100, 0, OH_OUTPUT_INTENSITY_MAX)), + .intensity = static_cast(OH::accurateMap(byte, 0, 100, 0, OH_OUTPUT_INTENSITY_MAX)), }; output->writeOutput(path, outputData); @@ -25,13 +25,13 @@ void BH::vestOutputTransformer(OH::HapticBody* output, std::string& value, const const oh_output_data_t outputData0{ .point = *layout[actIndex], // TODO: optimize generic type - .intensity = static_cast(OH::map(((byte >> 4) & 0xf), 0, 15, 0, OH_OUTPUT_INTENSITY_MAX)), + .intensity = static_cast(OH::simpleMap(((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::map((byte & 0xf), 0, 15, 0, OH_OUTPUT_INTENSITY_MAX)), + .intensity = static_cast(OH::simpleMap((byte & 0xf), 15, OH_OUTPUT_INTENSITY_MAX)), }; output->writeOutput(path, outputData0); @@ -81,7 +81,7 @@ void BH::vestX16OutputTransformer(OH::HapticBody* output, std::string& value, co const oh_output_data_t outputData{ .point = *layout[i], // TODO: optimize generic type - .intensity = static_cast(OH::map(result[i], 0, 15, 0, OH_OUTPUT_INTENSITY_MAX)), + .intensity = static_cast(OH::accurateMap(result[i], 0, 15, 0, OH_OUTPUT_INTENSITY_MAX)), }; output->writeOutput(path, outputData); diff --git a/lib/bhaptics_ble/connection_bhble.hpp b/lib/bhaptics_ble/connection_bhble.hpp index 06d45e71..7a32d6ba 100644 --- a/lib/bhaptics_ble/connection_bhble.hpp +++ b/lib/bhaptics_ble/connection_bhble.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -65,7 +66,7 @@ namespace BH void handleEvent(const OH::IEvent* event) const override { #if defined(BATTERY_ENABLED) && BATTERY_ENABLED == true if (event->eventName == OH_EVENT_BATTERY_LEVEL) { - uint16_t level = map(static_cast(event)->state.level, 0, 255, 0, 100); + uint16_t level = OH::simpleMap(static_cast(event)->state.level, 255, 100); this->batteryChar->setValue(level); this->batteryChar->notify(); diff --git a/lib/core/logging.hpp b/lib/core/logging.hpp index 1679cc2d..715c9f64 100644 --- a/lib/core/logging.hpp +++ b/lib/core/logging.hpp @@ -1,6 +1,6 @@ #pragma once -#if defined(ARDUINO_ARCH_ESP32) +#if defined(ESP32) #include #elif defined(UNITY_INCLUDE_PRINT_FORMATTED) #define log_e(...) TEST_PRINTF(__VA_ARGS__) diff --git a/lib/core/utility.hpp b/lib/core/utility.hpp index 606375e4..7533b8e1 100644 --- a/lib/core/utility.hpp +++ b/lib/core/utility.hpp @@ -3,6 +3,10 @@ #include #include +#ifndef constrain + #define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt))) +#endif + namespace OH { /** * Checks if a container contains a value. @@ -21,12 +25,18 @@ namespace OH { }; template - inline bool contains(const _Tp* arr, const std::size_t size, const _Tp& val) { + constexpr inline bool contains(const _Tp* arr, const std::size_t size, const _Tp& val) { return std::find(arr, arr + size, val) != arr + size; }; template - inline _Tp map(_Tp x, _Tp in_min, _Tp in_max, _Tp out_min, _Tp out_max) { + constexpr inline _Tp accurateMap(_Tp x, _Tp in_min, _Tp in_max, _Tp out_min, _Tp out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } + + // Same as the above, but both mins are 0. + template + constexpr inline _Tp simpleMap(_Tp x, _Tp in_max, _Tp out_max) { + return x * out_max / in_max; + } } // namespace OH diff --git a/lib/freertos/task.hpp b/lib/freertos/task.hpp index 5804b5f5..9def5fcf 100644 --- a/lib/freertos/task.hpp +++ b/lib/freertos/task.hpp @@ -4,7 +4,7 @@ extern "C" void delay(uint32_t ms); -#if defined(ARDUINO_ARCH_ESP32) +#if defined(ESP32) #include // Include the base FreeRTOS definitions. #include // Include the task definitions. diff --git a/lib/haptics/haptic_plane.hpp b/lib/haptics/haptic_plane.hpp index 8fffb099..5b775de5 100644 --- a/lib/haptics/haptic_plane.hpp +++ b/lib/haptics/haptic_plane.hpp @@ -53,8 +53,8 @@ namespace OH { */ template static inline oh_output_point_t* mapPoint(_Tp x, _Tp y, _Tp x_max, _Tp y_max) { - const oh_output_coord_t x_coord = map<_Tp>(x + 1, 0, x_max + 2, 0, OH_OUTPUT_COORD_MAX); - const oh_output_coord_t y_coord = map<_Tp>(y + 1, 0, y_max + 2, 0, OH_OUTPUT_COORD_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); return new oh_output_point_t(x_coord, y_coord); } diff --git a/lib/ina219/battery/ina219.cpp b/lib/ina219/battery/ina219.cpp index b0439a81..27bdf9eb 100644 --- a/lib/ina219/battery/ina219.cpp +++ b/lib/ina219/battery/ina219.cpp @@ -1,4 +1,5 @@ #include "battery/ina219.hpp" +#include namespace OH { void OH::INA219_Battery::setup() { @@ -15,7 +16,7 @@ namespace OH { auto batteryPercentage = (batteryVoltage - 3.0) / 0.96; return { - map(batteryPercentage, 0.0f, 1.0f, 0, 255) + simpleMap(batteryPercentage, 1.0f, 255.0f); }; } } diff --git a/lib/pca9685/output_writers/pca9685.cpp b/lib/pca9685/output_writers/pca9685.cpp index 6fdf832e..fd20a12b 100644 --- a/lib/pca9685/output_writers/pca9685.cpp +++ b/lib/pca9685/output_writers/pca9685.cpp @@ -1,5 +1,6 @@ #include "output_writers/pca9685.hpp" +#include void OH::PCA9685OutputWriter::writeOutput(oh_output_intensity_t intensity) { - this->driver->setPin(this->num, (uint16_t)map(intensity, 0, OH_OUTPUT_INTENSITY_MAX, 0, 4095)); + this->driver->setPin(this->num, simpleMap(intensity, OH_OUTPUT_INTENSITY_MAX, 4095)); } diff --git a/test/test_core_utility/main.cpp b/test/test_core_utility/main.cpp index 4b4335fb..d8afab4f 100644 --- a/test/test_core_utility/main.cpp +++ b/test/test_core_utility/main.cpp @@ -42,18 +42,24 @@ void test_contains_string(void) { TEST_ASSERT_FALSE(contains(s, 'z')); } -void test_map(void) { - TEST_ASSERT_EQUAL_UINT16(0, map(0, 0, 4095, 0, 255)); - TEST_ASSERT_EQUAL_UINT16(127, map(2047, 0, 4095, 0, 255)); - TEST_ASSERT_EQUAL_UINT16(255, map(4095, 0, 4095, 0, 255)); - - TEST_ASSERT_EQUAL_UINT16(127, map(0, 0, 4095, 127, 255)); - TEST_ASSERT_EQUAL_UINT16(190, map(2047, 0, 4095, 127, 255)); - TEST_ASSERT_EQUAL_UINT16(255, map(4095, 0, 4095, 127, 255)); - - TEST_ASSERT_EQUAL_UINT16(0, map(2048, 2048, 4095, 0, 255)); - TEST_ASSERT_EQUAL_UINT16(127, map(3071, 2048, 4095, 0, 255)); - TEST_ASSERT_EQUAL_UINT16(255, map(4095, 2048, 4095, 0, 255)); +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)); +} + +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) { @@ -62,7 +68,8 @@ int process(void) { RUN_TEST(test_contains_container); RUN_TEST(test_contains_iterator); RUN_TEST(test_contains_string); - RUN_TEST(test_map); + RUN_TEST(test_accurate_map); + RUN_TEST(test_simple_map); return UNITY_END(); } From c4d557e5f69ce05f8767712bb6a9efcb7d77404e Mon Sep 17 00:00:00 2001 From: Leonid Meleshin Date: Thu, 11 May 2023 23:00:26 +0400 Subject: [PATCH 05/23] feat(Sensor): add calibrated sensor --- lib/calibration/calibration.hpp | 124 ++++++++++++++++++++++++++++++++ lib/sensor/sensor.hpp | 48 +++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 lib/calibration/calibration.hpp diff --git a/lib/calibration/calibration.hpp b/lib/calibration/calibration.hpp new file mode 100644 index 00000000..1fef11f2 --- /dev/null +++ b/lib/calibration/calibration.hpp @@ -0,0 +1,124 @@ +/** + * Calibrated input + * Credit: https://github.com/JohnRThomas/OpenGloves-Firmware/blob/main/open-gloves/Calibration.hpp + */ + +#pragma once + +#include + +namespace OH { + class Calibrated { + public: + virtual void resetCalibration() = 0; + + virtual void enableCalibration() { + calibrate = true; + } + + virtual void disableCalibration() { + calibrate = false; + } + + protected: + bool calibrate; + }; + + template + struct Calibrator { + virtual void reset() = 0; + virtual void update(T input) = 0; + virtual T calibrate(T input) const = 0; + }; + + template + class MinMaxCalibrator : public Calibrator { + public: + MinMaxCalibrator() : value_min(output_max), value_max(output_min) {} + + void reset() { + value_min = output_max; + value_max = output_min; + } + + void update(T input) { + // Update the min and the max. + if (input < value_min) value_min = input; + if (input > value_max) value_max = input; + } + + T calibrate(T input) const { + // 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; + } + + // Map the input range to the output range. + T output = accurateMap(input, value_min, value_max, output_min, output_max); + + // Lock the range to the output. + return constrain(output, output_min, output_max); + } + + private: + T value_min; + T value_max; + }; + + template + class CenterPointDeviationCalibrator : public Calibrator { + public: + CenterPointDeviationCalibrator() : range_min(sensor_max), range_max(0) {} + + void reset() { + range_min = sensor_max; + range_max = 0; + } + + void update(T input) { + // Update the min and the max. + if (input < range_min) range_min = accurateMap(input, output_min, output_max, 0, sensor_max); + if (input > range_max) range_max = accurateMap(input, output_min, output_max, 0, sensor_max); + } + + T calibrate(T input) const { + // Find the center point of the sensor so we know how much we have deviated from it. + T center = (range_min + range_max) / 2.0f; + + // Map the input to the sensor range of motion. + T output = accurateMap(input, output_min, output_max, 0, sensor_max); + + // Find the deviation from the center and constrain it to the maximum that the driver supports. + output = constrain(output - center, -driver_max_deviation, driver_max_deviation); + + // Finally map the deviation from the center back to the output range. + return accurateMap(output, -driver_max_deviation, driver_max_deviation, output_min, output_max); + } + + private: + T range_min; + T range_max; + }; + + template + class FixedCenterPointDeviationCalibrator : public Calibrator { + public: + void reset() {} + void update(T input) {} + + T calibrate(T input) const { + // Find the center point of the sensor so we know how much we have deviated from it. + T center = sensor_max / 2.0f; + + // Map the input to the sensor range of motion. + T output = accurateMap(input, output_min, output_max, 0, sensor_max); + + // Find the deviation from the center and constrain it to the maximum that the driver supports. + output = constrain(output - center, -driver_max_deviation, driver_max_deviation); + + // Finally map the deviation from the center back to the output range. + return accurateMap(output, -driver_max_deviation, driver_max_deviation, output_min, output_max); + } + }; +} diff --git a/lib/sensor/sensor.hpp b/lib/sensor/sensor.hpp index a8e360ff..e34b26be 100644 --- a/lib/sensor/sensor.hpp +++ b/lib/sensor/sensor.hpp @@ -1,5 +1,20 @@ #pragma once +#include + +#include + +#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 + namespace OH { /** @@ -57,4 +72,37 @@ namespace OH this->value = this->sensor->getValue(); }; }; + + /** + * Calibrated sensor decorator + * + * @tparam _Tp Type of the sensor value + */ + template + class CalibratedSensor : public ISensor<_Tp>, public Calibrated { + protected: + ISensor<_Tp>* sensor; + Calibrator<_Tp>* calibrator; + + public: + /** + * @param sensor Sensor to be decorated + * @param calibrator Calibrator algorithm to be used + */ + CalibratedSensor(ISensor<_Tp>* sensor, Calibrator<_Tp>* calibrator) : sensor(sensor), calibrator(calibrator) {}; + + _Tp getValue() override { + auto value = this->sensor->getValue(); + + if (this->calibrate) { + this->calibrator->update(value); + } + + return this->calibrator->calibrate(value); + }; + + void resetCalibration() override { + calibrator.reset(); + } + }; } // namespace OH From 2411163794670918c575e3a51d4d79cb6768a3d9 Mon Sep 17 00:00:00 2001 From: Leonid Meleshin Date: Fri, 9 Jun 2023 00:15:11 +0400 Subject: [PATCH 06/23] ci(GitHub): update deprecated commands --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d0c59c8..0fc7a02a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,7 +121,7 @@ jobs: pio pkg update --global - name: Install libs - run: pio lib -e ${{matrix.target}} install + run: pio pkg install -e ${{matrix.target}} - name: Change memory segments if: matrix.coverage @@ -196,7 +196,7 @@ jobs: pio pkg update --global - name: Install libs - run: pio lib -e ${{matrix.target}} install + run: pio pkg install -e ${{matrix.target}} - name: Run Unit Tests run: pio test -e ${{matrix.target}} From daad28f96404a3c4f53d73921e41a86bcf406d2c Mon Sep 17 00:00:00 2001 From: Leonid Meleshin Date: Fri, 9 Jun 2023 00:46:45 +0400 Subject: [PATCH 07/23] test(Calibration): cover minmax calibrator --- lib/calibration/calibration.hpp | 8 +++++ test/test_calibration/main.cpp | 61 +++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 test/test_calibration/main.cpp diff --git a/lib/calibration/calibration.hpp b/lib/calibration/calibration.hpp index 1fef11f2..a4f9894e 100644 --- a/lib/calibration/calibration.hpp +++ b/lib/calibration/calibration.hpp @@ -54,6 +54,14 @@ namespace OH { return (output_min + output_max) / 2.0f; } + if (input < value_min) { + return output_min; + } + + if (input > value_max) { + return output_max; + } + // Map the input range to the output range. T output = accurateMap(input, value_min, value_max, output_min, output_max); diff --git a/test/test_calibration/main.cpp b/test/test_calibration/main.cpp new file mode 100644 index 00000000..5d97d41f --- /dev/null +++ b/test/test_calibration/main.cpp @@ -0,0 +1,61 @@ +#include +#include + +using namespace OH; + +void test_minmax_calibrator(void) { + auto calibrator = new MinMaxCalibrator(); + + // 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)); + + 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)); +} + +int process(void) { + UNITY_BEGIN(); + + RUN_TEST(test_minmax_calibrator); + + return UNITY_END(); +} + +#ifdef ARDUINO + +#include + +void setup(void) { + process(); +} + +void loop(void) {} + +#else + +int main(int argc, char** argv) { + return process(); +} + +#endif From 07ca931b255c18dd8f705364323adb3b3662daa8 Mon Sep 17 00:00:00 2001 From: Leonid Meleshin Date: Thu, 11 May 2023 23:37:38 +0400 Subject: [PATCH 08/23] feat(OpenGloves): add finger initialization --- .github/workflows/ci.yml | 11 ++- .../mode_configs/opengloves/opengloves.cpp | 75 +++++++++++++++++++ ini/opengloves.ini | 47 ++++++++++++ lib/arduino/sensor/joystick.hpp | 37 +++++++++ lib/opengloves/og_constants.hpp | 3 + lib/opengloves/sensor/og_finger.hpp | 41 ++++++++++ lib/sensor/sensor.hpp | 2 +- platformio.ini | 1 + 8 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 firmware/mode_configs/opengloves/opengloves.cpp create mode 100644 ini/opengloves.ini create mode 100644 lib/arduino/sensor/joystick.hpp create mode 100644 lib/opengloves/og_constants.hpp create mode 100644 lib/opengloves/sensor/og_finger.hpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0fc7a02a..6ec8db0f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ on: - '**/*.md' jobs: - build: + 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 }} runs-on: ${{ matrix.os }} strategy: @@ -58,6 +58,10 @@ jobs: serial_plotter_flag: SERIAL_PLOTTER=false nimble_flag: BLUETOOTH_USE_NIMBLE=true + - target: opengloves + os: ubuntu-latest + coverage: true + steps: - uses: actions/checkout@v3 @@ -214,7 +218,10 @@ jobs: retention-days: 5 coverage-report: - needs: [build, test] + needs: + - build-bhaptics + # - build-opengloves + - test runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp new file mode 100644 index 00000000..28fbcba3 --- /dev/null +++ b/firmware/mode_configs/opengloves/opengloves.cpp @@ -0,0 +1,75 @@ +#include +#include +#include +#include + +#define CALIBRATION_CURL OH::MinMaxCalibrator + +#define FINGER_THUMB_ENABLED (PIN_FINGER_THUMB != -1) +#define FINGER_INDEX_ENABLED (PIN_FINGER_INDEX != -1) +#define FINGER_MIDDLE_ENABLED (PIN_FINGER_MIDDLE != -1) +#define FINGER_RING_ENABLED (PIN_FINGER_RING != -1) +#define FINGER_PINKY_ENABLED (PIN_FINGER_PINKY != -1) +#define FINGER_COUNT (FINGER_THUMB_ENABLED + FINGER_INDEX_ENABLED + FINGER_MIDDLE_ENABLED + FINGER_RING_ENABLED + FINGER_PINKY_ENABLED) + +#define JOYSTICK_ENABLED (PIN_JOYSTICK_X != -1 && PIN_JOYSTICK_Y != -1) +#define JOYSTICK_COUNT (JOYSTICK_ENABLED ? 2 : 0) + +using namespace OpenGloves; + +#pragma region FingerSensor + +#if FINGER_THUMB_ENABLED + auto* fingerThumb = new FingerSensor(PIN_FINGER_THUMB, FINGER_THUMB_INVERT, new CALIBRATION_CURL()); +#endif + +#if FINGER_INDEX_ENABLED + auto* fingerIndex = new FingerSensor(PIN_FINGER_INDEX, FINGER_INDEX_INVERT, new CALIBRATION_CURL()); +#endif + +#if FINGER_MIDDLE_ENABLED + auto* fingerMiddle = new FingerSensor(PIN_FINGER_MIDDLE, FINGER_MIDDLE_INVERT, new CALIBRATION_CURL()); +#endif + +#if FINGER_RING_ENABLED + auto* fingerRing = new FingerSensor(PIN_FINGER_RING, FINGER_RING_INVERT, new CALIBRATION_CURL()); +#endif + +#if FINGER_PINKY_ENABLED + auto* fingerPinky = new FingerSensor(PIN_FINGER_PINKY, FINGER_PINKY_INVERT, new CALIBRATION_CURL()); +#endif + +IFinger* fingers[FINGER_COUNT] = { +#if FINGER_THUMB_ENABLED + fingerThumb, +#endif +#if FINGER_INDEX_ENABLED + fingerIndex, +#endif +#if FINGER_MIDDLE_ENABLED + fingerMiddle, +#endif +#if FINGER_RING_ENABLED + fingerRing, +#endif +#if FINGER_PINKY_ENABLED + fingerPinky, +#endif +}; + +#pragma endregion + +OH::JoystickAxisSensor* joystick[] = { +#if JOYSTICK_ENABLED + new OH::JoystickAxisSensor(PIN_JOYSTICK_X, JOYSTICK_DEADZONE, JOYSTICK_X_INVERT), + new OH::JoystickAxisSensor(PIN_JOYSTICK_Y, JOYSTICK_DEADZONE, JOYSTICK_Y_INVERT), +#endif +}; + +void setupMode() { + +} + +void loopMode() { + +} diff --git a/ini/opengloves.ini b/ini/opengloves.ini new file mode 100644 index 00000000..9c4dcca1 --- /dev/null +++ b/ini/opengloves.ini @@ -0,0 +1,47 @@ +[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 + -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 JOYSTICK_X_INVERT=false + -D JOYSTICK_Y_INVERT=false + -D JOYSTICK_DEADZONE=0.1 + + ; Pins configuration + ; If pin set to -1, then it will be ignored + -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 +build_unflags = ${common.build_unflags} +build_src_filter = ${common.build_src_filter} +lib_deps = ${common.lib_deps} + +[env:opengloves] +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} +build_unflags = ${opengloves.build_unflags} +build_src_filter = ${opengloves.build_src_filter} + + +lib_deps = ${opengloves.lib_deps} diff --git a/lib/arduino/sensor/joystick.hpp b/lib/arduino/sensor/joystick.hpp new file mode 100644 index 00000000..bcc2b271 --- /dev/null +++ b/lib/arduino/sensor/joystick.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include + +namespace OH { + class JoystickAxisSensor : public ISensor { + private: + uint8_t pin; + float dead_zone; + bool invert; + + 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; + } + + protected: + uint16_t getValue(void) override { + auto value = analogRead(this->pin); + value = this->filterDeadZone(value); + + if (this->invert) { + value = ANALOG_MAX - value; + } + + return value; + } + + public: + JoystickAxisSensor(uint8_t pin, float dead_zone, bool invert = false) : pin(pin), dead_zone(dead_zone), invert(invert) {} + }; +}; // namespace OH diff --git a/lib/opengloves/og_constants.hpp b/lib/opengloves/og_constants.hpp new file mode 100644 index 00000000..fc9712b7 --- /dev/null +++ b/lib/opengloves/og_constants.hpp @@ -0,0 +1,3 @@ +#pragma once + +#define OPENGLOVES_FINGERS_TASK_PRIORITY 1 diff --git a/lib/opengloves/sensor/og_finger.hpp b/lib/opengloves/sensor/og_finger.hpp new file mode 100644 index 00000000..e1eca696 --- /dev/null +++ b/lib/opengloves/sensor/og_finger.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +#include + +namespace OpenGloves +{ + struct IFinger { + // + }; + + class FingerSensor : public IFinger, public OH::CalibratedSensor + { + private: + uint8_t pin; + bool invert; + + protected: + uint16_t updateValue(void) override { + auto value = analogRead(this->pin); + + if (this->invert) { + value = ANALOG_MAX - value; + } + + // TODO: add median filter + + return value; + }; + + public: + FingerSensor(uint8_t pin, bool invert, OH::Calibrator* calibrator) : + OH::CalibratedSensor(calibrator), + pin(pin), invert(invert) {}; + }; + + // TODO: add splay finger sensor + +} // namespace OpenGloves diff --git a/lib/sensor/sensor.hpp b/lib/sensor/sensor.hpp index e34b26be..a1ce2043 100644 --- a/lib/sensor/sensor.hpp +++ b/lib/sensor/sensor.hpp @@ -102,7 +102,7 @@ namespace OH }; void resetCalibration() override { - calibrator.reset(); + calibrator->reset(); } }; } // namespace OH diff --git a/platformio.ini b/platformio.ini index 61b31ea2..558b10a8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -14,6 +14,7 @@ lib_dir = ./lib src_dir = ./firmware extra_configs = ini/bhaptics.ini + ini/opengloves.ini [common] build_unflags = From cf33c867f37a02b734a6c0f61f9852219c14444c Mon Sep 17 00:00:00 2001 From: Leonid Meleshin Date: Tue, 6 Jun 2023 20:37:49 +0400 Subject: [PATCH 09/23] feat(OpenGloves): setup components on init [skip ci] --- firmware/mode_configs/opengloves/opengloves.cpp | 10 ++++++++-- lib/arduino/sensor/joystick.hpp | 4 ++++ lib/opengloves/sensor/og_finger.hpp | 4 ++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp index 28fbcba3..359c78e6 100644 --- a/firmware/mode_configs/opengloves/opengloves.cpp +++ b/firmware/mode_configs/opengloves/opengloves.cpp @@ -39,7 +39,7 @@ using namespace OpenGloves; auto* fingerPinky = new FingerSensor(PIN_FINGER_PINKY, FINGER_PINKY_INVERT, new CALIBRATION_CURL()); #endif -IFinger* fingers[FINGER_COUNT] = { +FingerSensor* fingers[FINGER_COUNT] = { #if FINGER_THUMB_ENABLED fingerThumb, #endif @@ -59,7 +59,7 @@ IFinger* fingers[FINGER_COUNT] = { #pragma endregion -OH::JoystickAxisSensor* joystick[] = { +OH::JoystickAxisSensor* joystick[JOYSTICK_COUNT] = { #if JOYSTICK_ENABLED new OH::JoystickAxisSensor(PIN_JOYSTICK_X, JOYSTICK_DEADZONE, JOYSTICK_X_INVERT), new OH::JoystickAxisSensor(PIN_JOYSTICK_Y, JOYSTICK_DEADZONE, JOYSTICK_Y_INVERT), @@ -67,7 +67,13 @@ OH::JoystickAxisSensor* joystick[] = { }; void setupMode() { + for (int i = 0; i < FINGER_COUNT; i++) { + fingers[i]->setup(); + } + for (int i = 0; i < JOYSTICK_COUNT; i++) { + joystick[i]->setup(); + } } void loopMode() { diff --git a/lib/arduino/sensor/joystick.hpp b/lib/arduino/sensor/joystick.hpp index bcc2b271..0d94ea5a 100644 --- a/lib/arduino/sensor/joystick.hpp +++ b/lib/arduino/sensor/joystick.hpp @@ -33,5 +33,9 @@ namespace OH { public: JoystickAxisSensor(uint8_t pin, float dead_zone, bool invert = false) : pin(pin), dead_zone(dead_zone), invert(invert) {} + + void setup(void) { + pinMode(this->pin, INPUT); + }; }; }; // namespace OH diff --git a/lib/opengloves/sensor/og_finger.hpp b/lib/opengloves/sensor/og_finger.hpp index e1eca696..4a09e0e5 100644 --- a/lib/opengloves/sensor/og_finger.hpp +++ b/lib/opengloves/sensor/og_finger.hpp @@ -34,6 +34,10 @@ namespace OpenGloves FingerSensor(uint8_t pin, bool invert, OH::Calibrator* calibrator) : OH::CalibratedSensor(calibrator), pin(pin), invert(invert) {}; + + void setup(void) { + pinMode(this->pin, INPUT); + }; }; // TODO: add splay finger sensor From 4c292505767032b201fb4cd6eced0430684892f9 Mon Sep 17 00:00:00 2001 From: Leonid Meleshin Date: Fri, 9 Jun 2023 10:34:13 +0400 Subject: [PATCH 10/23] refactor(OpenGloves): use decorator sensors --- .../mode_configs/opengloves/opengloves.cpp | 24 ++++++++---- lib/arduino/sensor/analog.hpp | 37 +++++++++++++++++++ lib/arduino/sensor/joystick.hpp | 31 +++++++--------- lib/opengloves/sensor/og_finger.hpp | 32 ++++------------ lib/opengloves_utils/opengloves_protocol.hpp | 24 ++++++++++++ lib/sensor/sensor.hpp | 4 +- 6 files changed, 100 insertions(+), 52 deletions(-) create mode 100644 lib/arduino/sensor/analog.hpp create mode 100644 lib/opengloves_utils/opengloves_protocol.hpp diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp index 359c78e6..9ca4d895 100644 --- a/firmware/mode_configs/opengloves/opengloves.cpp +++ b/firmware/mode_configs/opengloves/opengloves.cpp @@ -1,5 +1,7 @@ #include +#include #include +#include #include #include @@ -20,23 +22,23 @@ using namespace OpenGloves; #pragma region FingerSensor #if FINGER_THUMB_ENABLED - auto* fingerThumb = new FingerSensor(PIN_FINGER_THUMB, FINGER_THUMB_INVERT, new CALIBRATION_CURL()); + auto* fingerThumb = new FingerSensor(new OH::AnalogSensor(PIN_FINGER_THUMB), new CALIBRATION_CURL(), EncodedInput::Type::THUMB); #endif #if FINGER_INDEX_ENABLED - auto* fingerIndex = new FingerSensor(PIN_FINGER_INDEX, FINGER_INDEX_INVERT, new CALIBRATION_CURL()); + auto* fingerIndex = new FingerSensor(new OH::AnalogSensor(PIN_FINGER_INDEX), new CALIBRATION_CURL(), EncodedInput::Type::INDEX); #endif #if FINGER_MIDDLE_ENABLED - auto* fingerMiddle = new FingerSensor(PIN_FINGER_MIDDLE, FINGER_MIDDLE_INVERT, new CALIBRATION_CURL()); + auto* fingerMiddle = new FingerSensor(new OH::AnalogSensor(PIN_FINGER_MIDDLE), new CALIBRATION_CURL(), EncodedInput::Type::MIDDLE); #endif #if FINGER_RING_ENABLED - auto* fingerRing = new FingerSensor(PIN_FINGER_RING, FINGER_RING_INVERT, new CALIBRATION_CURL()); + auto* fingerRing = new FingerSensor(new OH::AnalogSensor(PIN_FINGER_RING), new CALIBRATION_CURL(), EncodedInput::Type::RING); #endif #if FINGER_PINKY_ENABLED - auto* fingerPinky = new FingerSensor(PIN_FINGER_PINKY, FINGER_PINKY_INVERT, new CALIBRATION_CURL()); + auto* fingerPinky = new FingerSensor(new OH::AnalogSensor(PIN_FINGER_PINKY), new CALIBRATION_CURL(), EncodedInput::Type::PINKY); #endif FingerSensor* fingers[FINGER_COUNT] = { @@ -59,10 +61,10 @@ FingerSensor* fingers[FINGER_COUNT] = { #pragma endregion -OH::JoystickAxisSensor* joystick[JOYSTICK_COUNT] = { +OH::MemoizedSensor* joystick[JOYSTICK_COUNT] = { #if JOYSTICK_ENABLED - new OH::JoystickAxisSensor(PIN_JOYSTICK_X, JOYSTICK_DEADZONE, JOYSTICK_X_INVERT), - new OH::JoystickAxisSensor(PIN_JOYSTICK_Y, JOYSTICK_DEADZONE, JOYSTICK_Y_INVERT), + new OH::MemoizedSensor(new OH::JoystickAxisSensor(new OH::AnalogSensor(PIN_JOYSTICK_X), JOYSTICK_DEADZONE)), + new OH::MemoizedSensor(new OH::JoystickAxisSensor(new OH::AnalogSensor(PIN_JOYSTICK_Y), JOYSTICK_DEADZONE)), #endif }; @@ -77,5 +79,11 @@ void setupMode() { } void loopMode() { + for (int i = 0; i < FINGER_COUNT; i++) { + // fingers[i]->readValue(); + } + for (int i = 0; i < JOYSTICK_COUNT; i++) { + joystick[i]->updateValue(); + } } diff --git a/lib/arduino/sensor/analog.hpp b/lib/arduino/sensor/analog.hpp new file mode 100644 index 00000000..7d7fa6d9 --- /dev/null +++ b/lib/arduino/sensor/analog.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include + +namespace OH +{ + template + class AnalogSensor : public ISensor + { + private: + uint8_t pin; + + public: + AnalogSensor(uint8_t pin) : pin(pin) {} + + void setup(void) + { + pinMode(this->pin, INPUT); + }; + + uint16_t getValue(void) override; + }; + + template <> + uint16_t AnalogSensor::getValue(void) + { + return analogRead(this->pin); + } + + template <> + uint16_t AnalogSensor::getValue(void) + { + return ANALOG_MAX - analogRead(this->pin); + } +} // namespace OH diff --git a/lib/arduino/sensor/joystick.hpp b/lib/arduino/sensor/joystick.hpp index 0d94ea5a..32c28d30 100644 --- a/lib/arduino/sensor/joystick.hpp +++ b/lib/arduino/sensor/joystick.hpp @@ -5,11 +5,14 @@ #include namespace OH { - class JoystickAxisSensor : public ISensor { + /** + * Joystick axis sensor decorator + */ + template + class JoystickAxisSensor : public ISensor<_Tp> { private: - uint8_t pin; + ISensor<_Tp>* sensor; float dead_zone; - bool invert; int filterDeadZone(int in) { // This function clamps the input to the center of the range if @@ -19,23 +22,17 @@ namespace OH { return abs(center - in) < dead_zone * ANALOG_MAX ? center : in; } - protected: - uint16_t getValue(void) override { - auto value = analogRead(this->pin); - value = this->filterDeadZone(value); - - if (this->invert) { - value = ANALOG_MAX - value; - } - - return value; - } - public: - JoystickAxisSensor(uint8_t pin, float dead_zone, bool invert = false) : pin(pin), dead_zone(dead_zone), invert(invert) {} + JoystickAxisSensor(ISensor<_Tp>* sensor, float dead_zone) : sensor(sensor), dead_zone(dead_zone) {}; void setup(void) { - pinMode(this->pin, INPUT); + this->sensor->setup(); }; + + uint16_t getValue(void) override { + auto value = this->sensor->getValue(); + value = this->filterDeadZone(value); + return value; + } }; }; // namespace OH diff --git a/lib/opengloves/sensor/og_finger.hpp b/lib/opengloves/sensor/og_finger.hpp index 4a09e0e5..2f7c7385 100644 --- a/lib/opengloves/sensor/og_finger.hpp +++ b/lib/opengloves/sensor/og_finger.hpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -11,33 +12,14 @@ namespace OpenGloves // }; - class FingerSensor : public IFinger, public OH::CalibratedSensor + class FingerSensor : public IFinger, public OH::CalibratedSensor, public EncodedInput { - private: - uint8_t pin; - bool invert; + private: + EncodedInput::Type type; - protected: - uint16_t updateValue(void) override { - auto value = analogRead(this->pin); - - if (this->invert) { - value = ANALOG_MAX - value; - } - - // TODO: add median filter - - return value; - }; - - public: - FingerSensor(uint8_t pin, bool invert, OH::Calibrator* calibrator) : - OH::CalibratedSensor(calibrator), - pin(pin), invert(invert) {}; - - void setup(void) { - pinMode(this->pin, INPUT); - }; + public: + FingerSensor(ISensor* sensor, OH::Calibrator* calibrator, EncodedInput::Type type) + : CalibratedSensor(sensor, calibrator), type(type) {} }; // TODO: add splay finger sensor diff --git a/lib/opengloves_utils/opengloves_protocol.hpp b/lib/opengloves_utils/opengloves_protocol.hpp new file mode 100644 index 00000000..ead46e27 --- /dev/null +++ b/lib/opengloves_utils/opengloves_protocol.hpp @@ -0,0 +1,24 @@ +#pragma once + +namespace OpenGloves +{ + struct EncodedInput { + 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' + }; + }; +} // namespace OpenGloves diff --git a/lib/sensor/sensor.hpp b/lib/sensor/sensor.hpp index a1ce2043..d0e8af2c 100644 --- a/lib/sensor/sensor.hpp +++ b/lib/sensor/sensor.hpp @@ -102,7 +102,7 @@ namespace OH }; void resetCalibration() override { - calibrator->reset(); - } + this->calibrator->reset(); + }; }; } // namespace OH From 4be4071dcd543366ac14df6bd361e0a7c75370ae Mon Sep 17 00:00:00 2001 From: Leonid Meleshin Date: Fri, 9 Jun 2023 21:31:32 +0400 Subject: [PATCH 11/23] feat(OpenGloves): ouput fingers --- .../mode_configs/opengloves/opengloves.cpp | 73 +++++++++++++++---- lib/calibration/calibration.hpp | 14 ++-- lib/core/utility.hpp | 1 + lib/opengloves/sensor/og_finger.hpp | 49 +++++++++---- lib/opengloves/sensor/og_joystick.hpp | 8 ++ lib/opengloves/sensor/og_sensor.hpp | 23 ++++++ lib/opengloves_utils/opengloves_protocol.hpp | 47 +++++++----- lib/sensor/sensor.hpp | 25 +++++-- 8 files changed, 180 insertions(+), 60 deletions(-) create mode 100644 lib/opengloves/sensor/og_joystick.hpp create 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 9ca4d895..d74af2d2 100644 --- a/firmware/mode_configs/opengloves/opengloves.cpp +++ b/firmware/mode_configs/opengloves/opengloves.cpp @@ -3,6 +3,7 @@ #include #include #include + #include #define CALIBRATION_CURL OH::MinMaxCalibrator @@ -14,31 +15,37 @@ #define FINGER_PINKY_ENABLED (PIN_FINGER_PINKY != -1) #define FINGER_COUNT (FINGER_THUMB_ENABLED + FINGER_INDEX_ENABLED + FINGER_MIDDLE_ENABLED + FINGER_RING_ENABLED + FINGER_PINKY_ENABLED) +#define FINGER_CLASS(type, pin, invert, calib) new FingerSensor(new OH::CalibratedSensor(new OH::AnalogSensor(pin), new calib()), type) + #define JOYSTICK_ENABLED (PIN_JOYSTICK_X != -1 && PIN_JOYSTICK_Y != -1) #define JOYSTICK_COUNT (JOYSTICK_ENABLED ? 2 : 0) +#define JOYSTICK_CLASS(type, pin, invert, deadzone) new OH::MemoizedSensor(new OH::JoystickAxisSensor(new OH::AnalogSensor(pin), deadzone)) + +#define INPUTS_COUNT (FINGER_COUNT) + using namespace OpenGloves; #pragma region FingerSensor #if FINGER_THUMB_ENABLED - auto* fingerThumb = new FingerSensor(new OH::AnalogSensor(PIN_FINGER_THUMB), new CALIBRATION_CURL(), EncodedInput::Type::THUMB); + auto* fingerThumb = FINGER_CLASS(EncodedInput::Type::THUMB, PIN_FINGER_THUMB, FINGER_THUMB_INVERT, CALIBRATION_CURL); #endif #if FINGER_INDEX_ENABLED - auto* fingerIndex = new FingerSensor(new OH::AnalogSensor(PIN_FINGER_INDEX), new CALIBRATION_CURL(), EncodedInput::Type::INDEX); + auto* fingerIndex = FINGER_CLASS(EncodedInput::Type::INDEX, PIN_FINGER_INDEX, FINGER_INDEX_INVERT, CALIBRATION_CURL); #endif #if FINGER_MIDDLE_ENABLED - auto* fingerMiddle = new FingerSensor(new OH::AnalogSensor(PIN_FINGER_MIDDLE), new CALIBRATION_CURL(), EncodedInput::Type::MIDDLE); + auto* fingerMiddle = FINGER_CLASS(EncodedInput::Type::MIDDLE, PIN_FINGER_MIDDLE, FINGER_MIDDLE_INVERT, CALIBRATION_CURL); #endif #if FINGER_RING_ENABLED - auto* fingerRing = new FingerSensor(new OH::AnalogSensor(PIN_FINGER_RING), new CALIBRATION_CURL(), EncodedInput::Type::RING); + auto* fingerRing = FINGER_CLASS(EncodedInput::Type::RING, PIN_FINGER_RING, FINGER_RING_INVERT, CALIBRATION_CURL); #endif #if FINGER_PINKY_ENABLED - auto* fingerPinky = new FingerSensor(new OH::AnalogSensor(PIN_FINGER_PINKY), new CALIBRATION_CURL(), EncodedInput::Type::PINKY); + auto* fingerPinky = FINGER_CLASS(EncodedInput::Type::PINKY, PIN_FINGER_PINKY, FINGER_PINKY_INVERT, CALIBRATION_CURL); #endif FingerSensor* fingers[FINGER_COUNT] = { @@ -63,27 +70,63 @@ FingerSensor* fingers[FINGER_COUNT] = { OH::MemoizedSensor* joystick[JOYSTICK_COUNT] = { #if JOYSTICK_ENABLED - new OH::MemoizedSensor(new OH::JoystickAxisSensor(new OH::AnalogSensor(PIN_JOYSTICK_X), JOYSTICK_DEADZONE)), - new OH::MemoizedSensor(new OH::JoystickAxisSensor(new OH::AnalogSensor(PIN_JOYSTICK_Y), JOYSTICK_DEADZONE)), + JOYSTICK_CLASS(EncodedInput::Type::JOY_X, PIN_JOYSTICK_X, JOYSTICK_X_INVERT, JOYSTICK_DEADZONE), + JOYSTICK_CLASS(EncodedInput::Type::JOY_Y, PIN_JOYSTICK_Y, JOYSTICK_Y_INVERT, JOYSTICK_DEADZONE), #endif }; +IEncodedSensor* inputs[INPUTS_COUNT]; +char* encoded_output_string; + void setupMode() { - for (int i = 0; i < FINGER_COUNT; i++) { - fingers[i]->setup(); + size_t currentSensor = 0; + + for (size_t i = 0; i < FINGER_COUNT; i++) { + auto* finger = fingers[i]; + inputs[currentSensor++] = finger; + finger->setup(); + finger->enableCalibration(); } - for (int i = 0; i < JOYSTICK_COUNT; i++) { + for (size_t i = 0; i < JOYSTICK_COUNT; i++) { + // sensors[currentSensor++] = joystick[i]; joystick[i]->setup(); } + + int string_size = 0; + for(size_t i = 0; i < INPUTS_COUNT; i++) { + string_size += inputs[i]->getEncodedSize(); + } + + // Add 1 for new line and 1 for the null terminator. + encoded_output_string = new char[string_size + 1 + 1]; + + Serial.begin(115200); } -void loopMode() { - for (int i = 0; i < FINGER_COUNT; i++) { - // fingers[i]->readValue(); +int encodeAll(char* output, IEncodedSensor* encoders[], size_t count) { + int offset = 0; + // Loop over all of the encoders and encode them to the output string. + for (size_t i = 0; i < count; i++) { + // The offset is the total charecters already added to the string. + offset += encoders[i]->encode(output+offset); } - for (int i = 0; i < JOYSTICK_COUNT; i++) { - joystick[i]->updateValue(); + // Add a new line to the end of the encoded string. + output[offset++] = '\n'; + output[offset] = '\0'; + + return offset; +} + +void loopMode() { + // update all sensor values + for (int i = 0; i < INPUTS_COUNT; i++) { + auto* sensor = inputs[i]; + sensor->updateValue(); } + + // encode all sensor values + encodeAll(encoded_output_string, inputs, INPUTS_COUNT); + Serial.print(encoded_output_string); } diff --git a/lib/calibration/calibration.hpp b/lib/calibration/calibration.hpp index a4f9894e..2012c7b8 100644 --- a/lib/calibration/calibration.hpp +++ b/lib/calibration/calibration.hpp @@ -9,19 +9,19 @@ namespace OH { class Calibrated { + protected: + bool calibrate = false; + public: virtual void resetCalibration() = 0; - virtual void enableCalibration() { + void enableCalibration() { calibrate = true; } - virtual void disableCalibration() { + void disableCalibration() { calibrate = false; } - - protected: - bool calibrate; }; template @@ -54,11 +54,11 @@ namespace OH { 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; } diff --git a/lib/core/utility.hpp b/lib/core/utility.hpp index 7533b8e1..f793c0f9 100644 --- a/lib/core/utility.hpp +++ b/lib/core/utility.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include diff --git a/lib/opengloves/sensor/og_finger.hpp b/lib/opengloves/sensor/og_finger.hpp index 2f7c7385..a5c02521 100644 --- a/lib/opengloves/sensor/og_finger.hpp +++ b/lib/opengloves/sensor/og_finger.hpp @@ -1,25 +1,48 @@ #pragma once -#include -#include -#include - -#include +#include "og_sensor.hpp" namespace OpenGloves { - struct IFinger { - // + class IFinger : public ICalibratedEncodedSensor, public OH::MemoizedSensor { + public : + IFinger(OH::CalibratedSensor* sensor, EncodedInput::Type type) + : ICalibratedEncodedSensor(type), OH::MemoizedSensor(sensor) { }; }; - class FingerSensor : public IFinger, public OH::CalibratedSensor, public EncodedInput + class FingerSensor : public IFinger { - private: - EncodedInput::Type type; - public: - FingerSensor(ISensor* sensor, OH::Calibrator* calibrator, EncodedInput::Type type) - : CalibratedSensor(sensor, calibrator), type(type) {} + FingerSensor(OH::CalibratedSensor* sensor, EncodedInput::Type type) : IFinger(sensor, type) { }; + + void setup() override { + this->sensor->setup(); + } + + void updateValue() override { + this->value = this->sensor->getValue(); + } + + void resetCalibration() override { + static_cast*>(this->sensor)->resetCalibration(); + } + + void enableCalibration() override { + static_cast*>(this->sensor)->enableCalibration(); + } + + void disableCalibration() override { + static_cast*>(this->sensor)->disableCalibration(); + } + + size_t getEncodedSize() const override { + // Encode string size = AXXXX + '\0' + return 6; + } + + int encode(char* buffer) const override { + return snprintf(buffer, this->getEncodedSize(), "%c%04d", this->getType(), this->value); + } }; // TODO: add splay finger sensor diff --git a/lib/opengloves/sensor/og_joystick.hpp b/lib/opengloves/sensor/og_joystick.hpp new file mode 100644 index 00000000..3d6f63c2 --- /dev/null +++ b/lib/opengloves/sensor/og_joystick.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include "og_sensor.hpp" + +namespace OpenGloves +{ + // typedef SingleAnalogValueEncodedSensor EncidedJoystickSensor; +} // namespace OpenGloves diff --git a/lib/opengloves/sensor/og_sensor.hpp b/lib/opengloves/sensor/og_sensor.hpp new file mode 100644 index 00000000..35e890a6 --- /dev/null +++ b/lib/opengloves/sensor/og_sensor.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include + +namespace OpenGloves +{ + class IEncodedSensor : public EncodedInput { + public: + IEncodedSensor(Type type) : EncodedInput(type) { }; + virtual void setup() = 0; + virtual void updateValue() = 0; + }; + + struct ICalibratedEncodedSensor : public IEncodedSensor { + public: + ICalibratedEncodedSensor(Type type) : IEncodedSensor(type) { }; + virtual void resetCalibration() = 0; + virtual void enableCalibration() = 0; + virtual void disableCalibration() = 0; + }; +} // namespace OpenGloves diff --git a/lib/opengloves_utils/opengloves_protocol.hpp b/lib/opengloves_utils/opengloves_protocol.hpp index ead46e27..417c20bf 100644 --- a/lib/opengloves_utils/opengloves_protocol.hpp +++ b/lib/opengloves_utils/opengloves_protocol.hpp @@ -3,22 +3,35 @@ namespace OpenGloves { struct EncodedInput { - 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' - }; + 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' + }; + + EncodedInput(Type type) : type(type) { }; + + constexpr Type getType() const { + return this->type; + } + + virtual size_t getEncodedSize() const = 0; + virtual int encode(char* buffer) const = 0; + + private: + Type type; }; } // namespace OpenGloves diff --git a/lib/sensor/sensor.hpp b/lib/sensor/sensor.hpp index d0e8af2c..7c27608b 100644 --- a/lib/sensor/sensor.hpp +++ b/lib/sensor/sensor.hpp @@ -2,6 +2,7 @@ #include +#include #include #if defined(__AVR__) @@ -27,7 +28,7 @@ namespace OH /** * Setup the sensor hardware */ - virtual void setup() {}; + virtual void setup() = 0; /** * Get the current sensor value @@ -84,6 +85,16 @@ namespace OH ISensor<_Tp>* sensor; Calibrator<_Tp>* calibrator; + _Tp getCalibratedValue() { + auto value = this->sensor->getValue(); + + if (this->calibrate) { + this->calibrator->update(value); + } + + return this->calibrator->calibrate(value); + } + public: /** * @param sensor Sensor to be decorated @@ -91,14 +102,12 @@ namespace OH */ CalibratedSensor(ISensor<_Tp>* sensor, Calibrator<_Tp>* calibrator) : sensor(sensor), calibrator(calibrator) {}; - _Tp getValue() override { - auto value = this->sensor->getValue(); - - if (this->calibrate) { - this->calibrator->update(value); - } + void setup() override { + this->sensor->setup(); + }; - return this->calibrator->calibrate(value); + _Tp getValue() override { + return this->getCalibratedValue(); }; void resetCalibration() override { From 19a1027af63e03fb798ff57ce5e60ceee6a3fbee Mon Sep 17 00:00:00 2001 From: Leonid Meleshin Date: Fri, 9 Jun 2023 22:18:33 +0400 Subject: [PATCH 12/23] ci(GitHub): job for OpenGloves --- .github/workflows/ci.yml | 117 ++++++++++++++++-- .../mode_configs/opengloves/opengloves.cpp | 2 + lib/calibration/calibration.hpp | 56 ++++----- 3 files changed, 140 insertions(+), 35 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ec8db0f..a50a53a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,10 +58,6 @@ jobs: serial_plotter_flag: SERIAL_PLOTTER=false nimble_flag: BLUETOOTH_USE_NIMBLE=true - - target: opengloves - os: ubuntu-latest - coverage: true - steps: - uses: actions/checkout@v3 @@ -84,7 +80,9 @@ jobs: - name: Cache PlatformIO uses: actions/cache@v3 with: - path: ~/.platformio + path: | + ~/.platformio/.cache + ./.pio key: ${{ runner.os }}-pio-${{ matrix.target }}-${{ hashFiles('platformio.ini') }} restore-keys: | ${{ runner.os }}-pio-${{ matrix.target }}- @@ -153,6 +151,109 @@ jobs: path: ./build/lcov/lcov.info.${{matrix.target}} retention-days: 5 + build-opengloves: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ ubuntu-latest ] + target: + - opengloves + curl_calibration_flag: + - CALIBRATION_CURL="OH::MinMaxCalibrator" + - CALIBRATION_CURL="OH::CenterPointDeviationCalibrator" + - CALIBRATION_CURL="OH::FixedCenterPointDeviationCalibrator" + coverage: [ true ] + + steps: + - uses: actions/checkout@v3 + + - name: Speedup package installation + if: matrix.coverage + uses: abbbi/github-actions-tune@v1 + + - name: Setup LCOV + if: matrix.coverage + uses: hrishikesh-kadam/setup-lcov@v1 + + - 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/.cache + ./.pio + key: ${{ runner.os }}-pio-${{ matrix.target }}-${{ hashFiles('platformio.ini') }} + restore-keys: | + ${{ runner.os }}-pio-${{ matrix.target }}- + ${{ runner.os }}-pio- + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.9' + + - name: Enable coverage (non-macOS) + if: runner.os != 'macOS' && matrix.coverage + run: | + sed -i '/__OH_FIRMWARE__/p; s/-D __OH_FIRMWARE__/-lgcov --coverage/' platformio.ini + - name: Enable coverage (macOS) + if: runner.os == 'macOS' && matrix.coverage + run: | + sed -i '' '/__OH_FIRMWARE__/p; s/-D __OH_FIRMWARE__/-lgcov --coverage/' platformio.ini + + - name: Update build flags (non-macOS) + if: runner.os != 'macOS' + run: | + sed -i '/__OH_FIRMWARE__/p; s/__OH_FIRMWARE__/${{ matrix.curl_calibration_flag }}/' platformio.ini + - name: Update build flags (macOS) + if: runner.os == 'macOS' + run: | + sed -i '' '/__OH_FIRMWARE__/p; s/__OH_FIRMWARE__/${{ matrix.curl_calibration_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: Install libs + run: pio pkg install -e ${{matrix.target}} + + - 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 + + - name: Build + run: | + echo "::group::platformio.ini" + cat platformio.ini + echo "::endgroup::" + pio run --environment ${{matrix.target}} + + - name: Collect initial coverage + if: matrix.coverage + run: | + mkdir -p ./build/lcov + lcov -i -d ./.pio/build/${{matrix.target}}/ -c -o ./build/lcov/lcov.info.${{matrix.target}}-${{ hashFiles('platformio.ini') }} -gcov-tool ~/.platformio/packages/toolchain-xtensa-esp32/bin/xtensa-esp32-elf-gcov + + - name: Upload coverage Artifact + uses: actions/upload-artifact@v3 + if: matrix.coverage + with: + name: lcov.info.${{matrix.target}}-${{ hashFiles('platformio.ini') }} + path: ./build/lcov/lcov.info.${{matrix.target}}-* + retention-days: 5 + test: runs-on: ubuntu-latest strategy: @@ -181,7 +282,9 @@ jobs: - name: Cache PlatformIO uses: actions/cache@v3 with: - path: ~/.platformio + path: | + ~/.platformio/.cache + ./.pio key: ${{ runner.os }}-pio-${{ matrix.target }}-${{ hashFiles('platformio.ini') }} restore-keys: | ${{ runner.os }}-pio-${{ matrix.target }} @@ -220,7 +323,7 @@ jobs: coverage-report: needs: - build-bhaptics - # - build-opengloves + - build-opengloves - test runs-on: ubuntu-latest steps: diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp index d74af2d2..d52f726a 100644 --- a/firmware/mode_configs/opengloves/opengloves.cpp +++ b/firmware/mode_configs/opengloves/opengloves.cpp @@ -6,7 +6,9 @@ #include +#ifndef CALIBRATION_CURL #define CALIBRATION_CURL OH::MinMaxCalibrator +#endif #define FINGER_THUMB_ENABLED (PIN_FINGER_THUMB != -1) #define FINGER_INDEX_ENABLED (PIN_FINGER_INDEX != -1) diff --git a/lib/calibration/calibration.hpp b/lib/calibration/calibration.hpp index 2012c7b8..d7cc1186 100644 --- a/lib/calibration/calibration.hpp +++ b/lib/calibration/calibration.hpp @@ -24,15 +24,15 @@ namespace OH { } }; - template + template struct Calibrator { virtual void reset() = 0; - virtual void update(T input) = 0; - virtual T calibrate(T input) const = 0; + virtual void update(_Tp input) = 0; + virtual _Tp calibrate(_Tp input) const = 0; }; - template - class MinMaxCalibrator : public Calibrator { + template + class MinMaxCalibrator : public Calibrator<_Tp> { public: MinMaxCalibrator() : value_min(output_max), value_max(output_min) {} @@ -41,13 +41,13 @@ namespace OH { value_max = output_min; } - void update(T input) { + void update(_Tp input) { // Update the min and the max. if (input < value_min) value_min = input; if (input > value_max) value_max = input; } - T calibrate(T input) const { + _Tp calibrate(_Tp input) const { // 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) { @@ -63,19 +63,19 @@ namespace OH { } // Map the input range to the output range. - T output = accurateMap(input, value_min, value_max, output_min, output_max); + _Tp output = accurateMap<_Tp>(input, value_min, value_max, output_min, output_max); // Lock the range to the output. return constrain(output, output_min, output_max); } private: - T value_min; - T value_max; + _Tp value_min; + _Tp value_max; }; - template - class CenterPointDeviationCalibrator : public Calibrator { + template + class CenterPointDeviationCalibrator : public Calibrator<_Tp> { public: CenterPointDeviationCalibrator() : range_min(sensor_max), range_max(0) {} @@ -84,49 +84,49 @@ namespace OH { range_max = 0; } - void update(T input) { + void update(_Tp input) { // Update the min and the max. - if (input < range_min) range_min = accurateMap(input, output_min, output_max, 0, sensor_max); - if (input > range_max) range_max = accurateMap(input, output_min, output_max, 0, sensor_max); + if (input < range_min) range_min = 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); } - T calibrate(T input) const { + _Tp calibrate(_Tp input) const { // Find the center point of the sensor so we know how much we have deviated from it. - T center = (range_min + range_max) / 2.0f; + _Tp center = (range_min + range_max) / 2.0f; // Map the input to the sensor range of motion. - T output = accurateMap(input, output_min, output_max, 0, sensor_max); + _Tp output = accurateMap<_Tp>(input, output_min, output_max, 0, sensor_max); // Find the deviation from the center and constrain it to the maximum that the driver supports. output = constrain(output - center, -driver_max_deviation, driver_max_deviation); // Finally map the deviation from the center back to the output range. - return accurateMap(output, -driver_max_deviation, driver_max_deviation, output_min, output_max); + return accurateMap(output, -driver_max_deviation, driver_max_deviation, output_min, output_max); } private: - T range_min; - T range_max; + _Tp range_min; + _Tp range_max; }; - template - class FixedCenterPointDeviationCalibrator : public Calibrator { + template + class FixedCenterPointDeviationCalibrator : public Calibrator<_Tp> { public: void reset() {} - void update(T input) {} + void update(_Tp input) {} - T calibrate(T input) const { + _Tp calibrate(_Tp input) const { // Find the center point of the sensor so we know how much we have deviated from it. - T center = sensor_max / 2.0f; + _Tp center = sensor_max / 2.0f; // Map the input to the sensor range of motion. - T output = accurateMap(input, output_min, output_max, 0, sensor_max); + _Tp output = accurateMap<_Tp>(input, output_min, output_max, 0, sensor_max); // Find the deviation from the center and constrain it to the maximum that the driver supports. output = constrain(output - center, -driver_max_deviation, driver_max_deviation); // Finally map the deviation from the center back to the output range. - return accurateMap(output, -driver_max_deviation, driver_max_deviation, output_min, output_max); + return (_Tp) accurateMap(output, -driver_max_deviation, driver_max_deviation, output_min, output_max); } }; } From f6c4cf2f94ffd6b48bf57abf9bee7f05fd4825fa Mon Sep 17 00:00:00 2001 From: Leonid Meleshin Date: Sat, 10 Jun 2023 01:12:43 +0400 Subject: [PATCH 13/23] test(Calibration): cover FixedCenterPointDeviation --- lib/calibration/calibration.hpp | 10 ++++--- lib/core/utility.hpp | 11 +++++-- test/test_calibration/main.cpp | 51 +++++++++++++++++++++++++++++++++ test/test_core_utility/main.cpp | 2 ++ 4 files changed, 68 insertions(+), 6 deletions(-) diff --git a/lib/calibration/calibration.hpp b/lib/calibration/calibration.hpp index d7cc1186..09297028 100644 --- a/lib/calibration/calibration.hpp +++ b/lib/calibration/calibration.hpp @@ -77,7 +77,9 @@ namespace OH { template class CenterPointDeviationCalibrator : public Calibrator<_Tp> { public: - CenterPointDeviationCalibrator() : range_min(sensor_max), range_max(0) {} + CenterPointDeviationCalibrator() : range_min(sensor_max), range_max(0) { + #warning "CenterPointDeviationCalibrator is untested and may not work as expected." + } void reset() { range_min = sensor_max; @@ -95,13 +97,13 @@ namespace OH { _Tp center = (range_min + range_max) / 2.0f; // Map the input to the sensor range of motion. - _Tp output = accurateMap<_Tp>(input, output_min, output_max, 0, sensor_max); + int output = accurateMap<_Tp>(input, output_min, output_max, 0, sensor_max); // Find the deviation from the center and constrain it to the maximum that the driver supports. output = constrain(output - center, -driver_max_deviation, driver_max_deviation); // Finally map the deviation from the center back to the output range. - return accurateMap(output, -driver_max_deviation, driver_max_deviation, output_min, output_max); + return (_Tp) accurateMap(output, -driver_max_deviation, driver_max_deviation, output_min, output_max); } private: @@ -120,7 +122,7 @@ namespace OH { _Tp center = sensor_max / 2.0f; // Map the input to the sensor range of motion. - _Tp output = accurateMap<_Tp>(input, output_min, output_max, 0, sensor_max); + int output = accurateMap<_Tp>(input, output_min, output_max, 0, sensor_max); // Find the deviation from the center and constrain it to the maximum that the driver supports. output = constrain(output - center, -driver_max_deviation, driver_max_deviation); diff --git a/lib/core/utility.hpp b/lib/core/utility.hpp index f793c0f9..d80469c8 100644 --- a/lib/core/utility.hpp +++ b/lib/core/utility.hpp @@ -31,8 +31,15 @@ namespace OH { }; template - constexpr inline _Tp accurateMap(_Tp x, _Tp in_min, _Tp in_max, _Tp out_min, _Tp out_max) { - return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; + 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. diff --git a/test/test_calibration/main.cpp b/test/test_calibration/main.cpp index 5d97d41f..fffcde13 100644 --- a/test/test_calibration/main.cpp +++ b/test/test_calibration/main.cpp @@ -34,10 +34,61 @@ void test_minmax_calibrator(void) { TEST_ASSERT_EQUAL_UINT16(2048, calibrator->calibrate(4096)); } +void test_fixed_center_point_deviation_calibrator(void) { + auto calibrator = new FixedCenterPointDeviationCalibrator(); + + // below deviation + TEST_ASSERT_EQUAL_UINT16(0, calibrator->calibrate(0)); + TEST_ASSERT_EQUAL_UINT16(0, calibrator->calibrate(10)); + TEST_ASSERT_EQUAL_UINT16(0, calibrator->calibrate(1234)); + TEST_ASSERT_EQUAL_UINT16(0, calibrator->calibrate(1536)); + // center point + TEST_ASSERT_EQUAL_UINT16(32, calibrator->calibrate(1544)); + TEST_ASSERT_EQUAL_UINT16(32, calibrator->calibrate(1550)); + TEST_ASSERT_EQUAL_UINT16(64, calibrator->calibrate(1555)); + TEST_ASSERT_EQUAL_UINT16(96, calibrator->calibrate(1560)); + TEST_ASSERT_EQUAL_UINT16(256, calibrator->calibrate(1600)); + TEST_ASSERT_EQUAL_UINT16(1056, calibrator->calibrate(1800)); + TEST_ASSERT_EQUAL_UINT16(1440, calibrator->calibrate(1900)); + TEST_ASSERT_EQUAL_UINT16(2048, calibrator->calibrate(2048)); + TEST_ASSERT_EQUAL_UINT16(2656, calibrator->calibrate(2200)); + TEST_ASSERT_EQUAL_UINT16(3232, calibrator->calibrate(2345)); + // above deviation + TEST_ASSERT_EQUAL_UINT16(4096, calibrator->calibrate(4086)); + TEST_ASSERT_EQUAL_UINT16(4096, calibrator->calibrate(4096)); + + calibrator->reset(); + calibrator->update(1234); + calibrator->update(2345); + + // update does not change the calibration as the deviation is fixed + + // below deviation + TEST_ASSERT_EQUAL_UINT16(0, calibrator->calibrate(0)); + TEST_ASSERT_EQUAL_UINT16(0, calibrator->calibrate(10)); + TEST_ASSERT_EQUAL_UINT16(0, calibrator->calibrate(1234)); + TEST_ASSERT_EQUAL_UINT16(0, calibrator->calibrate(1536)); + // center point + TEST_ASSERT_EQUAL_UINT16(32, calibrator->calibrate(1544)); + TEST_ASSERT_EQUAL_UINT16(32, calibrator->calibrate(1550)); + TEST_ASSERT_EQUAL_UINT16(64, calibrator->calibrate(1555)); + TEST_ASSERT_EQUAL_UINT16(96, calibrator->calibrate(1560)); + TEST_ASSERT_EQUAL_UINT16(256, calibrator->calibrate(1600)); + TEST_ASSERT_EQUAL_UINT16(1056, calibrator->calibrate(1800)); + TEST_ASSERT_EQUAL_UINT16(1440, calibrator->calibrate(1900)); + TEST_ASSERT_EQUAL_UINT16(2048, calibrator->calibrate(2048)); + TEST_ASSERT_EQUAL_UINT16(2656, calibrator->calibrate(2200)); + TEST_ASSERT_EQUAL_UINT16(3232, calibrator->calibrate(2345)); + // above deviation + TEST_ASSERT_EQUAL_UINT16(4096, calibrator->calibrate(4086)); + TEST_ASSERT_EQUAL_UINT16(4096, calibrator->calibrate(4096)); +} + int process(void) { UNITY_BEGIN(); RUN_TEST(test_minmax_calibrator); + RUN_TEST(test_fixed_center_point_deviation_calibrator); return UNITY_END(); } diff --git a/test/test_core_utility/main.cpp b/test/test_core_utility/main.cpp index d8afab4f..4e9c684f 100644 --- a/test/test_core_utility/main.cpp +++ b/test/test_core_utility/main.cpp @@ -54,6 +54,8 @@ void test_accurate_map(void) { 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) { From 7d6183871e87f649028251d10f86229b0d54b63f Mon Sep 17 00:00:00 2001 From: Leonid Meleshin Date: Sat, 10 Jun 2023 01:36:29 +0400 Subject: [PATCH 14/23] docs(OpenGloves): add credits --- .vscode/settings.json | 7 ++++++- README.md | 24 +++++++++++++++--------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index ae85168d..85901741 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -53,7 +53,12 @@ "bit": "cpp", "set": "cpp", "iostream": "cpp", - "*.hpp": "cpp" + "*.hpp": "cpp", + "xlocmon": "cpp", + "xmemory": "cpp", + "xtree": "cpp", + "xhash": "cpp", + "xlocnum": "cpp" }, "files.eol": "\n", "files.insertFinalNewline": true, diff --git a/README.md b/README.md index 1b440d7e..be158554 100644 --- a/README.md +++ b/README.md @@ -73,21 +73,27 @@ Please note, that documentation is still work-in-progress ## Supported devices -| Device | Supported Devices | Retail price | DIY Price | Hardware | -| :-------------------- | :------------------- | -----------: | --------: | :-------------------------------------------------------------------------------------------------- | -| Haptics Face Interface | Tactal | US $149 | ~20$ | See [Hardware Reference](https://github.com/senseshift/senseshift-hardware#haptic-face-interface) | -| Haptic Gloves | TactGlove | US $299 | ~20$ | See [Hardware Reference](https://github.com/senseshift/senseshift-hardware#haptic-glove) | -| Haptic Sleeves | Tactosy for arms | US $249 | ~20$ | See [Hardware Reference](https://github.com/senseshift/senseshift-hardware#haptic-forearm-sleeve) | -| Haptic Hand Gauntlet | Tactosy for hands | US $249 | ~20$ | See [Hardware Reference](https://github.com/senseshift/senseshift-hardware#haptic-gauntlet) | -| Haptic Feet Device | Tactosy for feet | US $249 | ~20$ | See [Hardware Reference](https://github.com/senseshift/senseshift-hardware#haptic-feet-device) | -| X16 Haptic Vest | TactSuit X16 | US $299 | ~40$ | See [Hardware Reference](https://github.com/senseshift/senseshift-hardware#x16-haptic-vest) | -| X40 Haptic Vest | TactSuit X40, Tactot | US $499 | ~70$ | See [Hardware Reference](https://github.com/senseshift/senseshift-hardware#x40-haptic-vest) | +| Device | Supported Devices | Retail price | DIY Price | Hardware | +| :-------------------- | :------------------- | -----------: | ----------------: | :------------------------------------------------------------------------------------------------ | +| Haptics Face Interface | Tactal, TactVisor | US $149 | ~$20 | See [Hardware Reference](https://github.com/senseshift/senseshift-hardware#haptic-face-interface) | +| Haptic Gloves | TactGlove | US $299 | ~$20 | See [Hardware Reference](https://github.com/senseshift/senseshift-hardware#haptic-glove) | +| Haptic Sleeves | Tactosy for arms | US $249 | ~$20 | See [Hardware Reference](https://github.com/senseshift/senseshift-hardware#haptic-forearm-sleeve) | +| Haptic Hand Gauntlet | Tactosy for hands | US $249 | ~$20 | See [Hardware Reference](https://github.com/senseshift/senseshift-hardware#haptic-gauntlet) | +| Haptic Feet Device | Tactosy for feet | US $249 | ~$20 | See [Hardware Reference](https://github.com/senseshift/senseshift-hardware#haptic-feet-device) | +| X16 Haptic Vest | TactSuit X16 | US $299 | ~$40 | See [Hardware Reference](https://github.com/senseshift/senseshift-hardware#x16-haptic-vest) | +| X40 Haptic Vest | TactSuit X40, Tactot | US $499 | ~$70 | See [Hardware Reference](https://github.com/senseshift/senseshift-hardware#x40-haptic-vest) | +| VR Glove / OpenGloves | LucidGloves | N/A | ~$40 — ~$80 | See [Original Wiki](https://github.com/LucidVR/lucidgloves/wiki) | ## For Developers * [Code of Conduct](./CODE_OF_CONDUCT.md) * [Contributing Guidelines](./CONTRIBUTING.md) +## Credits + +* [LucasVRTech](https://github.com/lucas-vrtech) of the LucidGlove project +* [JohnRThomas](https://github.com/JohnRThomas) for the his implementation of LucidGlove firmware + ## Licensing [![GPL-3.0](https://www.gnu.org/graphics/gplv3-or-later-sm.png)](./LICENSE) From 0909a7abc17abb475916798c03bf14f2edbc41d0 Mon Sep 17 00:00:00 2001 From: Leonid Meleshin Date: Sat, 10 Jun 2023 17:22:55 +0400 Subject: [PATCH 15/23] feat(OpenGloves): add serial communication class --- .../mode_configs/opengloves/opengloves.cpp | 64 +++++-------------- .../og_protocol.hpp} | 18 +++++- lib/opengloves/sensor/og_finger.hpp | 4 +- lib/opengloves/sensor/og_sensor.hpp | 9 +-- .../og_serial_commmunications.hpp | 36 +++++++++++ 5 files changed, 72 insertions(+), 59 deletions(-) rename lib/{opengloves_utils/opengloves_protocol.hpp => opengloves/og_protocol.hpp} (60%) create mode 100644 lib/opengloves_serial/og_serial_commmunications.hpp diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp index d52f726a..a8e758bb 100644 --- a/firmware/mode_configs/opengloves/opengloves.cpp +++ b/firmware/mode_configs/opengloves/opengloves.cpp @@ -3,8 +3,8 @@ #include #include #include - #include +#include #ifndef CALIBRATION_CURL #define CALIBRATION_CURL OH::MinMaxCalibrator @@ -24,30 +24,28 @@ #define JOYSTICK_CLASS(type, pin, invert, deadzone) new OH::MemoizedSensor(new OH::JoystickAxisSensor(new OH::AnalogSensor(pin), deadzone)) -#define INPUTS_COUNT (FINGER_COUNT) - using namespace OpenGloves; #pragma region FingerSensor #if FINGER_THUMB_ENABLED - auto* fingerThumb = FINGER_CLASS(EncodedInput::Type::THUMB, PIN_FINGER_THUMB, FINGER_THUMB_INVERT, CALIBRATION_CURL); + auto* fingerThumb = FINGER_CLASS(IEncodedInput::Type::THUMB, PIN_FINGER_THUMB, FINGER_THUMB_INVERT, CALIBRATION_CURL); #endif #if FINGER_INDEX_ENABLED - auto* fingerIndex = FINGER_CLASS(EncodedInput::Type::INDEX, PIN_FINGER_INDEX, FINGER_INDEX_INVERT, CALIBRATION_CURL); + auto* fingerIndex = FINGER_CLASS(IEncodedInput::Type::INDEX, PIN_FINGER_INDEX, FINGER_INDEX_INVERT, CALIBRATION_CURL); #endif #if FINGER_MIDDLE_ENABLED - auto* fingerMiddle = FINGER_CLASS(EncodedInput::Type::MIDDLE, PIN_FINGER_MIDDLE, FINGER_MIDDLE_INVERT, CALIBRATION_CURL); + auto* fingerMiddle = FINGER_CLASS(IEncodedInput::Type::MIDDLE, PIN_FINGER_MIDDLE, FINGER_MIDDLE_INVERT, CALIBRATION_CURL); #endif #if FINGER_RING_ENABLED - auto* fingerRing = FINGER_CLASS(EncodedInput::Type::RING, PIN_FINGER_RING, FINGER_RING_INVERT, CALIBRATION_CURL); + auto* fingerRing = FINGER_CLASS(IEncodedInput::Type::RING, PIN_FINGER_RING, FINGER_RING_INVERT, CALIBRATION_CURL); #endif #if FINGER_PINKY_ENABLED - auto* fingerPinky = FINGER_CLASS(EncodedInput::Type::PINKY, PIN_FINGER_PINKY, FINGER_PINKY_INVERT, CALIBRATION_CURL); + auto* fingerPinky = FINGER_CLASS(IEncodedInput::Type::PINKY, PIN_FINGER_PINKY, FINGER_PINKY_INVERT, CALIBRATION_CURL); #endif FingerSensor* fingers[FINGER_COUNT] = { @@ -72,63 +70,35 @@ FingerSensor* fingers[FINGER_COUNT] = { OH::MemoizedSensor* joystick[JOYSTICK_COUNT] = { #if JOYSTICK_ENABLED - JOYSTICK_CLASS(EncodedInput::Type::JOY_X, PIN_JOYSTICK_X, JOYSTICK_X_INVERT, JOYSTICK_DEADZONE), - JOYSTICK_CLASS(EncodedInput::Type::JOY_Y, PIN_JOYSTICK_Y, JOYSTICK_Y_INVERT, JOYSTICK_DEADZONE), + JOYSTICK_CLASS(IEncodedInput::Type::JOY_X, PIN_JOYSTICK_X, JOYSTICK_X_INVERT, JOYSTICK_DEADZONE), + JOYSTICK_CLASS(IEncodedInput::Type::JOY_Y, PIN_JOYSTICK_Y, JOYSTICK_Y_INVERT, JOYSTICK_DEADZONE), #endif }; -IEncodedSensor* inputs[INPUTS_COUNT]; -char* encoded_output_string; +std::vector inputs = std::vector(); + +auto communication = new SerialCommunication(&Serial); void setupMode() { - size_t currentSensor = 0; + communication->setup(); for (size_t i = 0; i < FINGER_COUNT; i++) { auto* finger = fingers[i]; - inputs[currentSensor++] = finger; + inputs.push_back(finger); finger->setup(); - finger->enableCalibration(); } for (size_t i = 0; i < JOYSTICK_COUNT; i++) { - // sensors[currentSensor++] = joystick[i]; joystick[i]->setup(); } - - int string_size = 0; - for(size_t i = 0; i < INPUTS_COUNT; i++) { - string_size += inputs[i]->getEncodedSize(); - } - - // Add 1 for new line and 1 for the null terminator. - encoded_output_string = new char[string_size + 1 + 1]; - - Serial.begin(115200); -} - -int encodeAll(char* output, IEncodedSensor* encoders[], size_t count) { - int offset = 0; - // Loop over all of the encoders and encode them to the output string. - for (size_t i = 0; i < count; i++) { - // The offset is the total charecters already added to the string. - offset += encoders[i]->encode(output+offset); - } - - // Add a new line to the end of the encoded string. - output[offset++] = '\n'; - output[offset] = '\0'; - - return offset; } void loopMode() { // update all sensor values - for (int i = 0; i < INPUTS_COUNT; i++) { - auto* sensor = inputs[i]; - sensor->updateValue(); + for (size_t i = 0; i < inputs.size(); i++) { + inputs[i]->updateValue(); } - // encode all sensor values - encodeAll(encoded_output_string, inputs, INPUTS_COUNT); - Serial.print(encoded_output_string); + // send all sensor values + communication->send(inputs); } diff --git a/lib/opengloves_utils/opengloves_protocol.hpp b/lib/opengloves/og_protocol.hpp similarity index 60% rename from lib/opengloves_utils/opengloves_protocol.hpp rename to lib/opengloves/og_protocol.hpp index 417c20bf..3473582d 100644 --- a/lib/opengloves_utils/opengloves_protocol.hpp +++ b/lib/opengloves/og_protocol.hpp @@ -2,7 +2,7 @@ namespace OpenGloves { - struct EncodedInput { + struct IEncodedInput { public: enum Type : char { THUMB = 'A', @@ -22,7 +22,7 @@ namespace OpenGloves CALIBRATE = 'O' }; - EncodedInput(Type type) : type(type) { }; + IEncodedInput(Type type) : type(type) { }; constexpr Type getType() const { return this->type; @@ -34,4 +34,18 @@ namespace OpenGloves private: Type type; }; + + class IEncodedSensor : public IEncodedInput { + public: + IEncodedSensor(Type type) : IEncodedInput(type) { }; + + virtual void setup() = 0; + virtual void updateValue() = 0; + }; + + class ICommunication { + public: + virtual void setup() = 0; + virtual void send(std::vector sensors) = 0; + }; } // namespace OpenGloves diff --git a/lib/opengloves/sensor/og_finger.hpp b/lib/opengloves/sensor/og_finger.hpp index a5c02521..cb1f2be5 100644 --- a/lib/opengloves/sensor/og_finger.hpp +++ b/lib/opengloves/sensor/og_finger.hpp @@ -6,14 +6,14 @@ namespace OpenGloves { class IFinger : public ICalibratedEncodedSensor, public OH::MemoizedSensor { public : - IFinger(OH::CalibratedSensor* sensor, EncodedInput::Type type) + IFinger(OH::CalibratedSensor* sensor, IEncodedInput::Type type) : ICalibratedEncodedSensor(type), OH::MemoizedSensor(sensor) { }; }; class FingerSensor : public IFinger { public: - FingerSensor(OH::CalibratedSensor* sensor, EncodedInput::Type type) : IFinger(sensor, type) { }; + FingerSensor(OH::CalibratedSensor* sensor, IEncodedInput::Type type) : IFinger(sensor, type) { }; void setup() override { this->sensor->setup(); diff --git a/lib/opengloves/sensor/og_sensor.hpp b/lib/opengloves/sensor/og_sensor.hpp index 35e890a6..1a226ff7 100644 --- a/lib/opengloves/sensor/og_sensor.hpp +++ b/lib/opengloves/sensor/og_sensor.hpp @@ -2,17 +2,10 @@ #include #include -#include +#include namespace OpenGloves { - class IEncodedSensor : public EncodedInput { - public: - IEncodedSensor(Type type) : EncodedInput(type) { }; - virtual void setup() = 0; - virtual void updateValue() = 0; - }; - struct ICalibratedEncodedSensor : public IEncodedSensor { public: ICalibratedEncodedSensor(Type type) : IEncodedSensor(type) { }; diff --git a/lib/opengloves_serial/og_serial_commmunications.hpp b/lib/opengloves_serial/og_serial_commmunications.hpp new file mode 100644 index 00000000..292bb80e --- /dev/null +++ b/lib/opengloves_serial/og_serial_commmunications.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +namespace OpenGloves +{ + class SerialCommunication : public ICommunication { + private: + HardwareSerial* serial; + char* buffer = new char[256]; + + public: + SerialCommunication(HardwareSerial* serial) : serial(serial) { }; + + void setup() override { + this->serial->begin(115200); + } + + void send(std::vector sensors) override { + int offset = 0; + // Loop over all of the encoders and encode them to the output string. + for (int i = 0; i < sensors.size(); i++) { + // The offset is the total charecters already added to the string. + offset += sensors[i]->encode(this->buffer + offset); + } + + // Add a new line to the end of the encoded string. + this->buffer[offset++] = '\n'; + this->buffer[offset] = '\0'; + + this->serial->write(this->buffer, offset); + this->serial->flush(); + } + }; +} // namespace OpenGloves From de04be70efa0188fbe4b3bb478e5ae53b21498ff Mon Sep 17 00:00:00 2001 From: Leonid Meleshin Date: Sun, 11 Jun 2023 23:32:27 +0400 Subject: [PATCH 16/23] refactor(OpenGloves): update HAL --- .../mode_configs/opengloves/opengloves.cpp | 41 ++++++++++------- lib/calibration/calibration.hpp | 12 +++-- lib/opengloves/og_protocol.hpp | 11 ++--- lib/opengloves/sensor/og_finger.hpp | 46 +++++-------------- lib/opengloves/sensor/og_sensor.hpp | 33 +++++++++++-- .../og_serial_commmunications.hpp | 22 +++++---- 6 files changed, 92 insertions(+), 73 deletions(-) diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp index a8e758bb..cc0e8df8 100644 --- a/firmware/mode_configs/opengloves/opengloves.cpp +++ b/firmware/mode_configs/opengloves/opengloves.cpp @@ -29,40 +29,40 @@ using namespace OpenGloves; #pragma region FingerSensor #if FINGER_THUMB_ENABLED - auto* fingerThumb = FINGER_CLASS(IEncodedInput::Type::THUMB, PIN_FINGER_THUMB, FINGER_THUMB_INVERT, CALIBRATION_CURL); + auto* fingerThumbCurl = FINGER_CLASS(IEncodedInput::Type::THUMB, PIN_FINGER_THUMB, FINGER_THUMB_INVERT, CALIBRATION_CURL); #endif #if FINGER_INDEX_ENABLED - auto* fingerIndex = FINGER_CLASS(IEncodedInput::Type::INDEX, PIN_FINGER_INDEX, FINGER_INDEX_INVERT, CALIBRATION_CURL); + auto* fingerIndexCurl = FINGER_CLASS(IEncodedInput::Type::INDEX, PIN_FINGER_INDEX, FINGER_INDEX_INVERT, CALIBRATION_CURL); #endif #if FINGER_MIDDLE_ENABLED - auto* fingerMiddle = FINGER_CLASS(IEncodedInput::Type::MIDDLE, PIN_FINGER_MIDDLE, FINGER_MIDDLE_INVERT, CALIBRATION_CURL); + auto* fingerMiddleCurl = FINGER_CLASS(IEncodedInput::Type::MIDDLE, PIN_FINGER_MIDDLE, FINGER_MIDDLE_INVERT, CALIBRATION_CURL); #endif #if FINGER_RING_ENABLED - auto* fingerRing = FINGER_CLASS(IEncodedInput::Type::RING, PIN_FINGER_RING, FINGER_RING_INVERT, CALIBRATION_CURL); + auto* fingerRingCurl = FINGER_CLASS(IEncodedInput::Type::RING, PIN_FINGER_RING, FINGER_RING_INVERT, CALIBRATION_CURL); #endif #if FINGER_PINKY_ENABLED - auto* fingerPinky = FINGER_CLASS(IEncodedInput::Type::PINKY, PIN_FINGER_PINKY, FINGER_PINKY_INVERT, CALIBRATION_CURL); + auto* fingerPinkyCurl = FINGER_CLASS(IEncodedInput::Type::PINKY, PIN_FINGER_PINKY, FINGER_PINKY_INVERT, CALIBRATION_CURL); #endif FingerSensor* fingers[FINGER_COUNT] = { #if FINGER_THUMB_ENABLED - fingerThumb, + fingerThumbCurl, #endif #if FINGER_INDEX_ENABLED - fingerIndex, + fingerIndexCurl, #endif #if FINGER_MIDDLE_ENABLED - fingerMiddle, + fingerMiddleCurl, #endif #if FINGER_RING_ENABLED - fingerRing, + fingerRingCurl, #endif #if FINGER_PINKY_ENABLED - fingerPinky, + fingerPinkyCurl, #endif }; @@ -75,28 +75,37 @@ OH::MemoizedSensor* joystick[JOYSTICK_COUNT] = { #endif }; -std::vector inputs = std::vector(); +std::vector inputs = std::vector(); +std::vector calibrated = std::vector(); auto communication = new SerialCommunication(&Serial); void setupMode() { - communication->setup(); - for (size_t i = 0; i < FINGER_COUNT; i++) { auto* finger = fingers[i]; - inputs.push_back(finger); finger->setup(); + + #if defined(CALIBRATION_ALWAYS_CALIBRATE) && CALIBRATION_ALWAYS_CALIBRATE + finger->enableCalibration(); + #endif + + inputs.push_back(finger); + calibrated.push_back(finger); } for (size_t i = 0; i < JOYSTICK_COUNT; i++) { - joystick[i]->setup(); + auto* axis = joystick[i]; + axis->setup(); } + + communication->setup(); } void loopMode() { // update all sensor values for (size_t i = 0; i < inputs.size(); i++) { - inputs[i]->updateValue(); + auto* input = inputs[i]; + input->updateValue(); } // send all sensor values diff --git a/lib/calibration/calibration.hpp b/lib/calibration/calibration.hpp index 09297028..5d2e0fba 100644 --- a/lib/calibration/calibration.hpp +++ b/lib/calibration/calibration.hpp @@ -8,18 +8,24 @@ #include namespace OH { - class Calibrated { + struct ICalibrated { + virtual void resetCalibration() = 0; + virtual void enableCalibration() = 0; + virtual void disableCalibration() = 0; + }; + + class Calibrated : public ICalibrated { protected: bool calibrate = false; public: virtual void resetCalibration() = 0; - void enableCalibration() { + void enableCalibration() override { calibrate = true; } - void disableCalibration() { + void disableCalibration() override { calibrate = false; } }; diff --git a/lib/opengloves/og_protocol.hpp b/lib/opengloves/og_protocol.hpp index 3473582d..1189f7bc 100644 --- a/lib/opengloves/og_protocol.hpp +++ b/lib/opengloves/og_protocol.hpp @@ -28,24 +28,23 @@ namespace OpenGloves return this->type; } - virtual size_t getEncodedSize() const = 0; - virtual int encode(char* buffer) const = 0; - private: Type type; }; - class IEncodedSensor : public IEncodedInput { + class IStringEncodedSensor : public IEncodedInput { public: - IEncodedSensor(Type type) : IEncodedInput(type) { }; + IStringEncodedSensor(Type type) : IEncodedInput(type) { }; virtual void setup() = 0; virtual void updateValue() = 0; + + virtual size_t encodeString(char* buffer) = 0; }; class ICommunication { public: virtual void setup() = 0; - virtual void send(std::vector sensors) = 0; + virtual void send(std::vector sensors) = 0; }; } // namespace OpenGloves diff --git a/lib/opengloves/sensor/og_finger.hpp b/lib/opengloves/sensor/og_finger.hpp index cb1f2be5..71370895 100644 --- a/lib/opengloves/sensor/og_finger.hpp +++ b/lib/opengloves/sensor/og_finger.hpp @@ -4,45 +4,21 @@ namespace OpenGloves { - class IFinger : public ICalibratedEncodedSensor, public OH::MemoizedSensor { + class FingerSensor : public StringEncodedMemoizedSensor, public virtual OH::ICalibrated { public : - IFinger(OH::CalibratedSensor* sensor, IEncodedInput::Type type) - : ICalibratedEncodedSensor(type), OH::MemoizedSensor(sensor) { }; - }; - - class FingerSensor : public IFinger - { - public: - FingerSensor(OH::CalibratedSensor* sensor, IEncodedInput::Type type) : IFinger(sensor, type) { }; - - void setup() override { - this->sensor->setup(); - } - - void updateValue() override { - this->value = this->sensor->getValue(); - } - - void resetCalibration() override { - static_cast*>(this->sensor)->resetCalibration(); - } - - void enableCalibration() override { - static_cast*>(this->sensor)->enableCalibration(); - } + FingerSensor(OH::CalibratedSensor* sensor, IEncodedInput::Type type) : StringEncodedMemoizedSensor(sensor, type) { }; - void disableCalibration() override { - static_cast*>(this->sensor)->disableCalibration(); - } + void resetCalibration() override { + static_cast*>(this->sensor)->resetCalibration(); + } - size_t getEncodedSize() const override { - // Encode string size = AXXXX + '\0' - return 6; - } + void enableCalibration() override { + static_cast*>(this->sensor)->enableCalibration(); + } - int encode(char* buffer) const override { - return snprintf(buffer, this->getEncodedSize(), "%c%04d", this->getType(), this->value); - } + void disableCalibration() override { + static_cast*>(this->sensor)->disableCalibration(); + } }; // TODO: add splay finger sensor diff --git a/lib/opengloves/sensor/og_sensor.hpp b/lib/opengloves/sensor/og_sensor.hpp index 1a226ff7..65a7c539 100644 --- a/lib/opengloves/sensor/og_sensor.hpp +++ b/lib/opengloves/sensor/og_sensor.hpp @@ -6,11 +6,34 @@ namespace OpenGloves { - struct ICalibratedEncodedSensor : public IEncodedSensor { + template + class StringEncodedMemoizedSensor : public IStringEncodedSensor, public OH::MemoizedSensor<_Tp> { public: - ICalibratedEncodedSensor(Type type) : IEncodedSensor(type) { }; - virtual void resetCalibration() = 0; - virtual void enableCalibration() = 0; - virtual void disableCalibration() = 0; + StringEncodedMemoizedSensor(OH::ISensor* sensor, IEncodedInput::Type type) + : IStringEncodedSensor(type), OH::MemoizedSensor(sensor) { }; + + void setup() override { + this->sensor->setup(); + } + + void updateValue() override { + this->value = this->sensor->getValue(); + } + + size_t encodeString(char* buffer) override; }; + + template <> + size_t StringEncodedMemoizedSensor::encodeString(char* buffer) { + return snprintf(buffer, 6, "%c%d", this->getType(), this->getValue()); + } + + template <> + size_t StringEncodedMemoizedSensor::encodeString(char* buffer) { + if (value) { + buffer[0] = this->getType(); + } + return value ? 1 : 0; + } + } // namespace OpenGloves diff --git a/lib/opengloves_serial/og_serial_commmunications.hpp b/lib/opengloves_serial/og_serial_commmunications.hpp index 292bb80e..ad50ae9e 100644 --- a/lib/opengloves_serial/og_serial_commmunications.hpp +++ b/lib/opengloves_serial/og_serial_commmunications.hpp @@ -17,20 +17,26 @@ namespace OpenGloves this->serial->begin(115200); } - void send(std::vector sensors) override { - int offset = 0; + void send(std::vector sensors) override { + // Encode all of the sensors into a single string. + size_t length = encodeAll(buffer, sensors); + // Send the encoded string over serial. + this->serial->write(buffer, length); + } + + static size_t encodeAll(char* buffer, std::vector sensors) { + size_t offset = 0; // Loop over all of the encoders and encode them to the output string. - for (int i = 0; i < sensors.size(); i++) { + for (size_t i = 0; i < sensors.size(); i++) { // The offset is the total charecters already added to the string. - offset += sensors[i]->encode(this->buffer + offset); + offset += sensors[i]->encodeString(buffer + offset); } // Add a new line to the end of the encoded string. - this->buffer[offset++] = '\n'; - this->buffer[offset] = '\0'; + buffer[offset++] = '\n'; + buffer[offset] = '\0'; - this->serial->write(this->buffer, offset); - this->serial->flush(); + return offset; } }; } // namespace OpenGloves From 886354f75fce2c8a4f929a31753ada2170aa589d Mon Sep 17 00:00:00 2001 From: Leonid Meleshin Date: Mon, 12 Jun 2023 14:54:30 +0400 Subject: [PATCH 17/23] feat(OpenGloves): add Joystick sensors --- .../mode_configs/opengloves/opengloves.cpp | 6 +++-- lib/opengloves/og_protocol.hpp | 5 ++-- lib/opengloves/sensor/og_sensor.hpp | 23 +++++++++++++++---- .../og_serial_commmunications.hpp | 4 ++-- 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp index cc0e8df8..d97b4d83 100644 --- a/firmware/mode_configs/opengloves/opengloves.cpp +++ b/firmware/mode_configs/opengloves/opengloves.cpp @@ -22,7 +22,7 @@ #define JOYSTICK_ENABLED (PIN_JOYSTICK_X != -1 && PIN_JOYSTICK_Y != -1) #define JOYSTICK_COUNT (JOYSTICK_ENABLED ? 2 : 0) -#define JOYSTICK_CLASS(type, pin, invert, deadzone) new OH::MemoizedSensor(new OH::JoystickAxisSensor(new OH::AnalogSensor(pin), deadzone)) +#define JOYSTICK_CLASS(type, pin, invert, deadzone) new StringEncodedMemoizedSensor(new OH::JoystickAxisSensor(new OH::AnalogSensor(pin), deadzone), type) using namespace OpenGloves; @@ -68,7 +68,7 @@ FingerSensor* fingers[FINGER_COUNT] = { #pragma endregion -OH::MemoizedSensor* joystick[JOYSTICK_COUNT] = { +StringEncodedMemoizedSensor* joystick[JOYSTICK_COUNT] = { #if JOYSTICK_ENABLED JOYSTICK_CLASS(IEncodedInput::Type::JOY_X, PIN_JOYSTICK_X, JOYSTICK_X_INVERT, JOYSTICK_DEADZONE), JOYSTICK_CLASS(IEncodedInput::Type::JOY_Y, PIN_JOYSTICK_Y, JOYSTICK_Y_INVERT, JOYSTICK_DEADZONE), @@ -96,6 +96,8 @@ void setupMode() { for (size_t i = 0; i < JOYSTICK_COUNT; i++) { auto* axis = joystick[i]; axis->setup(); + + inputs.push_back(axis); } communication->setup(); diff --git a/lib/opengloves/og_protocol.hpp b/lib/opengloves/og_protocol.hpp index 1189f7bc..89270585 100644 --- a/lib/opengloves/og_protocol.hpp +++ b/lib/opengloves/og_protocol.hpp @@ -39,12 +39,13 @@ namespace OpenGloves virtual void setup() = 0; virtual void updateValue() = 0; - virtual size_t encodeString(char* buffer) = 0; + virtual size_t getEncodedLength() const = 0; + virtual size_t encodeString(char* buffer) const = 0; }; class ICommunication { public: virtual void setup() = 0; - virtual void send(std::vector sensors) = 0; + virtual void send(std::vector &sensors) = 0; }; } // namespace OpenGloves diff --git a/lib/opengloves/sensor/og_sensor.hpp b/lib/opengloves/sensor/og_sensor.hpp index 65a7c539..292a35c9 100644 --- a/lib/opengloves/sensor/og_sensor.hpp +++ b/lib/opengloves/sensor/og_sensor.hpp @@ -20,20 +20,33 @@ namespace OpenGloves this->value = this->sensor->getValue(); } - size_t encodeString(char* buffer) override; + size_t getEncodedLength() const override; + + size_t encodeString(char* buffer) const override; }; template <> - size_t StringEncodedMemoizedSensor::encodeString(char* buffer) { - return snprintf(buffer, 6, "%c%d", this->getType(), this->getValue()); + size_t StringEncodedMemoizedSensor::getEncodedLength() const { + return 6; + } + + template <> + size_t StringEncodedMemoizedSensor::encodeString(char* buffer) const { + // Format as "Axxxxx", where A is the type and xxxxx is the value without leading zeros. + return snprintf(buffer, this->getEncodedLength(), "%c%d", this->getType(), this->value); + } + + template <> + size_t StringEncodedMemoizedSensor::getEncodedLength() const { + return 1; } template <> - size_t StringEncodedMemoizedSensor::encodeString(char* buffer) { + size_t StringEncodedMemoizedSensor::encodeString(char* buffer) const { if (value) { buffer[0] = this->getType(); } - return value ? 1 : 0; + return value ? this->getEncodedLength() : 0; } } // namespace OpenGloves diff --git a/lib/opengloves_serial/og_serial_commmunications.hpp b/lib/opengloves_serial/og_serial_commmunications.hpp index ad50ae9e..dc6a0c4d 100644 --- a/lib/opengloves_serial/og_serial_commmunications.hpp +++ b/lib/opengloves_serial/og_serial_commmunications.hpp @@ -17,14 +17,14 @@ namespace OpenGloves this->serial->begin(115200); } - void send(std::vector sensors) override { + void send(std::vector &sensors) override { // Encode all of the sensors into a single string. size_t length = encodeAll(buffer, sensors); // Send the encoded string over serial. this->serial->write(buffer, length); } - static size_t encodeAll(char* buffer, std::vector sensors) { + static size_t encodeAll(char* buffer, std::vector &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++) { From 6b6f44e513d6a2eaeec8f18d86f4fe9bdd9d8aec Mon Sep 17 00:00:00 2001 From: Leonid Meleshin Date: Mon, 12 Jun 2023 16:40:16 +0400 Subject: [PATCH 18/23] feat: add button DigitalSensor --- lib/arduino/sensor/digital.hpp | 44 ++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 lib/arduino/sensor/digital.hpp diff --git a/lib/arduino/sensor/digital.hpp b/lib/arduino/sensor/digital.hpp new file mode 100644 index 00000000..7cf2290a --- /dev/null +++ b/lib/arduino/sensor/digital.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +namespace OH +{ + typedef ISensor IDigitalSensor; + + template + class DigitalSensor : public IDigitalSensor + { + private: + uint8_t pin; + uint8_t _pinMode; + + public: + DigitalSensor(uint8_t pin, uint8_t pinMode) : pin(pin), _pinMode(pinMode) { + // static_assert( + // _pinMode == INPUT || _pinMode == INPUT_PULLUP || _pinMode == INPUT_PULLDOWN, + // "pinMode must be INPUT, INPUT_PULLUP or INPUT_PULLDOWN" + // ); + } + + void setup(void) + { + pinMode(this->pin, this->_pinMode); + }; + + bool getValue(void) override; + }; + + template <> + bool DigitalSensor::getValue(void) + { + return digitalRead(this->pin) == HIGH; + } + + template <> + bool DigitalSensor::getValue(void) + { + return digitalRead(this->pin) == LOW; + } +} // namespace OH From 95262acdb831fb3be73309c5e1797a97d35e19f2 Mon Sep 17 00:00:00 2001 From: Leonid Meleshin Date: Mon, 12 Jun 2023 18:28:07 +0400 Subject: [PATCH 19/23] feat(OpenGloves): add buttons --- .../mode_configs/opengloves/opengloves.cpp | 40 ++++++++++++++++++- ini/opengloves.ini | 27 +++++++++++++ lib/arduino/sensor/digital.hpp | 9 +---- lib/opengloves/sensor/og_sensor.hpp | 4 +- 4 files changed, 69 insertions(+), 11 deletions(-) diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp index d97b4d83..1e732414 100644 --- a/firmware/mode_configs/opengloves/opengloves.cpp +++ b/firmware/mode_configs/opengloves/opengloves.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -16,14 +17,20 @@ #define FINGER_RING_ENABLED (PIN_FINGER_RING != -1) #define FINGER_PINKY_ENABLED (PIN_FINGER_PINKY != -1) #define FINGER_COUNT (FINGER_THUMB_ENABLED + FINGER_INDEX_ENABLED + FINGER_MIDDLE_ENABLED + FINGER_RING_ENABLED + FINGER_PINKY_ENABLED) - #define FINGER_CLASS(type, pin, invert, calib) new FingerSensor(new OH::CalibratedSensor(new OH::AnalogSensor(pin), new calib()), type) #define JOYSTICK_ENABLED (PIN_JOYSTICK_X != -1 && PIN_JOYSTICK_Y != -1) #define JOYSTICK_COUNT (JOYSTICK_ENABLED ? 2 : 0) - #define JOYSTICK_CLASS(type, pin, invert, deadzone) new StringEncodedMemoizedSensor(new OH::JoystickAxisSensor(new OH::AnalogSensor(pin), deadzone), type) +#define BUTTON_A_ENABLED (PIN_BUTTON_A != -1) +#define BUTTON_B_ENABLED (PIN_BUTTON_B != -1) +#define BUTTON_MENU_ENABLED (PIN_BUTTON_MENU != -1) +#define BUTTON_CALIBRATE_ENABLED (PIN_BUTTON_CALIBRATE != -1) +#define BUTTON_JOYSTICK_ENABLED (JOYSTICK_ENABLED && (PIN_BUTTON_JOYSTICK != -1)) +#define BUTTON_COUNT (BUTTON_A_ENABLED + BUTTON_B_ENABLED + BUTTON_MENU_ENABLED + BUTTON_CALIBRATE_ENABLED + BUTTON_JOYSTICK_ENABLED) +#define BUTTON_CLASS(type, pin, invert) new StringEncodedMemoizedSensor(new OH::DigitalSensor(pin), type) + using namespace OpenGloves; #pragma region FingerSensor @@ -75,6 +82,28 @@ StringEncodedMemoizedSensor* joystick[JOYSTICK_COUNT] = { #endif }; +#if BUTTON_CALIBRATE_ENABLED +StringEncodedMemoizedSensor* calibrateButton = BUTTON_CLASS(IEncodedInput::Type::CALIBRATE, PIN_BUTTON_CALIBRATE, BUTTON_CALIBRATE_INVERT); +#endif + +std::vector*> buttons = std::vector*>{ +#if BUTTON_A_ENABLED + BUTTON_CLASS(IEncodedInput::Type::A_BTN, PIN_BUTTON_A, BUTTON_A_INVERT), +#endif +#if BUTTON_B_ENABLED + BUTTON_CLASS(IEncodedInput::Type::B_BTN, PIN_BUTTON_B, BUTTON_B_INVERT), +#endif +#if BUTTON_MENU_ENABLED + BUTTON_CLASS(IEncodedInput::Type::MENU, PIN_BUTTON_MENU, BUTTON_MENU_INVERT), +#endif +#if BUTTON_JOYSTICK_ENABLED + BUTTON_CLASS(IEncodedInput::Type::JOY_BTN, PIN_BUTTON_JOYSTICK, BUTTON_JOYSTICK_INVERT), +#endif +#if BUTTON_CALIBRATE_ENABLED + calibrateButton, +#endif +}; + std::vector inputs = std::vector(); std::vector calibrated = std::vector(); @@ -100,6 +129,13 @@ void setupMode() { inputs.push_back(axis); } + for (size_t i = 0; i < BUTTON_COUNT; i++) { + auto* button = buttons[i]; + button->setup(); + + inputs.push_back(button); + } + communication->setup(); } diff --git a/ini/opengloves.ini b/ini/opengloves.ini index 9c4dcca1..9ef0551e 100644 --- a/ini/opengloves.ini +++ b/ini/opengloves.ini @@ -19,6 +19,22 @@ build_flags = ${common.build_flags} -D JOYSTICK_Y_INVERT=false -D JOYSTICK_DEADZONE=0.1 + -D BUTTON_A_INVERT=false + -D BUTTON_B_INVERT=false + -D BUTTON_MENU_INVERT=false + -D BUTTON_JOYSTICK_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=false + -D GESTURE_GRAB=false + -D GESTURE_PINCH=false + + ; Calibration + -D CALIBRATION_ALWAYS_CALIBRATE=false + ; Pins configuration ; If pin set to -1, then it will be ignored -D PIN_FINGER_THUMB=32 @@ -26,8 +42,19 @@ build_flags = ${common.build_flags} -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_A=27 + -D PIN_BUTTON_B=14 + -D PIN_BUTTON_JOYSTICK=26 + -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 = ${common.build_unflags} build_src_filter = ${common.build_src_filter} lib_deps = ${common.lib_deps} diff --git a/lib/arduino/sensor/digital.hpp b/lib/arduino/sensor/digital.hpp index 7cf2290a..abd812d2 100644 --- a/lib/arduino/sensor/digital.hpp +++ b/lib/arduino/sensor/digital.hpp @@ -12,19 +12,14 @@ namespace OH { private: uint8_t pin; - uint8_t _pinMode; public: - DigitalSensor(uint8_t pin, uint8_t pinMode) : pin(pin), _pinMode(pinMode) { - // static_assert( - // _pinMode == INPUT || _pinMode == INPUT_PULLUP || _pinMode == INPUT_PULLDOWN, - // "pinMode must be INPUT, INPUT_PULLUP or INPUT_PULLDOWN" - // ); + DigitalSensor(uint8_t pin) : pin(pin) { } void setup(void) { - pinMode(this->pin, this->_pinMode); + pinMode(this->pin, INPUT); }; bool getValue(void) override; diff --git a/lib/opengloves/sensor/og_sensor.hpp b/lib/opengloves/sensor/og_sensor.hpp index 292a35c9..1ad6dfcf 100644 --- a/lib/opengloves/sensor/og_sensor.hpp +++ b/lib/opengloves/sensor/og_sensor.hpp @@ -9,8 +9,8 @@ namespace OpenGloves template class StringEncodedMemoizedSensor : public IStringEncodedSensor, public OH::MemoizedSensor<_Tp> { public: - StringEncodedMemoizedSensor(OH::ISensor* sensor, IEncodedInput::Type type) - : IStringEncodedSensor(type), OH::MemoizedSensor(sensor) { }; + StringEncodedMemoizedSensor(OH::ISensor<_Tp>* sensor, IEncodedInput::Type type) + : IStringEncodedSensor(type), OH::MemoizedSensor<_Tp>(sensor) { }; void setup() override { this->sensor->setup(); From 33023bc963a8ec1587dea8b234c6b03424872019 Mon Sep 17 00:00:00 2001 From: Leonid Meleshin Date: Mon, 12 Jun 2023 19:08:11 +0400 Subject: [PATCH 20/23] feat(OpenGloves): respect update rate --- .../mode_configs/opengloves/opengloves.cpp | 34 ++++++++++++++++--- ini/opengloves.ini | 8 +++-- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp index 1e732414..7827947d 100644 --- a/firmware/mode_configs/opengloves/opengloves.cpp +++ b/firmware/mode_configs/opengloves/opengloves.cpp @@ -26,11 +26,19 @@ #define BUTTON_A_ENABLED (PIN_BUTTON_A != -1) #define BUTTON_B_ENABLED (PIN_BUTTON_B != -1) #define BUTTON_MENU_ENABLED (PIN_BUTTON_MENU != -1) -#define BUTTON_CALIBRATE_ENABLED (PIN_BUTTON_CALIBRATE != -1) #define BUTTON_JOYSTICK_ENABLED (JOYSTICK_ENABLED && (PIN_BUTTON_JOYSTICK != -1)) -#define BUTTON_COUNT (BUTTON_A_ENABLED + BUTTON_B_ENABLED + BUTTON_MENU_ENABLED + BUTTON_CALIBRATE_ENABLED + BUTTON_JOYSTICK_ENABLED) +#define BUTTON_CALIBRATE_ENABLED (PIN_BUTTON_CALIBRATE != -1) +#define BUTTON_TRIGGER_ENABLED (!GESTURE_TRIGGER_ENABLED && (PIN_BUTTON_TRIGGER != -1)) +#define BUTTON_GRAB_ENABLED (!GESTURE_GRAB_ENABLED && (PIN_BUTTON_GRAB != -1)) +#define BUTTON_PINCH_ENABLED (!GESTURE_PINCH_ENABLED && (PIN_BUTTON_PINCH != -1)) +#define BUTTON_COUNT (BUTTON_A_ENABLED + BUTTON_B_ENABLED + BUTTON_MENU_ENABLED + BUTTON_JOYSTICK_ENABLED + BUTTON_CALIBRATE_ENABLED + BUTTON_TRIGGER_ENABLED + BUTTON_GRAB_ENABLED + BUTTON_PINCH_ENABLED) #define BUTTON_CLASS(type, pin, invert) new StringEncodedMemoizedSensor(new OH::DigitalSensor(pin), type) +#ifndef UPDATE_RATE +#define UPDATE_RATE 90 +#endif +#define UPDATE_INTERVAL (1000 / UPDATE_RATE) + using namespace OpenGloves; #pragma region FingerSensor @@ -93,15 +101,24 @@ std::vector*> buttons = std::vector inputs = std::vector(); @@ -140,6 +157,8 @@ void setupMode() { } void loopMode() { + auto now = millis(); + // update all sensor values for (size_t i = 0; i < inputs.size(); i++) { auto* input = inputs[i]; @@ -148,4 +167,9 @@ void loopMode() { // send all sensor values communication->send(inputs); + + auto elapsed = millis() - now; + if (elapsed < UPDATE_INTERVAL) { + delay(UPDATE_INTERVAL - elapsed); + } } diff --git a/ini/opengloves.ini b/ini/opengloves.ini index 9ef0551e..9f22e627 100644 --- a/ini/opengloves.ini +++ b/ini/opengloves.ini @@ -28,9 +28,9 @@ build_flags = ${common.build_flags} -D BUTTON_GRAB_INVERT=false -D BUTTON_PINCH_INVERT=false - -D GESTURE_TRIGGER=false - -D GESTURE_GRAB=false - -D GESTURE_PINCH=false + -D GESTURE_TRIGGER_ENABLED=true + -D GESTURE_GRAB_ENABLED=true + -D GESTURE_PINCH_ENABLED=true ; Calibration -D CALIBRATION_ALWAYS_CALIBRATE=false @@ -55,6 +55,8 @@ build_flags = ${common.build_flags} -D PIN_BUTTON_GRAB=13 ; unused if GESTURE_GRAB is true -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is true + -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} From 9b62c3892b1c9dc7a37ea2f1ca50e8c884e3ee28 Mon Sep 17 00:00:00 2001 From: Leonid Meleshin Date: Mon, 12 Jun 2023 21:40:14 +0400 Subject: [PATCH 21/23] test(OpenGloves): cover sensors --- lib/calibration/calibration.hpp | 8 +- lib/opengloves/sensor/og_finger.hpp | 13 ++- lib/opengloves/sensor/og_joystick.hpp | 8 -- lib/sensor/sensor.hpp | 6 +- test/test_opengloves/main.cpp | 112 ++++++++++++++++++++++++++ test/test_opengloves_finger/main.cpp | 110 +++++++++++++++++++++++++ test/test_sensor/main.cpp | 45 ++++++++++- 7 files changed, 283 insertions(+), 19 deletions(-) delete mode 100644 lib/opengloves/sensor/og_joystick.hpp create mode 100644 test/test_opengloves/main.cpp create mode 100644 test/test_opengloves_finger/main.cpp diff --git a/lib/calibration/calibration.hpp b/lib/calibration/calibration.hpp index 5d2e0fba..ae94fcf3 100644 --- a/lib/calibration/calibration.hpp +++ b/lib/calibration/calibration.hpp @@ -31,14 +31,14 @@ namespace OH { }; template - struct Calibrator { + struct ICalibrator { virtual void reset() = 0; virtual void update(_Tp input) = 0; virtual _Tp calibrate(_Tp input) const = 0; }; template - class MinMaxCalibrator : public Calibrator<_Tp> { + class MinMaxCalibrator : public ICalibrator<_Tp> { public: MinMaxCalibrator() : value_min(output_max), value_max(output_min) {} @@ -81,7 +81,7 @@ namespace OH { }; template - class CenterPointDeviationCalibrator : public Calibrator<_Tp> { + class CenterPointDeviationCalibrator : public ICalibrator<_Tp> { public: CenterPointDeviationCalibrator() : range_min(sensor_max), range_max(0) { #warning "CenterPointDeviationCalibrator is untested and may not work as expected." @@ -118,7 +118,7 @@ namespace OH { }; template - class FixedCenterPointDeviationCalibrator : public Calibrator<_Tp> { + class FixedCenterPointDeviationCalibrator : public ICalibrator<_Tp> { public: void reset() {} void update(_Tp input) {} diff --git a/lib/opengloves/sensor/og_finger.hpp b/lib/opengloves/sensor/og_finger.hpp index 71370895..1a64ccfd 100644 --- a/lib/opengloves/sensor/og_finger.hpp +++ b/lib/opengloves/sensor/og_finger.hpp @@ -4,9 +4,18 @@ namespace OpenGloves { - class FingerSensor : public StringEncodedMemoizedSensor, public virtual OH::ICalibrated { + class IFinger : public StringEncodedMemoizedSensor, public virtual OH::ICalibrated { public : - FingerSensor(OH::CalibratedSensor* sensor, IEncodedInput::Type type) : StringEncodedMemoizedSensor(sensor, type) { }; + IFinger(OH::CalibratedSensor* sensor, IEncodedInput::Type type) : StringEncodedMemoizedSensor(sensor, type) { }; + + uint16_t getCurl() { + return this->getValue(); + } + }; + + class FingerSensor : public IFinger { + public : + FingerSensor(OH::CalibratedSensor* sensor, IEncodedInput::Type type) : IFinger(sensor, type) { }; void resetCalibration() override { static_cast*>(this->sensor)->resetCalibration(); diff --git a/lib/opengloves/sensor/og_joystick.hpp b/lib/opengloves/sensor/og_joystick.hpp deleted file mode 100644 index 3d6f63c2..00000000 --- a/lib/opengloves/sensor/og_joystick.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include "og_sensor.hpp" - -namespace OpenGloves -{ - // typedef SingleAnalogValueEncodedSensor EncidedJoystickSensor; -} // namespace OpenGloves diff --git a/lib/sensor/sensor.hpp b/lib/sensor/sensor.hpp index 7c27608b..93289b5f 100644 --- a/lib/sensor/sensor.hpp +++ b/lib/sensor/sensor.hpp @@ -83,7 +83,7 @@ namespace OH class CalibratedSensor : public ISensor<_Tp>, public Calibrated { protected: ISensor<_Tp>* sensor; - Calibrator<_Tp>* calibrator; + ICalibrator<_Tp>* calibrator; _Tp getCalibratedValue() { auto value = this->sensor->getValue(); @@ -98,9 +98,9 @@ namespace OH public: /** * @param sensor Sensor to be decorated - * @param calibrator Calibrator algorithm to be used + * @param calibrator ICalibrator algorithm to be used */ - CalibratedSensor(ISensor<_Tp>* sensor, Calibrator<_Tp>* calibrator) : sensor(sensor), calibrator(calibrator) {}; + CalibratedSensor(ISensor<_Tp>* sensor, ICalibrator<_Tp>* calibrator) : sensor(sensor), calibrator(calibrator) {}; void setup() override { this->sensor->setup(); diff --git a/test/test_opengloves/main.cpp b/test/test_opengloves/main.cpp new file mode 100644 index 00000000..f8826c4e --- /dev/null +++ b/test/test_opengloves/main.cpp @@ -0,0 +1,112 @@ +#include +#include + +using namespace OpenGloves; + +class TestAnalogSensor : public OH::ISensor { + private: + uint16_t count = 0; + + public: + int setupCounter = 0; + + void setup() override { + this->setupCounter++; + }; + + uint16_t getValue() override { + return ++this->count; + }; +}; + +class TestBinarySensor : public OH::ISensor { + public: + bool value = false; + int setupCounter = 0; + + void setup() override { + this->setupCounter++; + }; + + bool getValue() override { + return this->value; + }; +}; + +void test_string_encoded_sensor_uint16(void) { + auto inner = new TestAnalogSensor(); + auto sensor = new StringEncodedMemoizedSensor(inner, IEncodedInput::Type::INDEX); + + TEST_ASSERT_EQUAL_INT(0, inner->setupCounter); + sensor->setup(); + 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(inner, IEncodedInput::Type::A_BTN); + + TEST_ASSERT_EQUAL_INT(0, inner->setupCounter); + sensor->setup(); + 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); +} + +int process(void) { + UNITY_BEGIN(); + + RUN_TEST(test_string_encoded_sensor_uint16); + RUN_TEST(test_string_encoded_sensor_bool); + + return UNITY_END(); +} + +#ifdef ARDUINO + +#include + +void setup(void) { + process(); +} + +void loop(void) {} + +#else + +int main(int argc, char** argv) { + return process(); +} + +#endif diff --git a/test/test_opengloves_finger/main.cpp b/test/test_opengloves_finger/main.cpp new file mode 100644 index 00000000..bfb5a7cc --- /dev/null +++ b/test/test_opengloves_finger/main.cpp @@ -0,0 +1,110 @@ +#include +#include + +using namespace OpenGloves; + +class TestAnalogSensor : public OH::ISensor { + private: + uint16_t count = 0; + + public: + int setupCounter = 0; + + void setup() override { + this->setupCounter++; + }; + + uint16_t getValue() override { + return ++this->count; + }; +}; + +class DummyCalibrator : public OH::ICalibrator { + public: + uint8_t resetCounter = 0; + uint16_t calibrated = 0; + + void reset() override { + this->resetCounter++; + this->calibrated = 0; + }; + void update(uint16_t input) override { + this->calibrated = input; + }; + uint16_t calibrate(uint16_t input) const override { + return calibrated; + }; +}; + +void test_finger_sensor_curl(void) { + auto* inner = new TestAnalogSensor(); + auto* calibrator = new DummyCalibrator(); + auto* calibrated = new OH::CalibratedSensor(inner, calibrator); + auto* sensor = new FingerSensor(calibrated, IEncodedInput::Type::INDEX); + + TEST_ASSERT_EQUAL_INT(0, inner->setupCounter); + sensor->setup(); + TEST_ASSERT_EQUAL_INT(1, inner->setupCounter); + + TEST_ASSERT_EQUAL_INT(0, sensor->getValue()); + TEST_ASSERT_EQUAL_INT(0, sensor->getCurl()); + + sensor->updateValue(); + + TEST_ASSERT_EQUAL_INT(0, sensor->getValue()); + TEST_ASSERT_EQUAL_INT(0, sensor->getCurl()); + + calibrator->calibrated = 100; + + TEST_ASSERT_EQUAL_INT(0, sensor->getValue()); + TEST_ASSERT_EQUAL_INT(0, sensor->getCurl()); + + sensor->updateValue(); + + TEST_ASSERT_EQUAL_INT(100, sensor->getValue()); + 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(); + TEST_ASSERT_EQUAL_INT(3, sensor->getValue()); + TEST_ASSERT_EQUAL_INT(3, sensor->getCurl()); + + sensor->updateValue(); + TEST_ASSERT_EQUAL_INT(4, sensor->getValue()); + TEST_ASSERT_EQUAL_INT(4, sensor->getCurl()); + + sensor->disableCalibration(); + sensor->updateValue(); + TEST_ASSERT_EQUAL_INT(4, sensor->getValue()); + TEST_ASSERT_EQUAL_INT(4, sensor->getCurl()); +} + +int process(void) { + UNITY_BEGIN(); + + RUN_TEST(test_finger_sensor_curl); + + return UNITY_END(); +} + +#ifdef ARDUINO + +#include + +void setup(void) { + process(); +} + +void loop(void) {} + +#else + +int main(int argc, char** argv) { + return process(); +} + +#endif diff --git a/test/test_sensor/main.cpp b/test/test_sensor/main.cpp index dd033ad8..0dd68966 100644 --- a/test/test_sensor/main.cpp +++ b/test/test_sensor/main.cpp @@ -3,7 +3,7 @@ using namespace OH; -class TestSensor : public ISensor { +class TestAnalogSensor : public ISensor { private: int count = 0; @@ -20,7 +20,7 @@ class TestSensor : public ISensor { }; void test_memoized_sensor(void) { - auto inner = new TestSensor(); + auto inner = new TestAnalogSensor(); auto sensor = new MemoizedSensor(inner); TEST_ASSERT_EQUAL_INT(0, inner->setupCounter); @@ -36,10 +36,51 @@ void test_memoized_sensor(void) { TEST_ASSERT_EQUAL_INT(1, sensor->getValue()); } +class DummyCalibrator : public ICalibrator { + public: + uint8_t resetCounter = 0; + int calibrated = 0; + + void reset() override { + this->resetCounter++; + this->calibrated = 0; + }; + void update(int input) override { + this->calibrated = input; + }; + int calibrate(int input) const override { + return calibrated; + }; +}; + +void test_calibrated_sensor(void) { + auto inner = new TestAnalogSensor(); + auto calibrator = new DummyCalibrator(); + auto sensor = new CalibratedSensor(inner, calibrator); + + TEST_ASSERT_EQUAL_INT(0, inner->setupCounter); + sensor->setup(); + TEST_ASSERT_EQUAL_INT(1, inner->setupCounter); + + calibrator->update(-1); + TEST_ASSERT_EQUAL_INT(-1, sensor->getValue()); + + sensor->enableCalibration(); + TEST_ASSERT_EQUAL_INT(2, sensor->getValue()); + + sensor->disableCalibration(); + TEST_ASSERT_EQUAL_INT(2, sensor->getValue()); + + sensor->resetCalibration(); + TEST_ASSERT_EQUAL_INT(0, sensor->getValue()); + TEST_ASSERT_EQUAL_INT(1, calibrator->resetCounter); +} + int process(void) { UNITY_BEGIN(); RUN_TEST(test_memoized_sensor); + RUN_TEST(test_calibrated_sensor); return UNITY_END(); } From 936a2638412bb17991bbf8cd581986d712f4bae0 Mon Sep 17 00:00:00 2001 From: Leonid Meleshin Date: Mon, 12 Jun 2023 23:01:30 +0400 Subject: [PATCH 22/23] feat(OpenGloves): add gestures --- .../mode_configs/opengloves/opengloves.cpp | 34 ++++++- lib/opengloves/sensor/og_finger.hpp | 9 +- lib/opengloves/sensor/og_gesture.hpp | 86 ++++++++++++++++ test/test_opengloves_gesture/main.cpp | 98 +++++++++++++++++++ 4 files changed, 222 insertions(+), 5 deletions(-) create mode 100644 lib/opengloves/sensor/og_gesture.hpp create mode 100644 test/test_opengloves_gesture/main.cpp diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp index 7827947d..235129f5 100644 --- a/firmware/mode_configs/opengloves/opengloves.cpp +++ b/firmware/mode_configs/opengloves/opengloves.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #ifndef CALIBRATION_CURL @@ -34,6 +35,20 @@ #define BUTTON_COUNT (BUTTON_A_ENABLED + BUTTON_B_ENABLED + BUTTON_MENU_ENABLED + BUTTON_JOYSTICK_ENABLED + BUTTON_CALIBRATE_ENABLED + BUTTON_TRIGGER_ENABLED + BUTTON_GRAB_ENABLED + BUTTON_PINCH_ENABLED) #define BUTTON_CLASS(type, pin, invert) new StringEncodedMemoizedSensor(new OH::DigitalSensor(pin), type) +#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) new StringEncodedMemoizedSensor(sensor, type) + #ifndef UPDATE_RATE #define UPDATE_RATE 90 #endif @@ -98,25 +113,38 @@ std::vector*> buttons = std::vector, public virtual OH::ICalibrated { + class ICurl { + public : + virtual uint16_t getCurl() = 0; + }; + + class IFinger : public StringEncodedMemoizedSensor, public virtual OH::ICalibrated, public virtual ICurl { public : IFinger(OH::CalibratedSensor* sensor, IEncodedInput::Type type) : StringEncodedMemoizedSensor(sensor, type) { }; - uint16_t getCurl() { + uint16_t getCurl() override { return this->getValue(); } }; diff --git a/lib/opengloves/sensor/og_gesture.hpp b/lib/opengloves/sensor/og_gesture.hpp new file mode 100644 index 00000000..464c31f8 --- /dev/null +++ b/lib/opengloves/sensor/og_gesture.hpp @@ -0,0 +1,86 @@ +#pragma once + +#include + +namespace OpenGloves +{ + class Gesture : public OH::ISensor { + + }; + + 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 setup() override { + // this->index->setup(); + // this->middle->setup(); + // this->ring->setup(); + // this->pinky->setup(); + }; + + bool getValue() 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 setup() override { + // this->index->setup(); + }; + + bool getValue() 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, + uint16_t threshold + ) : index(index), thumb(thumb), threshold(threshold) { }; + + void setup() override { + // this->index->setup(); + // this->thumb->setup(); + }; + + bool getValue() override { + return this->index->getCurl() > this->threshold + && this->thumb->getCurl() > this->threshold; + } + }; +} // namespace OpenGloves diff --git a/test/test_opengloves_gesture/main.cpp b/test/test_opengloves_gesture/main.cpp new file mode 100644 index 00000000..11d9e112 --- /dev/null +++ b/test/test_opengloves_gesture/main.cpp @@ -0,0 +1,98 @@ +#include +#include + +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 = new TestCurlFinger(threshold - 1); + auto* middle = new TestCurlFinger(threshold - 1); + auto* ring = new TestCurlFinger(threshold - 1); + auto* pinky = new TestCurlFinger(threshold - 1); + + auto* gesture = new 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 = new TestCurlFinger(threshold - 1); + + auto* gesture = new 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 = new TestCurlFinger(threshold - 1); + auto* middle = new TestCurlFinger(threshold - 1); + + auto* gesture = new 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 + +void setup(void) { + process(); +} + +void loop(void) {} + +#else + +int main(int argc, char** argv) { + return process(); +} + +#endif From 3ccc213241567553a9692fdc56bd77d8cedaa943 Mon Sep 17 00:00:00 2001 From: Leonid Meleshin Date: Tue, 13 Jun 2023 11:20:16 +0400 Subject: [PATCH 23/23] feat(OpenGloves): enable calibration on button press --- .../mode_configs/opengloves/opengloves.cpp | 57 +++++++++++++++++-- ini/opengloves.ini | 9 +-- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp index 235129f5..bfa4b8b5 100644 --- a/firmware/mode_configs/opengloves/opengloves.cpp +++ b/firmware/mode_configs/opengloves/opengloves.cpp @@ -8,9 +8,18 @@ #include #include +#pragma region Calibration + #ifndef CALIBRATION_CURL #define CALIBRATION_CURL OH::MinMaxCalibrator #endif +#ifndef CALIBRATION_DURATION +#define CALIBRATION_DURATION 2000 // duration in milliseconds +#endif + +#pragma endregion + +#pragma region Fingers #define FINGER_THUMB_ENABLED (PIN_FINGER_THUMB != -1) #define FINGER_INDEX_ENABLED (PIN_FINGER_INDEX != -1) @@ -20,10 +29,18 @@ #define FINGER_COUNT (FINGER_THUMB_ENABLED + FINGER_INDEX_ENABLED + FINGER_MIDDLE_ENABLED + FINGER_RING_ENABLED + FINGER_PINKY_ENABLED) #define FINGER_CLASS(type, pin, invert, calib) new FingerSensor(new OH::CalibratedSensor(new OH::AnalogSensor(pin), new calib()), type) +#pragma endregion + +#pragma region Joysticks + #define JOYSTICK_ENABLED (PIN_JOYSTICK_X != -1 && PIN_JOYSTICK_Y != -1) #define JOYSTICK_COUNT (JOYSTICK_ENABLED ? 2 : 0) #define JOYSTICK_CLASS(type, pin, invert, deadzone) new StringEncodedMemoizedSensor(new OH::JoystickAxisSensor(new OH::AnalogSensor(pin), deadzone), type) +#pragma endregion + +#pragma region Buttons + #define BUTTON_A_ENABLED (PIN_BUTTON_A != -1) #define BUTTON_B_ENABLED (PIN_BUTTON_B != -1) #define BUTTON_MENU_ENABLED (PIN_BUTTON_MENU != -1) @@ -35,6 +52,10 @@ #define BUTTON_COUNT (BUTTON_A_ENABLED + BUTTON_B_ENABLED + BUTTON_MENU_ENABLED + BUTTON_JOYSTICK_ENABLED + BUTTON_CALIBRATE_ENABLED + BUTTON_TRIGGER_ENABLED + BUTTON_GRAB_ENABLED + BUTTON_PINCH_ENABLED) #define BUTTON_CLASS(type, pin, invert) new StringEncodedMemoizedSensor(new OH::DigitalSensor(pin), type) +#pragma endregion + +#pragma region Gestures + #ifndef GESTURE_TRIGGER_THRESHOLD #define GESTURE_TRIGGER_THRESHOLD (ANALOG_MAX / 2) #endif @@ -49,6 +70,8 @@ #define GESTURE_CLASS(type, sensor) new StringEncodedMemoizedSensor(sensor, type) +#pragma endregion + #ifndef UPDATE_RATE #define UPDATE_RATE 90 #endif @@ -150,7 +173,17 @@ std::vector*> buttons = std::vector inputs = std::vector(); + std::vector calibrated = std::vector(); +unsigned long long calibrationStarted = 0; +void startCalibration(void) { + for (size_t i = 0; i < calibrated.size(); i++) { + auto* input = calibrated[i]; + input->resetCalibration(); + input->enableCalibration(); + } + calibrationStarted = millis(); +} auto communication = new SerialCommunication(&Serial); @@ -159,10 +192,6 @@ void setupMode() { auto* finger = fingers[i]; finger->setup(); - #if defined(CALIBRATION_ALWAYS_CALIBRATE) && CALIBRATION_ALWAYS_CALIBRATE - finger->enableCalibration(); - #endif - inputs.push_back(finger); calibrated.push_back(finger); } @@ -181,6 +210,10 @@ void setupMode() { inputs.push_back(button); } +#if defined(CALIBRATION_ALWAYS_CALIBRATE) && CALIBRATION_ALWAYS_CALIBRATE + startCalibration(); +#endif + communication->setup(); } @@ -193,6 +226,22 @@ void loopMode() { input->updateValue(); } +#if BUTTON_CALIBRATE_ENABLED + if (calibrateButton->getValue() == true) { + startCalibration(); + } +#endif + +#if !CALIBRATION_ALWAYS_CALIBRATE + if (calibrationStarted > 0 && now - calibrationStarted > CALIBRATION_DURATION) { + for (size_t i = 0; i < calibrated.size(); i++) { + auto* input = calibrated[i]; + input->disableCalibration(); + } + calibrationStarted = 0; + } +#endif + // send all sensor values communication->send(inputs); diff --git a/ini/opengloves.ini b/ini/opengloves.ini index 9f22e627..e7b6a687 100644 --- a/ini/opengloves.ini +++ b/ini/opengloves.ini @@ -32,11 +32,8 @@ build_flags = ${common.build_flags} -D GESTURE_GRAB_ENABLED=true -D GESTURE_PINCH_ENABLED=true - ; Calibration - -D CALIBRATION_ALWAYS_CALIBRATE=false - ; Pins configuration - ; If pin set to -1, then it will be ignored + ; If pin set to -1, then it will be disabled -D PIN_FINGER_THUMB=32 -D PIN_FINGER_INDEX=35 -D PIN_FINGER_MIDDLE=34 @@ -55,6 +52,10 @@ build_flags = ${common.build_flags} -D PIN_BUTTON_GRAB=13 ; unused if GESTURE_GRAB is true -D PIN_BUTTON_PINCH=23 ; unused if GESTURE_PINCH is 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}