diff --git a/lib/Cross/Gun/SSD1306Ui.hpp b/lib/Cross/Gun/SSD1306Ui.hpp index e0d1b1d..7920620 100644 --- a/lib/Cross/Gun/SSD1306Ui.hpp +++ b/lib/Cross/Gun/SSD1306Ui.hpp @@ -33,6 +33,7 @@ static Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); /** * @brief UI on a SSD1306 OLED display wired over I²C bus * + * Used https://rickkas7.github.io/DisplayGenerator/index.html for prototyping */ class SSD1306Ui : public IGunUi { public: @@ -57,46 +58,49 @@ class SSD1306Ui : public IGunUi { display.display(); delay(timeoutMs); display.clearDisplay(); + display.display(); } void displayBatteryStatus(uint16_t mv, uint8_t percent) override { display.setFont(NULL); - display.fillRect(79, 0, 59, 24, 0); // clear + display.fillRect(100, 0, 25, 9, 0); // clear display.setCursor(100, 2); display.print(percent); display.println("%"); - - // prepare font for next shoot count display - display.setFont(&FreeMonoBold18pt7b); } + void displayChargingStatus(bool isCharging) override { - display.setCursor(80, 15); if (isCharging) { + display.setFont(NULL); + display.setCursor(80, 15); display.println("charging"); } else { - display.println(" "); + display.fillRect(80, 12, 47, 11, 0); // clear } } void displayShootCount(uint16_t count) override { display.fillRect(11, 0, 68, 30, 0); // clear - - if (count > 999) + display.setFont(&FreeMonoBold18pt7b); + if (count > 999) { count = 0; - + } if (count < 10) { display.setCursor(15, 25); } else { display.setCursor(10, 25); } - display.setFont(&FreeMonoBold18pt7b); display.print(count); display.display(); } + void clearCalibration() override { + display.fillRect(2, 10, 126, 14, 0); // clear + } + void displayCalibration() override { - display.fillRect(11, 0, 68, 30, 0); // clear - display.setCursor(15, 25); + display.clearDisplay(); + display.setCursor(2, 25); display.setFont(&FreeMonoOblique9pt7b); display.print("Calibration"); display.display(); diff --git a/lib/Domain/Gun/Button.cpp b/lib/Domain/Gun/Button.cpp index c113686..6db3ad7 100644 --- a/lib/Domain/Gun/Button.cpp +++ b/lib/Domain/Gun/Button.cpp @@ -18,5 +18,5 @@ #include void Button::setGun(Gun *gun) { this->gun = gun; } -void Button::onShortPress() { gun->shootCount = 0; } +void Button::onShortPress() { gun->resetShoots(); } void Button::onLongPress() { gun->toggleCalibrationMode(); } \ No newline at end of file diff --git a/lib/Domain/Gun/Gun.cpp b/lib/Domain/Gun/Gun.cpp index e258046..a9ff4b7 100644 --- a/lib/Domain/Gun/Gun.cpp +++ b/lib/Domain/Gun/Gun.cpp @@ -40,12 +40,19 @@ void Gun::toggleCalibrationMode() { ui->displayCalibration(); } else { hal->laserOff(); + ui->clearCalibration(); ui->displayShootCount(shootCount); + batteryDisplayCycleCountdown = 0; } } +void Gun::resetShoots() { + shootCount = 0; + ui->displayShootCount(shootCount); +} + void Gun::activateShoot() { - if (shootCycleCountdown == 0) { + if (!calibrationMode && shootCycleCountdown == 0) { shootCount += 1; hal->laserOn(); hal->vibrationOn(); @@ -74,6 +81,7 @@ void Gun::loop(void) { shootCycleCountdown--; } } + if (batteryDisplayCycleCountdown > 0) { batteryDisplayCycleCountdown--; } else { @@ -81,7 +89,6 @@ void Gun::loop(void) { hal->getBatteryVoltagePercent()); batteryDisplayCycleCountdown = TICKS_BETWEEN_BATTERY_UI_UPDATE; } - hal->sleep(); } void Gun::setup(void) { diff --git a/lib/Domain/Gun/Gun.hpp b/lib/Domain/Gun/Gun.hpp index b0ff0df..cf1fda3 100644 --- a/lib/Domain/Gun/Gun.hpp +++ b/lib/Domain/Gun/Gun.hpp @@ -41,6 +41,14 @@ class Gun { Gun(IGunHal *hal, IGunUi *ui); + /** + * Reset shoot count and redisplay + */ + void resetShoots(); + + /** + * Activate laser and vibrator + */ void activateShoot(); void toggleCalibrationMode(); diff --git a/lib/Domain/Gun/IGunUi.hpp b/lib/Domain/Gun/IGunUi.hpp index c66510a..3f4a60e 100644 --- a/lib/Domain/Gun/IGunUi.hpp +++ b/lib/Domain/Gun/IGunUi.hpp @@ -27,4 +27,5 @@ class IGunUi { virtual void displayChargingStatus(bool isCharging) = 0; virtual void displayShootCount(uint16_t shootCount) = 0; virtual void displayCalibration() = 0; + virtual void clearCalibration() = 0; }; \ No newline at end of file diff --git a/src/GunApp.cpp b/src/GunApp.cpp index 1e75f03..fa32e66 100644 --- a/src/GunApp.cpp +++ b/src/GunApp.cpp @@ -28,24 +28,32 @@ ISR(TIMER2_COMPA_vect) { tick = true; } volatile Contactor::Event pendingTriggerEvent; void triggerInterruptHandler() { - if (bit_is_set(PIND, PD2)) { + if (hal.triggerIsUp()) { pendingTriggerEvent = Contactor::Event::Released; } else { pendingTriggerEvent = Contactor::Event::Pressed; } + tick = true; } volatile Contactor::Event pendingButtonEvent; void buttonInterruptHandler() { - if (bit_is_set(PIND, PD3)) { + if (hal.buttonIsUp()) { pendingButtonEvent = Contactor::Event::Released; } else { pendingButtonEvent = Contactor::Event::Pressed; } + tick = true; } void loop(void) { + // other interrupt may wakeup the board + // and trigger loop(). Especially counter0 + // and counter1, used internally by the Arduino + // framework. Avoid processing these wakeups as + // applicative events using 'tick' control variable. + if (tick) { // wire interrupt-based events with main code gun.trigger.pendingEvent = pendingTriggerEvent; @@ -55,6 +63,7 @@ void loop(void) { gun.loop(); tick = false; + hal.sleep(); } } @@ -62,5 +71,6 @@ void setup(void) { hal.setGun(&gun); ui.setup(); gun.setup(); + hal.configureInputCallbacks(); hal.setupHeartbeat(); } \ No newline at end of file diff --git a/test/native/test_gun/gun.cpp b/test/native/test_gun/gun.cpp index 6b98ea7..63fa00d 100644 --- a/test/native/test_gun/gun.cpp +++ b/test/native/test_gun/gun.cpp @@ -37,6 +37,7 @@ using namespace fakeit; 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() {} @@ -85,8 +86,8 @@ void expect_gun_to_shoot_50ms_on_trigger_down() { Verify(Method(mockHal, vibrationOn)).Once(); Verify(Method(mockHal, vibrationOff)).Once(); Verify(Method(mockHal, laserOff)).Once(); - Verify(Method(mockHal, sleep)).Exactly(7); } + void expect_ui_to_display_battery_state_at_boot_and_each_1s() { Mock mockUi; Mock mockHal; @@ -137,15 +138,67 @@ void expect_switch_to_maintenance_after_2s_button_continuous_press() { // back to normal Verify(Method(mockHal, laserOff)).Exactly(1); + Verify(Method(mockUi, clearCalibration)).Exactly(1); Verify(Method(mockUi, displayShootCount)).Exactly(1); } +void expect_button_press_to_reset_shoot_count_and_redisplay() { + Mock mockUi; + Mock mockHal; + MOCK_ALL(); + 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_trigger_to_have_no_effect_in_calibration_mode() { + Mock mockUi; + Mock mockHal; + + MOCK_ALL(); + + 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); +} + int main(int, char **) { UNITY_BEGIN(); 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_button_press_to_reset_shoot_count_and_redisplay); + RUN_TEST(expect_trigger_to_have_no_effect_in_calibration_mode); UNITY_END(); return 0;