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;