From 87bf553b4a441ae39d10664150f5decf5879985f Mon Sep 17 00:00:00 2001 From: Aurelien Labrosse Date: Wed, 22 Nov 2023 15:27:48 +0100 Subject: [PATCH] wip --- lib/{Domain => Cross}/Gun/Atmega328pHal.cpp | 12 +- lib/{Domain => Cross}/Gun/Atmega328pHal.hpp | 6 +- lib/{Domain => Cross}/Gun/SSD1306Ui.hpp | 1 + lib/Domain/Gun/Button.cpp | 1 + lib/Domain/Gun/Button.hpp | 2 +- lib/Domain/Gun/Gun.cpp | 48 +++- lib/Domain/Gun/Gun.hpp | 18 +- lib/Domain/Gun/IGunHal.hpp | 3 +- lib/Domain/Gun/Trigger.cpp | 11 +- lib/Domain/Gun/Trigger.hpp | 2 +- src/GunApp.cpp | 18 +- test/native/test_gun/gun.cpp | 235 ++++++++------------ 12 files changed, 169 insertions(+), 188 deletions(-) rename lib/{Domain => Cross}/Gun/Atmega328pHal.cpp (87%) rename lib/{Domain => Cross}/Gun/Atmega328pHal.hpp (92%) rename lib/{Domain => Cross}/Gun/SSD1306Ui.hpp (96%) diff --git a/lib/Domain/Gun/Atmega328pHal.cpp b/lib/Cross/Gun/Atmega328pHal.cpp similarity index 87% rename from lib/Domain/Gun/Atmega328pHal.cpp rename to lib/Cross/Gun/Atmega328pHal.cpp index 02594a5..5e59b10 100644 --- a/lib/Domain/Gun/Atmega328pHal.cpp +++ b/lib/Cross/Gun/Atmega328pHal.cpp @@ -32,13 +32,15 @@ #define MIN_BAT_VOLTAGE 3000 #define MAX_BAT_VOLTAGE 4120 -ISR(TIMER2_COMPA_vect) {} - Atmega328pHal::Atmega328pHal() { pinMode(VIBRATOR_PIN, OUTPUT); pinMode(LASER_PIN, OUTPUT); } + +bool Atmega328pHal::triggerIsUp() { return bit_is_set(PIND, PD2); } +bool Atmega328pHal::buttonIsUp() { return bit_is_set(PIND, PD3); } + /* * the 'loop' method shall be called each 10ms * @@ -48,7 +50,7 @@ Atmega328pHal::Atmega328pHal() { void Atmega328pHal::setupHeartbeat() { TCCR2A = (1U << WGM21) | (0U << WGM20); // set Timer2 in CTC mode TCCR2B = (1U << CS22) | (1U << CS21) | (1U << CS20); // 1/1024 prescaler - TIMSK2 = (1U << OCIE2A); // enable compare Interrupt + TIMSK2 = (1U << OCIE2A); // enable compare Interrupt ASSR &= ~(1U << AS2); TCNT2 = 0U; @@ -83,7 +85,7 @@ bool Atmega328pHal::isCharging() { } void Atmega328pHal::sleep() { - LowPower.idle(SLEEP_15MS, ADC_OFF, TIMER2_ON, TIMER1_ON, TIMER0_ON, SPI_OFF, + LowPower.idle(SLEEP_15MS, ADC_OFF, TIMER2_ON, TIMER1_OFF, TIMER0_OFF, SPI_OFF, USART0_OFF, TWI_OFF); } @@ -96,7 +98,7 @@ void Atmega328pHal::configureInputCallbacks() { attachInterrupt(digitalPinToInterrupt(BUTTON2_PIN), buttonInterruptHandler, CHANGE); -extern void triggerInterruptHandler(); + extern void triggerInterruptHandler(); pinMode(TRIGGER_PIN, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(TRIGGER_PIN), triggerInterruptHandler, CHANGE); diff --git a/lib/Domain/Gun/Atmega328pHal.hpp b/lib/Cross/Gun/Atmega328pHal.hpp similarity index 92% rename from lib/Domain/Gun/Atmega328pHal.hpp rename to lib/Cross/Gun/Atmega328pHal.hpp index 2fec336..7881bf0 100644 --- a/lib/Domain/Gun/Atmega328pHal.hpp +++ b/lib/Cross/Gun/Atmega328pHal.hpp @@ -39,13 +39,11 @@ class Atmega328pHal : public IGunHal { void laserOff() override; void vibrationOn() override; void vibrationOff() override; - + bool triggerIsUp() override; + bool buttonIsUp() override; uint16_t getBatteryVoltageMv() override; - uint8_t getBatteryVoltagePercent() override; bool isCharging() override; - void configureInputCallbacks() override; - void sleep() override; }; \ No newline at end of file diff --git a/lib/Domain/Gun/SSD1306Ui.hpp b/lib/Cross/Gun/SSD1306Ui.hpp similarity index 96% rename from lib/Domain/Gun/SSD1306Ui.hpp rename to lib/Cross/Gun/SSD1306Ui.hpp index 1de15ca..5783195 100644 --- a/lib/Domain/Gun/SSD1306Ui.hpp +++ b/lib/Cross/Gun/SSD1306Ui.hpp @@ -44,6 +44,7 @@ class SSD1306Ui : public IGunUi { ; // Don't proceed, loop forever } } + void displaySplash(uint16_t timeoutMs) override { display.clearDisplay(); display.setTextColor(WHITE); diff --git a/lib/Domain/Gun/Button.cpp b/lib/Domain/Gun/Button.cpp index 651f8e6..c113686 100644 --- a/lib/Domain/Gun/Button.cpp +++ b/lib/Domain/Gun/Button.cpp @@ -17,5 +17,6 @@ #include #include +void Button::setGun(Gun *gun) { this->gun = gun; } void Button::onShortPress() { gun->shootCount = 0; } void Button::onLongPress() { gun->toggleCalibrationMode(); } \ No newline at end of file diff --git a/lib/Domain/Gun/Button.hpp b/lib/Domain/Gun/Button.hpp index 0782c52..1b6a546 100644 --- a/lib/Domain/Gun/Button.hpp +++ b/lib/Domain/Gun/Button.hpp @@ -28,5 +28,5 @@ class Button : public Contactor { void onShortPress() override; void onLongPress() override; - void setGun(Gun *gun) { gun = gun; } + void setGun(Gun *gun); }; \ No newline at end of file diff --git a/lib/Domain/Gun/Gun.cpp b/lib/Domain/Gun/Gun.cpp index 33b4c3c..6e9e3d3 100644 --- a/lib/Domain/Gun/Gun.cpp +++ b/lib/Domain/Gun/Gun.cpp @@ -16,18 +16,39 @@ */ #include -#define TICKS_BETWEEN_UI_UPDATE 10 +#define TICKS_BETWEEN_BATTERY_UI_UPDATE 100 // counter for ticks between battery display update uint16_t updateBatteryDisplayCycleCount; long now = 0; -void Gun::loop(void) { +Gun::Gun(IGunHal *hal, IGunUi *ui) { + this->hal = hal; + this->ui = ui; + calibrationMode = false; + shootCount = 0; + shootCycleCountdown = 0; + + button.setGun(this); + trigger.setGun(this); +} + +void Gun::activateShoot() { + if (shootCycleCountdown == 0) { + shootCount += 1; + hal->laserOn(); + hal->vibrationOn(); - now += 10; // 10ms per loop thanks to timer2 + // add 1 to allow easy detection of last tick + shootCycleCountdown = Gun::SHOOT_DURATION_TICKS + 1; + } +} + +void Gun::loop(void) { - decreaseShootCycleCount(); + // 10ms per loop thanks to timer2 + now += 10; trigger.processPendingEvent(now); button.processPendingEvent(now); @@ -39,10 +60,15 @@ void Gun::loop(void) { hal->vibrationOff(); } else { - if (shootCycleCountdown == 0) { - hal->laserOff(); - hal->vibrationOff(); - ui->displayShootCount(shootCount); + + if (shootCycleCountdown > 0) { + shootCycleCountdown--; + if (shootCycleCountdown == 1) { + hal->laserOff(); + hal->vibrationOff(); + ui->displayShootCount(shootCount); + shootCycleCountdown--; + } } if (updateBatteryDisplayCycleCount > 0) { @@ -51,7 +77,7 @@ void Gun::loop(void) { if (updateBatteryDisplayCycleCount == 0) { ui->displayBatteryStatus(hal->getBatteryVoltageMv(), hal->getBatteryVoltagePercent()); - updateBatteryDisplayCycleCount = TICKS_BETWEEN_UI_UPDATE; + updateBatteryDisplayCycleCount = TICKS_BETWEEN_BATTERY_UI_UPDATE; } } hal->sleep(); @@ -60,10 +86,10 @@ void Gun::loop(void) { void Gun::setup(void) { shootCount = 0; - updateBatteryDisplayCycleCount = TICKS_BETWEEN_UI_UPDATE; + updateBatteryDisplayCycleCount = TICKS_BETWEEN_BATTERY_UI_UPDATE; ui->displaySplash(2000); - + // mV is not used ui->displayBatteryStatus(0, hal->getBatteryVoltagePercent()); diff --git a/lib/Domain/Gun/Gun.hpp b/lib/Domain/Gun/Gun.hpp index 14831c3..53e61c2 100644 --- a/lib/Domain/Gun/Gun.hpp +++ b/lib/Domain/Gun/Gun.hpp @@ -24,6 +24,8 @@ class Gun { + uint8_t shootCycleCountdown; + public: /* 50 ms */ static const uint8_t SHOOT_DURATION_TICKS = 5; @@ -34,20 +36,10 @@ class Gun { Trigger trigger; bool calibrationMode; uint16_t shootCount; - uint8_t shootCycleCountdown; - Gun(IGunHal *hal, IGunUi *ui) - : hal(hal), ui(ui), calibrationMode(false), shootCount(0), - shootCycleCountdown(0) { - button.setGun(this); - trigger.setGun(this); - } - - void decreaseShootCycleCount() { - if (shootCycleCountdown > 0) { - shootCycleCountdown--; - } - } + Gun(IGunHal *hal, IGunUi *ui); + + void activateShoot(); void toggleCalibrationMode() { calibrationMode = !calibrationMode; } diff --git a/lib/Domain/Gun/IGunHal.hpp b/lib/Domain/Gun/IGunHal.hpp index 78153d9..ebc9bc8 100644 --- a/lib/Domain/Gun/IGunHal.hpp +++ b/lib/Domain/Gun/IGunHal.hpp @@ -31,7 +31,8 @@ class IGunHal { * the 'loop' method shall be called each 10ms */ virtual void setupHeartbeat() = 0; - + virtual bool triggerIsUp() = 0; + virtual bool buttonIsUp() = 0; virtual void laserOn() = 0; virtual void laserOff() = 0; virtual void vibrationOn() = 0; diff --git a/lib/Domain/Gun/Trigger.cpp b/lib/Domain/Gun/Trigger.cpp index 418701f..706625c 100644 --- a/lib/Domain/Gun/Trigger.cpp +++ b/lib/Domain/Gun/Trigger.cpp @@ -14,15 +14,12 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include #include +#include + +void Trigger::setGun(Gun *gun) { this->gun = gun; } void Trigger::onDown(long now) { - if (gun->shootCycleCountdown == 0) { - gun->shootCount += 1; - gun->hal->laserOn(); - gun->hal->vibrationOn(); - gun->shootCycleCountdown = Gun::SHOOT_DURATION_TICKS; - } + gun->activateShoot(); Contactor::onDown(now); } \ No newline at end of file diff --git a/lib/Domain/Gun/Trigger.hpp b/lib/Domain/Gun/Trigger.hpp index b2c2ecf..c669e2f 100644 --- a/lib/Domain/Gun/Trigger.hpp +++ b/lib/Domain/Gun/Trigger.hpp @@ -24,6 +24,6 @@ class Trigger : public Contactor { public: Trigger() : Contactor() {} - void setGun(Gun *gun) { gun = gun; } + void setGun(Gun *gun); void onDown(long now) override; }; \ No newline at end of file diff --git a/src/GunApp.cpp b/src/GunApp.cpp index 3601672..1e75f03 100644 --- a/src/GunApp.cpp +++ b/src/GunApp.cpp @@ -23,6 +23,9 @@ SSD1306Ui ui; Atmega328pHal hal; Gun gun(&hal, &ui); +volatile bool tick = false; +ISR(TIMER2_COMPA_vect) { tick = true; } + volatile Contactor::Event pendingTriggerEvent; void triggerInterruptHandler() { if (bit_is_set(PIND, PD2)) { @@ -43,13 +46,16 @@ void buttonInterruptHandler() { void loop(void) { - // wire interrupt-based events with main code - gun.trigger.pendingEvent = pendingTriggerEvent; - gun.button.pendingEvent = pendingButtonEvent; - pendingTriggerEvent = Contactor::Event::NoEvent; - pendingButtonEvent = Contactor::Event::NoEvent; + if (tick) { + // wire interrupt-based events with main code + gun.trigger.pendingEvent = pendingTriggerEvent; + gun.button.pendingEvent = pendingButtonEvent; + pendingTriggerEvent = Contactor::Event::NoEvent; + pendingButtonEvent = Contactor::Event::NoEvent; - gun.loop(); + gun.loop(); + tick = false; + } } void setup(void) { diff --git a/test/native/test_gun/gun.cpp b/test/native/test_gun/gun.cpp index 3d2d8be..12cb2b0 100644 --- a/test/native/test_gun/gun.cpp +++ b/test/native/test_gun/gun.cpp @@ -15,166 +15,123 @@ * 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(); - void tearDown() {} void setUp() { ArduinoFakeReset(); } -void expect_shot_to_activate_laser_during_short_duration() { - - Mock mock; - MOCK_ALL(); - IGunHal &hal = mock.get(); - Gun gun(hal); - - // first, give 5 shots - gun.onButton2ShortPress(); - - // shot - gun.onButton1ShortPress(); - - Verify(Method(mock, laserOn)).Once(); - Verify(Method(mock, shortDelay)).Once(); - Verify(Method(mock, laserOff)).Once(); -} - -void expect_shot_to_activate_vibration_during_short_duration() { - - Mock mock; - MOCK_ALL(); - IGunHal &hal = mock.get(); - Gun gun(hal); +void expect_gun_to_loop() { - // first, give 5 shots - gun.onButton2ShortPress(); + Mock mockUi; + Mock mockHal; - // shot - gun.onButton1ShortPress(); + 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(5000); + When(Method(mockHal, getBatteryVoltagePercent)).AlwaysReturn(100); + When(Method(mockHal, sleep)).AlwaysReturn(); + When(Method(mockUi, displayBatteryStatus)).AlwaysReturn(); + When(Method(mockUi, displayShootCount)).AlwaysReturn(); - Verify(Method(mock, vibrationOn)).Once(); - Verify(Method(mock, shortDelay)).Once(); - Verify(Method(mock, vibrationOff)).Once(); -} + IGunUi &ui = mockUi.get(); + IGunHal &hal = mockHal.get(); -void expect_short_press_on_button2_to_give_5_shots() { - Mock mock; - 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"); -} + Gun gun(&hal, &ui); -void expect_shot_to_decrement_available_shot_count() { - Mock mock; - 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"); + gun.loop(); } -void expect_shot_to_do_nothing_if_there_is_no_available_shots() { - Mock mock; - 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); +void expect_gun_to_shoot_50ms_on_trigger_down() { + + Mock mockUi; + Mock mockHal; + + 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(5000); + When(Method(mockHal, getBatteryVoltagePercent)).AlwaysReturn(100); + When(Method(mockHal, sleep)).AlwaysReturn(); + When(Method(mockUi, displayBatteryStatus)).AlwaysReturn(); + When(Method(mockUi, displayShootCount)).AlwaysReturn(); + + 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(); + Verify(Method(mockHal, sleep)).Exactly(7); } -void expect_long_press_on_button_two_to_activate_continuous_laser() { - Mock mock; - MOCK_ALL(); - IGunHal &hal = mock.get(); - Gun gun(hal); - - gun.onButton2LongPress(); - - Verify(Method(mock, laserOn)).Once(); - Verify(Method(mock, shortDelay)).Exactly(0); - Verify(Method(mock, laserOff)).Exactly(0); -} - -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.onButton1LongPress(); - - Verify(Method(mock, deepSleep)).Once(); +void expect_ui_to_display_battery_state_each_1s() { + Mock mockUi; + Mock mockHal; + + 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, displaySplash)).AlwaysReturn(); + + IGunUi &ui = mockUi.get(); + IGunHal &hal = mockHal.get(); + + Gun gun(&hal, &ui); + + // displayBatteryStatus first call + gun.setup(); + + for (uint8_t tickCounter = 0; tickCounter < 100; tickCounter++) { + // displayBatteryStatus call after 100 ticks + gun.loop(); + } + + Verify(Method(mockUi, displayBatteryStatus)).Exactly(2); } 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_each_1s); UNITY_END(); return 0; }