From 4fe8990a6f3286b35f27f9a83d1099f88de1c35f Mon Sep 17 00:00:00 2001 From: Aurelien Labrosse Date: Fri, 24 Nov 2023 09:47:00 +0000 Subject: [PATCH] feat: Implement Gun --- lib/Domain/Gun.cpp | 39 ----- lib/Domain/Gun.hpp | 33 ---- lib/Domain/Gun/Gun.cpp | 99 ++++++++++++ lib/Domain/Gun/Gun.hpp | 65 ++++++++ test/native/test_gun/gun.cpp | 288 +++++++++++++++++++++-------------- 5 files changed, 336 insertions(+), 188 deletions(-) delete mode 100644 lib/Domain/Gun.cpp delete mode 100644 lib/Domain/Gun.hpp create mode 100644 lib/Domain/Gun/Gun.cpp create mode 100644 lib/Domain/Gun/Gun.hpp diff --git a/lib/Domain/Gun.cpp b/lib/Domain/Gun.cpp deleted file mode 100644 index 40f5832..0000000 --- a/lib/Domain/Gun.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/* - * - * Copyright (c) 2023 Aurélien Labrosse - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include - -static const uint8_t SHOTS_PER_REARM = 5; - -void Gun::onButton1ShortPress() { - if (availableShots > 0) { - _hal.laserOn(); - _hal.vibrationOn(); - _hal.shortDelay(); - _hal.laserOff(); - _hal.vibrationOff(); - availableShots--; - } - -} - -void Gun::onButton1LongPress() { - _hal.deepSleep(); -} -void Gun::onButton2ShortPress() { availableShots = SHOTS_PER_REARM; } -void Gun::onButton2LongPress() { - _hal.laserOn(); -} diff --git a/lib/Domain/Gun.hpp b/lib/Domain/Gun.hpp deleted file mode 100644 index cfabb6e..0000000 --- a/lib/Domain/Gun.hpp +++ /dev/null @@ -1,33 +0,0 @@ -/* - * - * Copyright (c) 2023 Aurélien Labrosse - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once - -#include -#include - -class Gun { - - IGunHal &_hal; - -public: - uint8_t availableShots; - Gun(IGunHal &hal) : _hal(hal), availableShots(0) {} - void onButton1ShortPress(); - void onButton1LongPress(); - void onButton2ShortPress(); - void onButton2LongPress(); -}; \ No newline at end of file diff --git a/lib/Domain/Gun/Gun.cpp b/lib/Domain/Gun/Gun.cpp new file mode 100644 index 0000000..a9ff4b7 --- /dev/null +++ b/lib/Domain/Gun/Gun.cpp @@ -0,0 +1,99 @@ +/* + * + * Copyright (c) 2023 Aurélien Labrosse + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +#define TICKS_BETWEEN_BATTERY_UI_UPDATE 100 + +Gun::Gun(IGunHal *hal, IGunUi *ui) { + this->hal = hal; + this->ui = ui; + calibrationMode = false; + shootCount = 0; + + // display at first loop + shootCycleCountdown = 0; + batteryDisplayCycleCountdown = 0; + + button.setGun(this); + trigger.setGun(this); +} + +void Gun::toggleCalibrationMode() { + calibrationMode = !calibrationMode; + if (calibrationMode) { + hal->laserOn(); + hal->vibrationOff(); + ui->displayCalibration(); + } else { + hal->laserOff(); + ui->clearCalibration(); + ui->displayShootCount(shootCount); + batteryDisplayCycleCountdown = 0; + } +} + +void Gun::resetShoots() { + shootCount = 0; + ui->displayShootCount(shootCount); +} + +void Gun::activateShoot() { + if (!calibrationMode && shootCycleCountdown == 0) { + shootCount += 1; + hal->laserOn(); + hal->vibrationOn(); + + // add 1 to allow easy detection of last tick + shootCycleCountdown = Gun::SHOOT_DURATION_TICKS + 1; + } +} + +void Gun::loop(void) { + + // 10ms per loop thanks to timer2 + millisSinceStart += 10; + + trigger.processPendingEvent(millisSinceStart); + button.processPendingEvent(millisSinceStart); + + button.checkForLongPress(millisSinceStart); + + if (shootCycleCountdown > 0) { + shootCycleCountdown--; + if (shootCycleCountdown == 1) { + hal->laserOff(); + hal->vibrationOff(); + ui->displayShootCount(shootCount); + shootCycleCountdown--; + } + } + + if (batteryDisplayCycleCountdown > 0) { + batteryDisplayCycleCountdown--; + } else { + ui->displayBatteryStatus(hal->getBatteryVoltageMv(), + hal->getBatteryVoltagePercent()); + batteryDisplayCycleCountdown = TICKS_BETWEEN_BATTERY_UI_UPDATE; + } +} + +void Gun::setup(void) { + ui->displaySplash(2000); + // mV is not used + ui->displayBatteryStatus(0, hal->getBatteryVoltagePercent()); + ui->displayShootCount(0); +} \ No newline at end of file diff --git a/lib/Domain/Gun/Gun.hpp b/lib/Domain/Gun/Gun.hpp new file mode 100644 index 0000000..cf1fda3 --- /dev/null +++ b/lib/Domain/Gun/Gun.hpp @@ -0,0 +1,65 @@ +/* + * + * Copyright (c) 2023 Aurélien Labrosse + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include +#include +#include +#include +#include + +class Gun { + + uint8_t shootCycleCountdown; + uint16_t batteryDisplayCycleCountdown; + long millisSinceStart = 0; + bool calibrationMode; + +public: + /* 50 ms */ + static const uint8_t SHOOT_DURATION_TICKS = 5; + + IGunHal *hal; + IGunUi *ui; + Button button; + Trigger trigger; + uint16_t shootCount; + + Gun(IGunHal *hal, IGunUi *ui); + + /** + * Reset shoot count and redisplay + */ + void resetShoots(); + + /** + * Activate laser and vibrator + */ + void activateShoot(); + + void toggleCalibrationMode(); + + /** + * @brief Gun loop shall be called at least each 10ms, + * but interrupt shall trigger loop() execution in + * a shorter delay. + * + */ + void loop(); + + void setup(); +}; \ No newline at end of file diff --git a/test/native/test_gun/gun.cpp b/test/native/test_gun/gun.cpp index 3d2d8be..4ecc02b 100644 --- a/test/native/test_gun/gun.cpp +++ b/test/native/test_gun/gun.cpp @@ -15,165 +15,221 @@ * along with this program. If not, see . */ -#include -#include +#include +#include +#include + #include #include using namespace fakeit; #define MOCK_ALL() \ - When(Method(mock, vibrationOn)).AlwaysReturn(); \ - When(Method(mock, vibrationOff)).AlwaysReturn(); \ - When(Method(mock, laserOn)).AlwaysReturn(); \ - When(Method(mock, laserOff)).AlwaysReturn(); \ - When(Method(mock, ledOn)).AlwaysReturn(); \ - When(Method(mock, ledOff)).AlwaysReturn(); \ - When(Method(mock, shortDelay)).AlwaysReturn(); \ - When(Method(mock, longDelay)).AlwaysReturn(); \ - When(Method(mock, isButton1Pressed)).AlwaysReturn(); \ - When(Method(mock, isButton2Pressed)).AlwaysReturn(); \ - When(Method(mock, deepSleep)).AlwaysReturn(); + When(Method(mockHal, triggerIsUp)).AlwaysReturn(true); \ + When(Method(mockHal, buttonIsUp)).AlwaysReturn(true); \ + When(Method(mockHal, laserOn)).AlwaysReturn(); \ + When(Method(mockHal, vibrationOn)).AlwaysReturn(); \ + When(Method(mockHal, vibrationOff)).AlwaysReturn(); \ + When(Method(mockHal, laserOff)).AlwaysReturn(); \ + When(Method(mockHal, getBatteryVoltageMv)).AlwaysReturn(5000U); \ + When(Method(mockHal, getBatteryVoltagePercent)).AlwaysReturn(100U); \ + When(Method(mockHal, sleep)).AlwaysReturn(); \ + When(Method(mockUi, displayBatteryStatus)).AlwaysReturn(); \ + When(Method(mockUi, displayShootCount)).AlwaysReturn(); \ + When(Method(mockUi, displayCalibration)).AlwaysReturn(); \ + When(Method(mockUi, clearCalibration)).AlwaysReturn(); \ + When(Method(mockUi, displaySplash)).AlwaysReturn(); void tearDown() {} void setUp() { ArduinoFakeReset(); } -void expect_shot_to_activate_laser_during_short_duration() { +void expect_gun_to_loop() { + + Mock mockUi; + Mock mockHal; - Mock mock; MOCK_ALL(); - IGunHal &hal = mock.get(); - Gun gun(hal); - // first, give 5 shots - gun.onButton2ShortPress(); + IGunUi &ui = mockUi.get(); + IGunHal &hal = mockHal.get(); + + Gun gun(&hal, &ui); + + gun.loop(); +} +void expect_gun_to_shoot_50ms_on_trigger_down() { + + Mock mockUi; + Mock mockHal; - // shot - gun.onButton1ShortPress(); + MOCK_ALL(); - Verify(Method(mock, laserOn)).Once(); - Verify(Method(mock, shortDelay)).Once(); - Verify(Method(mock, laserOff)).Once(); + IGunUi &ui = mockUi.get(); + IGunHal &hal = mockHal.get(); + + Gun gun(&hal, &ui); + + gun.trigger.pendingEvent = Contactor::Event::Pressed; + gun.loop(); + gun.loop(); + gun.trigger.pendingEvent = Contactor::Event::Released; + gun.loop(); + gun.trigger.pendingEvent = Contactor::Event::NoEvent; + gun.loop(); + gun.loop(); + gun.loop(); + gun.loop(); + + // trigger is pressed then released, + // activating laser for 50ms/5 ticks + Verify(Method(mockHal, laserOn)).Once(); + Verify(Method(mockHal, vibrationOn)).Once(); + Verify(Method(mockHal, vibrationOff)).Once(); + Verify(Method(mockHal, laserOff)).Once(); } -void expect_shot_to_activate_vibration_during_short_duration() { +void expect_ui_to_display_battery_state_at_boot_and_each_1s() { + Mock mockUi; + Mock mockHal; - Mock mock; MOCK_ALL(); - IGunHal &hal = mock.get(); - Gun gun(hal); - // first, give 5 shots - gun.onButton2ShortPress(); + IGunUi &ui = mockUi.get(); + IGunHal &hal = mockHal.get(); + + Gun gun(&hal, &ui); - // shot - gun.onButton1ShortPress(); + for (uint8_t tickCounter = 0; tickCounter <= 101; tickCounter++) { + // displayBatteryStatus call after 100 ticks + gun.loop(); + } - Verify(Method(mock, vibrationOn)).Once(); - Verify(Method(mock, shortDelay)).Once(); - Verify(Method(mock, vibrationOff)).Once(); + // display at first loop, then each second (or 100 ticks) + Verify(Method(mockUi, displayBatteryStatus)).Exactly(2); } -void expect_short_press_on_button2_to_give_5_shots() { - Mock mock; +void expect_switch_to_maintenance_after_2s_button_continuous_press() { + Mock mockUi; + Mock mockHal; + MOCK_ALL(); - IGunHal &hal = mock.get(); - Gun gun(hal); - gun.onButton2ShortPress(); - TEST_ASSERT_EQUAL_INT_MESSAGE(5, gun.availableShots, - "Shots shall have been incremented"); - - gun.onButton2ShortPress(); - TEST_ASSERT_EQUAL_INT_MESSAGE(5, gun.availableShots, - "A rearm always provide 5 shots"); + + IGunUi &ui = mockUi.get(); + IGunHal &hal = mockHal.get(); + + Gun gun(&hal, &ui); + + gun.button.pendingEvent = Contactor::Event::Pressed; + for (uint8_t tickCounter = 0; tickCounter <= 200; tickCounter++) { + // long press shall be accounted after 2s, 200 ticks + gun.loop(); + } + gun.button.pendingEvent = Contactor::Event::Released; + gun.loop(); + gun.button.pendingEvent = Contactor::Event::Pressed; + for (uint8_t tickCounter = 0; tickCounter <= 200; tickCounter++) { + // long press shall be accounted after 2s, 200 ticks + gun.loop(); + } + + // switch to calibration + Verify(Method(mockHal, laserOn)).Exactly(1); + Verify(Method(mockUi, displayCalibration)).Exactly(1); + + // back to normal + Verify(Method(mockHal, laserOff)).Exactly(1); + Verify(Method(mockUi, clearCalibration)).Exactly(1); + Verify(Method(mockUi, displayShootCount)).Exactly(1); } -void expect_shot_to_decrement_available_shot_count() { - Mock mock; +void expect_button_press_to_reset_shoot_count_and_redisplay() { + Mock mockUi; + Mock mockHal; MOCK_ALL(); - IGunHal &hal = mock.get(); - Gun gun(hal); - - // first, give 5 shots - gun.onButton2ShortPress(); - - gun.onButton1ShortPress(); - TEST_ASSERT_EQUAL_INT_MESSAGE( - 4, gun.availableShots, - "There shall be 4 more available shots after 1 shots"); - gun.onButton1ShortPress(); - gun.onButton1ShortPress(); - gun.onButton1ShortPress(); - gun.onButton1ShortPress(); - TEST_ASSERT_EQUAL_INT_MESSAGE( - 0, gun.availableShots, - "There shall be no more available shots after 5 shots"); - gun.onButton1ShortPress(); - - // no bug when shooting with a remaining of 0 - TEST_ASSERT_EQUAL_INT_MESSAGE( - 0, gun.availableShots, - "There shall be no more available shots after 5 shots"); + IGunUi &ui = mockUi.get(); + IGunHal &hal = mockHal.get(); + + Gun gun(&hal, &ui); + + gun.shootCount = 10; + + gun.button.pendingEvent = Contactor::Event::Pressed; + gun.loop(); + gun.button.pendingEvent = Contactor::Event::Released; + gun.loop(); + + TEST_ASSERT_EQUAL_MESSAGE(0, gun.shootCount, "Shoot count shall be reset"); + Verify(Method(mockUi, displayShootCount)).Exactly(1); } -void expect_shot_to_do_nothing_if_there_is_no_available_shots() { - Mock mock; +void expect_trigger_to_have_no_effect_in_calibration_mode() { + Mock mockUi; + Mock mockHal; + MOCK_ALL(); - IGunHal &hal = mock.get(); - Gun gun(hal); - - // first, give 5 shots - gun.onButton2ShortPress(); - - gun.onButton1ShortPress(); // shot 1 - gun.onButton1ShortPress(); // shot 2 - gun.onButton1ShortPress(); // shot 3 - gun.onButton1ShortPress(); // shot 4 - gun.onButton1ShortPress(); // shot 5 - gun.onButton1ShortPress(); // shot 6, which shall do nothing - - Verify(Method(mock, vibrationOn)).Exactly(5); - Verify(Method(mock, vibrationOff)).Exactly(5); - Verify(Method(mock, laserOn)).Exactly(5); - Verify(Method(mock, laserOff)).Exactly(5); - Verify(Method(mock, shortDelay)).Exactly(5); - Verify(Method(mock, vibrationOff)).Exactly(5); + + IGunUi &ui = mockUi.get(); + IGunHal &hal = mockHal.get(); + + Gun gun(&hal, &ui); + + gun.button.pendingEvent = Contactor::Event::Pressed; + for (uint8_t tickCounter = 0; tickCounter <= 200; tickCounter++) { + // long press shall be accounted after 2s, 200 ticks + gun.loop(); + } + + gun.button.pendingEvent = Contactor::Event::Released; + gun.loop(); + + gun.trigger.pendingEvent = Contactor::Event::Pressed; + gun.loop(); + gun.trigger.pendingEvent = Contactor::Event::Released; + gun.loop(); + + Verify(Method(mockHal, laserOn)).Exactly(1); + Verify(Method(mockHal, laserOff)).Exactly(0); } -void expect_long_press_on_button_two_to_activate_continuous_laser() { - Mock mock; +void expect_long_press_to_trigger_only_if_button_is_down() { + Mock mockUi; + Mock mockHal; + MOCK_ALL(); - IGunHal &hal = mock.get(); - Gun gun(hal); - gun.onButton2LongPress(); + IGunUi &ui = mockUi.get(); + IGunHal &hal = mockHal.get(); - Verify(Method(mock, laserOn)).Once(); - Verify(Method(mock, shortDelay)).Exactly(0); - Verify(Method(mock, laserOff)).Exactly(0); -} + Gun gun(&hal, &ui); + + gun.button.pendingEvent = Contactor::Event::Pressed; + gun.loop(); -void expect_long_press_on_button_one_to_trigger_deep_sleep() -{ - Mock mock; - When(Method(mock, deepSleep)).Return(); - IGunHal &hal = mock.get(); - Gun gun(hal); + gun.button.pendingEvent = Contactor::Event::Released; + gun.loop(); - gun.onButton1LongPress(); + for (uint8_t tickCounter = 0; tickCounter <= 200; tickCounter++) { + // long press shall be accounted after 2s, 200 ticks + gun.loop(); + } - Verify(Method(mock, deepSleep)).Once(); + // No long press, no switch to calibration + Verify(Method(mockHal, laserOn)).Exactly(0); + Verify(Method(mockUi, displayCalibration)).Exactly(0); + Verify(Method(mockHal, laserOff)).Exactly(0); + Verify(Method(mockUi, clearCalibration)).Exactly(0); + Verify(Method(mockUi, displayShootCount)).Exactly(1); } int main(int, char **) { UNITY_BEGIN(); - RUN_TEST(expect_short_press_on_button2_to_give_5_shots); - RUN_TEST(expect_shot_to_activate_laser_during_short_duration); - RUN_TEST(expect_shot_to_activate_vibration_during_short_duration); - RUN_TEST(expect_shot_to_decrement_available_shot_count); - RUN_TEST(expect_shot_to_do_nothing_if_there_is_no_available_shots); - RUN_TEST(expect_long_press_on_button_two_to_activate_continuous_laser); - RUN_TEST(expect_long_press_on_button_one_to_trigger_deep_sleep); + RUN_TEST(expect_gun_to_loop); + RUN_TEST(expect_gun_to_shoot_50ms_on_trigger_down); + RUN_TEST(expect_ui_to_display_battery_state_at_boot_and_each_1s); + RUN_TEST(expect_switch_to_maintenance_after_2s_button_continuous_press); + RUN_TEST(expect_long_press_to_trigger_only_if_button_is_down); + RUN_TEST(expect_button_press_to_reset_shoot_count_and_redisplay); + RUN_TEST(expect_trigger_to_have_no_effect_in_calibration_mode); UNITY_END(); return 0;