diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0298c543..a50a53a9 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: @@ -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 ] @@ -76,11 +77,15 @@ 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/.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 @@ -118,7 +123,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 @@ -146,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: @@ -174,9 +282,12 @@ jobs: - name: Cache PlatformIO uses: actions/cache@v3 with: - path: ~/.platformio - key: ${{ runner.os }}-pio-${{ hashFiles('**/lockfiles') }} + 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 @@ -191,6 +302,9 @@ jobs: pio upgrade --dev pio pkg update --global + - name: Install libs + run: pio pkg install -e ${{matrix.target}} + - name: Run Unit Tests run: pio test -e ${{matrix.target}} @@ -207,7 +321,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/.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/.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) 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 new file mode 100644 index 00000000..8e5cf34e --- /dev/null +++ b/firmware/mode_configs/bhaptics/tactvisor.cpp @@ -0,0 +1,64 @@ +// 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 BatterySensor( + new ADCNaiveBattery(36), + &App, + { .sampleRate = BATTERY_SAMPLE_RATE }, + { "ADC Battery", 4096, BATTERY_TASK_PRIORITY, tskNO_AFFINITY } + ); + battery->begin(); +#endif +} + +void loopMode() { + // Free up the Arduino loop task + vTaskDelete(NULL); +} diff --git a/firmware/mode_configs/opengloves/opengloves.cpp b/firmware/mode_configs/opengloves/opengloves.cpp new file mode 100644 index 00000000..bfa4b8b5 --- /dev/null +++ b/firmware/mode_configs/opengloves/opengloves.cpp @@ -0,0 +1,252 @@ +#include +#include +#include +#include +#include +#include +#include +#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) +#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 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) +#define BUTTON_JOYSTICK_ENABLED (JOYSTICK_ENABLED && (PIN_BUTTON_JOYSTICK != -1)) +#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) + +#pragma endregion + +#pragma region Gestures + +#ifndef GESTURE_TRIGGER_THRESHOLD +#define GESTURE_TRIGGER_THRESHOLD (ANALOG_MAX / 2) +#endif + +#ifndef GESTURE_GRAB_THRESHOLD +#define GESTURE_GRAB_THRESHOLD (ANALOG_MAX / 2) +#endif + +#ifndef GESTURE_PINCH_THRESHOLD +#define GESTURE_PINCH_THRESHOLD (ANALOG_MAX / 2) +#endif + +#define GESTURE_CLASS(type, sensor) new StringEncodedMemoizedSensor(sensor, type) + +#pragma endregion + +#ifndef UPDATE_RATE +#define UPDATE_RATE 90 +#endif +#define UPDATE_INTERVAL (1000 / UPDATE_RATE) + +using namespace OpenGloves; + +#pragma region FingerSensor + +#if FINGER_THUMB_ENABLED + auto* fingerThumbCurl = FINGER_CLASS(IEncodedInput::Type::THUMB, PIN_FINGER_THUMB, FINGER_THUMB_INVERT, CALIBRATION_CURL); +#endif + +#if FINGER_INDEX_ENABLED + auto* fingerIndexCurl = FINGER_CLASS(IEncodedInput::Type::INDEX, PIN_FINGER_INDEX, FINGER_INDEX_INVERT, CALIBRATION_CURL); +#endif + +#if FINGER_MIDDLE_ENABLED + auto* fingerMiddleCurl = FINGER_CLASS(IEncodedInput::Type::MIDDLE, PIN_FINGER_MIDDLE, FINGER_MIDDLE_INVERT, CALIBRATION_CURL); +#endif + +#if FINGER_RING_ENABLED + auto* fingerRingCurl = FINGER_CLASS(IEncodedInput::Type::RING, PIN_FINGER_RING, FINGER_RING_INVERT, CALIBRATION_CURL); +#endif + +#if FINGER_PINKY_ENABLED + auto* fingerPinkyCurl = FINGER_CLASS(IEncodedInput::Type::PINKY, PIN_FINGER_PINKY, FINGER_PINKY_INVERT, CALIBRATION_CURL); +#endif + +FingerSensor* fingers[FINGER_COUNT] = { +#if FINGER_THUMB_ENABLED + fingerThumbCurl, +#endif +#if FINGER_INDEX_ENABLED + fingerIndexCurl, +#endif +#if FINGER_MIDDLE_ENABLED + fingerMiddleCurl, +#endif +#if FINGER_RING_ENABLED + fingerRingCurl, +#endif +#if FINGER_PINKY_ENABLED + fingerPinkyCurl, +#endif +}; + +#pragma endregion + +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), +#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_JOYSTICK_ENABLED + BUTTON_CLASS(IEncodedInput::Type::JOY_BTN, PIN_BUTTON_JOYSTICK, BUTTON_JOYSTICK_INVERT), +#endif + +#if BUTTON_MENU_ENABLED + BUTTON_CLASS(IEncodedInput::Type::MENU, PIN_BUTTON_MENU, BUTTON_MENU_INVERT), +#endif + +#if BUTTON_CALIBRATE_ENABLED + calibrateButton, +#endif + +#if GESTURE_TRIGGER_ENABLED && FINGER_INDEX_ENABLED + GESTURE_CLASS(IEncodedInput::Type::TRIGGER, new TriggerGesture(fingerIndexCurl, GESTURE_TRIGGER_THRESHOLD)), +#elif BUTTON_TRIGGER_ENABLED + BUTTON_CLASS(IEncodedInput::Type::TRIGGER, PIN_BUTTON_TRIGGER, BUTTON_TRIGGER_INVERT), +#endif + +#if GESTURE_GRAB_ENABLED && FINGER_INDEX_ENABLED && FINGER_MIDDLE_ENABLED && FINGER_RING_ENABLED && FINGER_PINKY_ENABLED + GESTURE_CLASS(IEncodedInput::Type::GRAB, new GrabGesture(fingerIndexCurl, fingerMiddleCurl, fingerRingCurl, fingerPinkyCurl, GESTURE_GRAB_THRESHOLD)), +#elif BUTTON_GRAB_ENABLED + BUTTON_CLASS(IEncodedInput::Type::GRAB, PIN_BUTTON_GRAB, BUTTON_GRAB_INVERT), +#endif + +#if GESTURE_PINCH_ENABLED && FINGER_THUMB_ENABLED && FINGER_INDEX_ENABLED + GESTURE_CLASS(IEncodedInput::Type::PINCH, new PinchGesture(fingerThumbCurl, fingerIndexCurl, GESTURE_PINCH_THRESHOLD)), +#elif BUTTON_PINCH_ENABLED + BUTTON_CLASS(IEncodedInput::Type::PINCH, PIN_BUTTON_PINCH, BUTTON_PINCH_INVERT), +#endif +}; + +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); + +void setupMode() { + for (size_t i = 0; i < FINGER_COUNT; i++) { + auto* finger = fingers[i]; + finger->setup(); + + inputs.push_back(finger); + calibrated.push_back(finger); + } + + for (size_t i = 0; i < JOYSTICK_COUNT; i++) { + auto* axis = joystick[i]; + axis->setup(); + + inputs.push_back(axis); + } + + for (size_t i = 0; i < BUTTON_COUNT; i++) { + auto* button = buttons[i]; + button->setup(); + + inputs.push_back(button); + } + +#if defined(CALIBRATION_ALWAYS_CALIBRATE) && CALIBRATION_ALWAYS_CALIBRATE + startCalibration(); +#endif + + communication->setup(); +} + +void loopMode() { + auto now = millis(); + + // update all sensor values + for (size_t i = 0; i < inputs.size(); i++) { + auto* input = inputs[i]; + 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); + + auto elapsed = millis() - now; + if (elapsed < UPDATE_INTERVAL) { + delay(UPDATE_INTERVAL - elapsed); + } +} 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/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/ini/opengloves.ini b/ini/opengloves.ini new file mode 100644 index 00000000..e7b6a687 --- /dev/null +++ b/ini/opengloves.ini @@ -0,0 +1,77 @@ +[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 + + -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_ENABLED=true + -D GESTURE_GRAB_ENABLED=true + -D GESTURE_PINCH_ENABLED=true + + ; Pins configuration + ; 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 + -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 + + ; Calibration + -D CALIBRATION_ALWAYS_CALIBRATE=false + -D CALIBRATION_DURATION=2000 ; in ms + + -D UPDATE_RATE=90 ; sensors update rate in Hz + +build_unflags = ${common.build_unflags} +build_src_filter = ${common.build_src_filter} +lib_deps = ${common.lib_deps} + +[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/battery/adc_naive.cpp b/lib/arduino/battery/adc_naive.cpp index 7d396997..90e54d85 100644 --- a/lib/arduino/battery/adc_naive.cpp +++ b/lib/arduino/battery/adc_naive.cpp @@ -1,11 +1,17 @@ #include "battery/adc_naive.hpp" +#include #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 = simpleMap(analogRead(this->pin), 4095, 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/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/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/digital.hpp b/lib/arduino/sensor/digital.hpp new file mode 100644 index 00000000..abd812d2 --- /dev/null +++ b/lib/arduino/sensor/digital.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +namespace OH +{ + typedef ISensor IDigitalSensor; + + template + class DigitalSensor : public IDigitalSensor + { + private: + uint8_t pin; + + public: + DigitalSensor(uint8_t pin) : pin(pin) { + } + + void setup(void) + { + pinMode(this->pin, INPUT); + }; + + 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 diff --git a/lib/arduino/sensor/joystick.hpp b/lib/arduino/sensor/joystick.hpp new file mode 100644 index 00000000..32c28d30 --- /dev/null +++ b/lib/arduino/sensor/joystick.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include + +namespace OH { + /** + * Joystick axis sensor decorator + */ + template + class JoystickAxisSensor : public ISensor<_Tp> { + private: + ISensor<_Tp>* sensor; + float dead_zone; + + int filterDeadZone(int in) { + // This function clamps the input to the center of the range if + // the value is within the threshold. This is to eliminate at-rest + // noise of the joystick. + int center = ANALOG_MAX / 2; + return abs(center - in) < dead_zone * ANALOG_MAX ? center : in; + } + + public: + JoystickAxisSensor(ISensor<_Tp>* sensor, float dead_zone) : sensor(sensor), dead_zone(dead_zone) {}; + + void setup(void) { + 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/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/bh_constants.hpp b/lib/bhaptics/bh_constants.hpp index a93c5be2..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 @@ -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 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 7e9978e0..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)->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/calibration/calibration.hpp b/lib/calibration/calibration.hpp new file mode 100644 index 00000000..ae94fcf3 --- /dev/null +++ b/lib/calibration/calibration.hpp @@ -0,0 +1,140 @@ +/** + * Calibrated input + * Credit: https://github.com/JohnRThomas/OpenGloves-Firmware/blob/main/open-gloves/Calibration.hpp + */ + +#pragma once + +#include + +namespace OH { + 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() override { + calibrate = true; + } + + void disableCalibration() override { + calibrate = false; + } + }; + + template + struct ICalibrator { + virtual void reset() = 0; + virtual void update(_Tp input) = 0; + virtual _Tp calibrate(_Tp input) const = 0; + }; + + template + class MinMaxCalibrator : public ICalibrator<_Tp> { + public: + MinMaxCalibrator() : value_min(output_max), value_max(output_min) {} + + void reset() { + value_min = output_max; + value_max = output_min; + } + + void update(_Tp input) { + // Update the min and the max. + if (input < value_min) value_min = input; + if (input > value_max) value_max = input; + } + + _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) { + 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. + _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: + _Tp value_min; + _Tp value_max; + }; + + template + class CenterPointDeviationCalibrator : public ICalibrator<_Tp> { + public: + CenterPointDeviationCalibrator() : range_min(sensor_max), range_max(0) { + #warning "CenterPointDeviationCalibrator is untested and may not work as expected." + } + + void reset() { + range_min = sensor_max; + range_max = 0; + } + + void update(_Tp input) { + // Update the min and the 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); + } + + _Tp calibrate(_Tp input) const { + // Find the center point of the sensor so we know how much we have deviated from it. + _Tp center = (range_min + range_max) / 2.0f; + + // Map the input to the sensor range of motion. + 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 (_Tp) accurateMap(output, -driver_max_deviation, driver_max_deviation, output_min, output_max); + } + + private: + _Tp range_min; + _Tp range_max; + }; + + template + class FixedCenterPointDeviationCalibrator : public ICalibrator<_Tp> { + public: + void reset() {} + void update(_Tp input) {} + + _Tp calibrate(_Tp input) const { + // Find the center point of the sensor so we know how much we have deviated from it. + _Tp center = sensor_max / 2.0f; + + // Map the input to the sensor range of motion. + 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 (_Tp) accurateMap(output, -driver_max_deviation, driver_max_deviation, output_min, output_max); + } + }; +} 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..d80469c8 100644 --- a/lib/core/utility.hpp +++ b/lib/core/utility.hpp @@ -1,8 +1,13 @@ #pragma once +#include #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 +26,25 @@ 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) { - 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. + 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 ed882f00..27bdf9eb 100644 --- a/lib/ina219/battery/ina219.cpp +++ b/lib/ina219/battery/ina219.cpp @@ -1,17 +1,22 @@ #include "battery/ina219.hpp" +#include -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 { + simpleMap(batteryPercentage, 1.0f, 255.0f); + }; + } } 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/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/og_protocol.hpp b/lib/opengloves/og_protocol.hpp new file mode 100644 index 00000000..89270585 --- /dev/null +++ b/lib/opengloves/og_protocol.hpp @@ -0,0 +1,51 @@ +#pragma once + +namespace OpenGloves +{ + struct IEncodedInput { + public: + enum Type : char { + THUMB = 'A', + INDEX = 'B', + MIDDLE = 'C', + RING = 'D', + PINKY = 'E', + JOY_X = 'F', + JOY_Y = 'G', + JOY_BTN = 'H', + TRIGGER = 'I', + A_BTN = 'J', + B_BTN = 'K', + GRAB = 'L', + PINCH = 'M', + MENU = 'N', + CALIBRATE = 'O' + }; + + IEncodedInput(Type type) : type(type) { }; + + constexpr Type getType() const { + return this->type; + } + + private: + Type type; + }; + + class IStringEncodedSensor : public IEncodedInput { + public: + IStringEncodedSensor(Type type) : IEncodedInput(type) { }; + + virtual void setup() = 0; + virtual void updateValue() = 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; + }; +} // namespace OpenGloves diff --git a/lib/opengloves/sensor/og_finger.hpp b/lib/opengloves/sensor/og_finger.hpp new file mode 100644 index 00000000..8716b2f2 --- /dev/null +++ b/lib/opengloves/sensor/og_finger.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "og_sensor.hpp" + +namespace OpenGloves +{ + 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() override { + 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(); + } + + void enableCalibration() override { + static_cast*>(this->sensor)->enableCalibration(); + } + + void disableCalibration() override { + static_cast*>(this->sensor)->disableCalibration(); + } + }; + + // TODO: add splay finger sensor + +} // namespace OpenGloves 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/lib/opengloves/sensor/og_sensor.hpp b/lib/opengloves/sensor/og_sensor.hpp new file mode 100644 index 00000000..1ad6dfcf --- /dev/null +++ b/lib/opengloves/sensor/og_sensor.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include + +namespace OpenGloves +{ + template + class StringEncodedMemoizedSensor : public IStringEncodedSensor, public OH::MemoizedSensor<_Tp> { + public: + StringEncodedMemoizedSensor(OH::ISensor<_Tp>* sensor, IEncodedInput::Type type) + : IStringEncodedSensor(type), OH::MemoizedSensor<_Tp>(sensor) { }; + + void setup() override { + this->sensor->setup(); + } + + void updateValue() override { + this->value = this->sensor->getValue(); + } + + size_t getEncodedLength() const override; + + size_t encodeString(char* buffer) const override; + }; + + template <> + 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) const { + if (value) { + buffer[0] = this->getType(); + } + 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 new file mode 100644 index 00000000..dc6a0c4d --- /dev/null +++ b/lib/opengloves_serial/og_serial_commmunications.hpp @@ -0,0 +1,42 @@ +#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 { + // 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 (size_t i = 0; i < sensors.size(); i++) { + // The offset is the total charecters already added to the string. + offset += sensors[i]->encodeString(buffer + offset); + } + + // Add a new line to the end of the encoded string. + buffer[offset++] = '\n'; + buffer[offset] = '\0'; + + return offset; + } + }; +} // namespace OpenGloves 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/lib/sensor/sensor.hpp b/lib/sensor/sensor.hpp index fe70ed29..93289b5f 100644 --- a/lib/sensor/sensor.hpp +++ b/lib/sensor/sensor.hpp @@ -1,35 +1,117 @@ #pragma once -#include "task.hpp" +#include + +#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 { + /** + * 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() = 0; + + /** + * 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>; - - private: - virtual void run(void) { - while (true) { - this->value = this->updateValue(); - delay(this->rate); - } - }; + class MemoizedSensor : public ISensor<_Tp> { + protected: + ISensor<_Tp>* sensor; + _Tp value; + + public: + /** + * @param sensor Sensor to be decorated + */ + MemoizedSensor(ISensor<_Tp>* sensor) : sensor(sensor) {}; + + /** + * Setup the sensor hardware + */ + void setup() override { + this->sensor->setup(); + }; + + /** + * Get the current memoized value + */ + _Tp getValue() override { + return this->value; + }; + + /** + * Read actual value from the hardware and memoize it + */ + void updateValue() { + this->value = this->sensor->getValue(); + }; + }; + /** + * Calibrated sensor decorator + * + * @tparam _Tp Type of the sensor value + */ + template + class CalibratedSensor : public ISensor<_Tp>, public Calibrated { protected: - _Tp value; - uint32_t rate; - virtual _Tp updateValue(void) = 0; + ISensor<_Tp>* sensor; + ICalibrator<_Tp>* calibrator; + + _Tp getCalibratedValue() { + auto value = this->sensor->getValue(); + + if (this->calibrate) { + this->calibrator->update(value); + } + + return this->calibrator->calibrate(value); + } public: - ThrottledSensor(TaskConfig taskConfig, uint32_t rate) : Task>(taskConfig), rate(rate) {}; - _Tp getValue() override { return this->value; }; + /** + * @param sensor Sensor to be decorated + * @param calibrator ICalibrator algorithm to be used + */ + CalibratedSensor(ISensor<_Tp>* sensor, ICalibrator<_Tp>* calibrator) : sensor(sensor), calibrator(calibrator) {}; + + void setup() override { + this->sensor->setup(); + }; + + _Tp getValue() override { + return this->getCalibratedValue(); + }; + + void resetCalibration() override { + this->calibrator->reset(); + }; }; } // namespace 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 = diff --git a/test/test_calibration/main.cpp b/test/test_calibration/main.cpp new file mode 100644 index 00000000..fffcde13 --- /dev/null +++ b/test/test_calibration/main.cpp @@ -0,0 +1,112 @@ +#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)); +} + +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(); +} + +#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_core_utility/main.cpp b/test/test_core_utility/main.cpp index 4b4335fb..4e9c684f 100644 --- a/test/test_core_utility/main.cpp +++ b/test/test_core_utility/main.cpp @@ -42,18 +42,26 @@ 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)); + + TEST_ASSERT_EQUAL_UINT16(2047, accurateMap(343, 343, 343, 0, 4095)); +} + +void test_simple_map(void) { + TEST_ASSERT_EQUAL_UINT16(0, simpleMap(0, 4095, 255)); + TEST_ASSERT_EQUAL_UINT16(127, simpleMap(2047, 4095, 255)); + TEST_ASSERT_EQUAL_UINT16(255, simpleMap(4095, 4095, 255)); } int process(void) { @@ -62,7 +70,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(); } 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_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 diff --git a/test/test_sensor/main.cpp b/test/test_sensor/main.cpp new file mode 100644 index 00000000..0dd68966 --- /dev/null +++ b/test/test_sensor/main.cpp @@ -0,0 +1,104 @@ +#include +#include + +using namespace OH; + +class TestAnalogSensor : 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 TestAnalogSensor(); + 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()); +} + +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(); +} + +#ifdef ARDUINO + +#include + +void setup(void) { + process(); +} + +void loop(void) {} + +#else + +int main(int argc, char** argv) { + return process(); +} + +#endif