diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 3557901..506aeb1 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -23,15 +23,15 @@ jobs:
- name: Test project in native environment
run: pio test -vv -e native
- - name: Build console application
+ - name: Build native console application
run: pio run -e native
- - name: Build 'target' cross application
- run: pio run -e target
-
- - name: Build 'gun' cross application
+ - name: Build cross Gun application
run: pio run -e gun
+ - name: Build cross Target application
+ run: pio run -e target
+
- name: Archive test console application
uses: actions/upload-artifact@v3
with:
diff --git a/lib/Domain/Contactor.cpp b/lib/Domain/Contactor/Contactor.cpp
similarity index 94%
rename from lib/Domain/Contactor.cpp
rename to lib/Domain/Contactor/Contactor.cpp
index a14956d..97b266d 100644
--- a/lib/Domain/Contactor.cpp
+++ b/lib/Domain/Contactor/Contactor.cpp
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-#include
+#include
void Contactor::checkForLongPress(long now) {
if ((downStartTime > 0) && ((now - downStartTime) >= LONG_PRESS)) {
diff --git a/lib/Domain/Contactor.hpp b/lib/Domain/Contactor/Contactor.hpp
similarity index 100%
rename from lib/Domain/Contactor.hpp
rename to lib/Domain/Contactor/Contactor.hpp
diff --git a/lib/Domain/Game.cpp b/lib/Domain/Game.cpp
index 5d51835..407ad75 100644
--- a/lib/Domain/Game.cpp
+++ b/lib/Domain/Game.cpp
@@ -19,15 +19,13 @@
// 5 rounds per player
static const uint8_t TOTAL_ROUNDS = 20;
-Game::Game(ITargetGui *gui) {
- this->gui = gui;
+Game::Game() {
currentRound = 0;
currentPlayer = &players[0];
}
void Game::recordSucceededShoot() {
currentPlayer->recordSucceededShoot();
- gui->displayPlayerInfo(*currentPlayer);
}
/**
@@ -45,9 +43,7 @@ void Game::nextRound() {
nextPlayerId = playerId + 1;
}
currentPlayer = &players[nextPlayerId];
-
currentPlayer->startRound();
- gui->displayPlayerInfo(*currentPlayer);
}
bool Game::isFinished() {
@@ -60,21 +56,14 @@ void Game::changeCurrentPlayerTo(uint8_t playerIndex) {
currentPlayer->endRound();
currentPlayer = &players[playerIndex];
currentPlayer->startRound();
- gui->setCurrentPlayer(playerIndex);
- gui->resetTargets();
- gui->displayPlayerInfo(*currentPlayer);
}
}
void Game::reset() {
- gui->restart();
for (Player &player : players) {
player.reset();
- gui->displayPlayerInfo(player);
}
currentRound = 0;
currentPlayer = &players[0];
currentPlayer->startRound();
- gui->displayPlayerInfo(*currentPlayer);
- gui->setCurrentPlayer(0);
}
\ No newline at end of file
diff --git a/lib/Domain/Game.hpp b/lib/Domain/Game.hpp
index bc8615d..7c13833 100644
--- a/lib/Domain/Game.hpp
+++ b/lib/Domain/Game.hpp
@@ -16,8 +16,8 @@
*/
#pragma once
-#include
#include
+#include
#include
@@ -26,8 +26,6 @@
*/
class Game {
- ITargetGui *gui;
-
public:
static const uint8_t PLAYER_COUNT = 4;
@@ -43,7 +41,7 @@ class Game {
uint8_t currentRound;
Player *currentPlayer;
- explicit Game(ITargetGui *);
+ Game();
void recordSucceededShoot();
diff --git a/lib/Domain/Gun/Button.hpp b/lib/Domain/Gun/Button.hpp
index 1b6a546..47eaa72 100644
--- a/lib/Domain/Gun/Button.hpp
+++ b/lib/Domain/Gun/Button.hpp
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
#pragma once
-#include
+#include
class Gun;
diff --git a/lib/Domain/Gun/Gun.hpp b/lib/Domain/Gun/Gun.hpp
index cf1fda3..750d66c 100644
--- a/lib/Domain/Gun/Gun.hpp
+++ b/lib/Domain/Gun/Gun.hpp
@@ -16,7 +16,7 @@
*/
#pragma once
-#include
+#include
#include
#include
#include
diff --git a/lib/Domain/Gun/Trigger.hpp b/lib/Domain/Gun/Trigger.hpp
index c669e2f..97450c1 100644
--- a/lib/Domain/Gun/Trigger.hpp
+++ b/lib/Domain/Gun/Trigger.hpp
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
#pragma once
-#include
+#include
class Gun;
diff --git a/lib/Domain/Target/ITarget.hpp b/lib/Domain/Target/ITarget.hpp
new file mode 100644
index 0000000..e2a1546
--- /dev/null
+++ b/lib/Domain/Target/ITarget.hpp
@@ -0,0 +1,44 @@
+
+/*
+ *
+ * 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
+
+/**
+ * This interface suggests an implemetation following the state pattern.
+ * `update()` behaviour can change according to current ITarget state.
+ *
+ */
+class ITarget {
+
+public:
+
+ enum State { Activated, Calibrating, Ready, Hit, Error };
+ enum Event { NoEvent, Calibrate, Calibrated, Shoot, Reset };
+
+ virtual ~ITarget() {}
+
+ virtual void post(ITarget::Event event) = 0;
+
+ virtual ITarget::State getState() = 0;
+
+ /**
+ * System heartbeat callback
+ */
+ virtual void update() = 0;
+};
\ No newline at end of file
diff --git a/lib/Domain/Target/ITargetUi.hpp b/lib/Domain/Target/ITargetUi.hpp
new file mode 100644
index 0000000..aaa50b0
--- /dev/null
+++ b/lib/Domain/Target/ITargetUi.hpp
@@ -0,0 +1,36 @@
+/*
+ *
+ * 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 ITargetUi {
+public:
+ virtual ~ITargetUi() {}
+
+ // map ITarget states
+
+ virtual void onHit() = 0;
+ virtual void onCalibrating() = 0;
+ virtual void onReady() = 0;
+ virtual void onError() = 0;
+
+ virtual void log(const char *) = 0;
+
+ virtual void log(uint8_t value) = 0;
+};
\ No newline at end of file
diff --git a/lib/Domain/Target/LDRTarget.cpp b/lib/Domain/Target/LDRTarget.cpp
new file mode 100644
index 0000000..d3a163c
--- /dev/null
+++ b/lib/Domain/Target/LDRTarget.cpp
@@ -0,0 +1,92 @@
+/*
+ *
+ * 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
+#include
+
+LDRTarget::LDRTarget(uint8_t luminosityPin)
+ : ambiantLuminosity(0), luminosityPin(luminosityPin), state(Ready),
+ threshold(0) {
+
+ state = ITarget::State::Activated;
+}
+
+void LDRTarget::post(ITarget::Event event) { nextEvent = event; }
+
+void LDRTarget::update() {
+
+ if (nextEvent != NoEvent) {
+
+ switch (state) {
+
+ case ITarget::State::Activated: {
+ if (nextEvent == ITarget::Event::Calibrate) {
+ onCalibrate();
+ }
+ }
+
+ case ITarget::State::Calibrating: {
+ if (nextEvent == ITarget::Event::Calibrated) {
+ onReady();
+ }
+ }
+
+ case ITarget::State::Ready: {
+ if (nextEvent == ITarget::Event::Shoot) {
+ onShoot();
+ }
+ }
+
+ case ITarget::State::Hit: {
+ if (nextEvent == ITarget::Event::Reset) {
+ onReady();
+ }
+ }
+ }
+
+ if (nextEvent == ITarget::Event::Error) {
+ onError();
+ }
+
+ // consumed or ignored
+ nextEvent = NoEvent;
+ } else {
+ if (getLuminosity() > (ambiantLuminosity + threshold)) {
+ post(ITarget::Event::Shoot);
+ }
+ }
+}
+
+ITarget::State LDRTarget::getState() { return state; }
+
+uint16_t LDRTarget::getLuminosity() { return analogRead(luminosityPin); }
+
+void LDRTarget::onError() { state = ITarget::State::Error; }
+void LDRTarget::onReady() { state = ITarget::State::Ready; }
+void LDRTarget::onShoot() { state = ITarget::State::Hit; }
+
+void LDRTarget::onCalibrate() {
+ state = ITarget::State::Calibrating;
+ getLuminosity();
+ ambiantLuminosity = getLuminosity();
+ ambiantLuminosity += getLuminosity();
+ ambiantLuminosity += getLuminosity();
+ ambiantLuminosity += getLuminosity();
+ ambiantLuminosity /= 4;
+ post(ITarget::Event::Calibrated);
+}
+
+void LDRTarget::setThreshold(uint8_t threshold) { this->threshold = threshold; }
diff --git a/lib/Domain/Target/LDRTarget.hpp b/lib/Domain/Target/LDRTarget.hpp
new file mode 100644
index 0000000..1098a54
--- /dev/null
+++ b/lib/Domain/Target/LDRTarget.hpp
@@ -0,0 +1,57 @@
+
+/*
+ *
+ * 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
+
+// forward declaration
+class TargetHost;
+
+class LDRTarget : public ITarget {
+
+ uint16_t ambiantLuminosity;
+ uint8_t luminosityPin;
+ ITarget::State state;
+ ITarget::Event nextEvent;
+ uint8_t threshold;
+
+ uint16_t getLuminosity();
+ void onReady();
+ void onError();
+ void onShoot();
+ void onCalibrate();
+
+ enum State { All, Activated, Calibrating, Ready, Hit, Failure };
+ enum Event { NoEvent, Calibrate, Calibrated, Shoot, Reset, Error };
+
+public:
+ LDRTarget(uint8_t luminosityPin);
+
+ ITarget::State getState() override;
+
+ void post(ITarget::Event event) override;
+
+ void update() override;
+
+ /**
+ * @brief Set the difference with ambiant luminosity level that triggers a hit
+ * condition
+ *
+ * @param threshold
+ */
+ void setThreshold(uint8_t threshold);
+};
\ No newline at end of file
diff --git a/lib/Domain/BTEGui.cpp b/lib/Domain/TargetHost/BTEGui.cpp
similarity index 89%
rename from lib/Domain/BTEGui.cpp
rename to lib/Domain/TargetHost/BTEGui.cpp
index 57527d5..c56c550 100644
--- a/lib/Domain/BTEGui.cpp
+++ b/lib/Domain/TargetHost/BTEGui.cpp
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-#include
+#include
#if not defined(NATIVE)
#include
@@ -35,18 +35,13 @@ void BTEGui::_output(const char *message) {
#endif
}
-void BTEGui::hitTarget(ITargetGui::TARGET target) {
- targetState |= (1 << target);
+void BTEGui::updateTarget(ITarget* target) {
char letter = TARGET_APP_LETTERS[(uint8_t)target];
sprintf(stringBuffer, "*%cR255G255B255*", letter);
_output(stringBuffer);
}
-bool BTEGui::isTargetHit(ITargetGui::TARGET target) {
- return ((targetState & (1 << target)) == (1 << target));
-}
-
void BTEGui::resetTargets() {
targetState = 0;
for (char letter : TARGET_APP_LETTERS) {
@@ -76,15 +71,23 @@ void BTEGui::displayPlayerInfo(const Player &player) {
_output(stringBuffer);
}
-#if defined(AVR)
+void BTEGui::log(const char *message) {
+ sprintf(stringBuffer, "%s", message);
+ _output(stringBuffer);
+}
+void BTEGui::log(uint8_t value) {
+ sprintf(stringBuffer, "%u", value);
+ _output(stringBuffer);
+}
+
+#if not defined(NATIVE)
static void sendApplication() {
Serial.println(F("*.kwl"));
Serial.println(F("clear_panel()"));
Serial.println(F("set_grid_size(21,10)"));
// current player
- Serial.println(
- F("add_text(4,8,xlarge,L,Current player: , 245, 240, 245,)"));
+ Serial.println(F("add_text(4,8,xlarge,L,Current player: , 245, 240, 245,)"));
Serial.println(F("add_text(10,8,xlarge,C,1,245,240,245,M)"));
// targets
@@ -118,7 +121,7 @@ static void sendApplication() {
Serial.println(F("add_button(19,5,25,N,|)"));
Serial.println(F("add_button(0,8,30,R,|)"));
- Serial.println(F("add_slider(10,9,8,100,1024,500,T ,|,1)"));
+ Serial.println(F("add_slider(10,9,8,1,50,1,T ,|,1)"));
Serial.println(F("add_monitor(18,8,3,,1)"));
Serial.println(F("set_panel_notes(-,,,)"));
diff --git a/lib/Domain/BTEGui.hpp b/lib/Domain/TargetHost/BTEGui.hpp
similarity index 74%
rename from lib/Domain/BTEGui.hpp
rename to lib/Domain/TargetHost/BTEGui.hpp
index e316c08..c24aa8a 100644
--- a/lib/Domain/BTEGui.hpp
+++ b/lib/Domain/TargetHost/BTEGui.hpp
@@ -16,7 +16,9 @@
*/
#pragma once
-#include
+#include
+#include
+
#include
#if not defined(AVR)
@@ -24,10 +26,13 @@
#endif
/**
- * GUI for Bluetooth Electronics interface (Android)
+ * UI for Bluetooth Electronics interface (Android)
+ *
+ * This UI allows to display each target status as well as player current score.
+ * Callbacks methods also allow to reset the UI to initial state.
+ *
*/
-class BTEGui : public ITargetGui {
- uint8_t targetState;
+class BTEGui {
/* Bluetooth application uses a letter to identify which widget
* shall use the received data. Theses are for targets representation
@@ -52,17 +57,18 @@ class BTEGui : public ITargetGui {
void _output(const char *message);
public:
-#if defined(AVR)
- BTEGui() : targetState(0) {}
-#else
+#if !defined(AVR)
std::ostream &out;
- explicit BTEGui(std::ostream &out) : targetState(0), out(out) {}
+ explicit BTEGui(std::ostream &out) : out(out) {}
#endif
- void setCurrentPlayer(uint8_t playerId) override;
- void hitTarget(ITargetGui::TARGET target) override;
- bool isTargetHit(ITargetGui::TARGET target) override;
- void resetTargets() override;
- void displayPlayerInfo(const Player &player) override;
- void restart() override;
+ void setCurrentPlayer(const Player &player);
+
+ void updateTarget(ITarget *target);
+
+ void displayPlayerInfo(const Player &player);
+
+ void restart();
+ void log(const char *);
+ void log(uint8_t value);
};
\ No newline at end of file
diff --git a/lib/Domain/ITargetGui.hpp b/lib/Domain/TargetHost/ITargetHost.hpp
similarity index 54%
rename from lib/Domain/ITargetGui.hpp
rename to lib/Domain/TargetHost/ITargetHost.hpp
index 6439432..e20f90b 100644
--- a/lib/Domain/ITargetGui.hpp
+++ b/lib/Domain/TargetHost/ITargetHost.hpp
@@ -16,46 +16,36 @@
*/
#pragma once
+#include
#include
-#include
-class ITargetGui {
+/**
+ * @brief Target host is a device managing 5 targets
+ *
+ * A target is considered hit if its luminosity value exceed (ambientValue +
+ * threshold) A target has a multicolor led: Off is ready, green is shot, blue
+ * is calibration, red is error
+ */
+
+class ITargetHost {
+
public:
+ virtual ~ITargetHost() {}
- enum TARGET{
- One = 0,
- Two,
- Three,
- Four,
- Five
- };
-
- virtual ~ITargetGui() {}
+ virtual void reset() = 0;
/**
- * Tell GUI a target has been hit
- */
- virtual void hitTarget(TARGET) = 0;
+ * Callback for system clock
+ */
+ virtual void update() = 0;
/**
- * Ask GUI if a target has been hit
- */
- virtual bool isTargetHit(TARGET) = 0;
+ * Notification led on
+ */
+ virtual void ledOn() = 0;
/**
- * Reset target hit status
- */
- virtual void resetTargets() = 0;
-
- virtual void setCurrentPlayer(uint8_t playerId) = 0;
-
- /*
- * Send player info : id, shoots and hit shoots
- *
- *
- */
- virtual void displayPlayerInfo(const Player&) = 0;
-
- virtual void restart() = 0;
-
+ * Notification led off
+ */
+ virtual void ledOff() = 0;
};
\ No newline at end of file
diff --git a/lib/Domain/TargetHost/TargetHost.cpp b/lib/Domain/TargetHost/TargetHost.cpp
new file mode 100644
index 0000000..af9311a
--- /dev/null
+++ b/lib/Domain/TargetHost/TargetHost.cpp
@@ -0,0 +1,142 @@
+/*
+ *
+ * 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
+#include
+#include
+#include
+#include
+
+char serial_command_buffer_[16];
+SerialCommands _serial_commands(&Serial, serial_command_buffer_,
+ sizeof(serial_command_buffer_), "|", " ");
+
+static const uint8_t TRESHOLD_ADDRESS = 0;
+static const uint8_t LED_PIN = 9;
+static const uint8_t TARGET_A_PIN = A0;
+static const uint8_t TARGET_Z_PIN = A1;
+static const uint8_t TARGET_E_PIN = A2;
+static const uint8_t TARGET_R_PIN = A3;
+static const uint8_t TARGET_T_PIN = A4;
+
+static const uint8_t TARGET_COUNT = 5;
+
+LDRTarget _targets[TARGET_COUNT] = {LDRTarget(A0), LDRTarget(A1), LDRTarget(A2),
+ LDRTarget(A3), LDRTarget(A4)};
+
+/**
+ * Fallback method called when garbage command
+ * is received on the serial port
+ */
+static void serial_cmd_unrecognized_callback(SerialCommands *sender,
+ const char *cmd);
+
+static void serial_cmd_setThreshold_callback(SerialCommands *sender);
+static void serial_cmd_changePlayer_callback(SerialCommands *sender);
+static void serial_cmd_reset_callback(SerialCommands *sender);
+
+SerialCommand serial_cmd_setThreshold("T", &serial_cmd_setThreshold_callback);
+SerialCommand serial_cmd_changePlayer("P", &serial_cmd_changePlayer_callback);
+SerialCommand serial_cmd_reset("R", &serial_cmd_reset_callback);
+
+uint8_t TargetHost::getThreshold() {
+ if (thresholdCache == 0) {
+ thresholdCache = EEPROM.read(TRESHOLD_ADDRESS);
+ }
+ return thresholdCache;
+}
+
+void TargetHost::storeThreshold(uint8_t threshold) {
+ thresholdCache = threshold;
+ EEPROM.update(TRESHOLD_ADDRESS, threshold);
+}
+
+void TargetHost::ledOn() { digitalWrite(LED_PIN, HIGH); }
+
+void TargetHost::ledOff() { digitalWrite(LED_PIN, LOW); }
+
+void TargetHost::setup() {
+
+ Serial.begin(115200);
+
+ pinMode(LED_PIN, OUTPUT);
+ ledOff();
+
+ // load value in cache from EEPROM
+ getThreshold();
+
+ for (uint8_t targetIndex = 0; targetIndex < TARGET_COUNT; targetIndex++) {
+ _targets[targetIndex].post(ITarget::Event::Calibrate);
+ }
+
+ _serial_commands.context = this;
+ _serial_commands.SetDefaultHandler(serial_cmd_unrecognized_callback);
+ _serial_commands.AddCommand(&serial_cmd_setThreshold);
+ _serial_commands.AddCommand(&serial_cmd_changePlayer);
+ _serial_commands.AddCommand(&serial_cmd_reset);
+
+ ui->restart();
+}
+
+void TargetHost::update() {
+
+ for (uint8_t targetIndex = 0; targetIndex < TARGET_COUNT; targetIndex++) {
+ _targets[targetIndex].update();
+ }
+ _serial_commands.ReadSerial();
+ delay(5);
+}
+
+void serial_cmd_unrecognized_callback(SerialCommands *sender, const char *cmd) {
+ sender->GetSerial()->print("Unrecognized command [");
+ sender->GetSerial()->print(cmd);
+ sender->GetSerial()->println("]");
+}
+
+void serial_cmd_reset_callback(SerialCommands *sender) {
+ TargetHost *targetHost = static_cast(sender->context);
+ targetHost->game->reset();
+ targetHost->ui->restart();
+}
+
+void serial_cmd_setThreshold_callback(SerialCommands *sender) {
+
+ uint8_t value = atoi(sender->Next());
+ TargetHost *targetHost = static_cast(sender->context);
+
+ if (value > 0 && value != targetHost->getThreshold()) {
+ targetHost->storeThreshold(value);
+ targetHost->ui->log((const char *)F("Thrsh: "));
+ targetHost->ui->log(value);
+ targetHost->ui->log("\n");
+ }
+}
+
+void serial_cmd_changePlayer_callback(SerialCommands *sender) {
+ uint8_t playerId = (uint8_t)atoi(sender->Next());
+ TargetHost *targetHost = static_cast(sender->context);
+ targetHost->game->changeCurrentPlayerTo(playerId);
+}
+
+void TargetHost::reset() { ui->resetTargets(); }
+
+TargetHost::TargetHost(Game *game, BTEGui *ui) {
+
+ this->game = game;
+ this->ui = ui;
+
+ thresholdCache = 0;
+}
\ No newline at end of file
diff --git a/lib/Domain/TargetHost/TargetHost.hpp b/lib/Domain/TargetHost/TargetHost.hpp
new file mode 100644
index 0000000..bc5cc35
--- /dev/null
+++ b/lib/Domain/TargetHost/TargetHost.hpp
@@ -0,0 +1,62 @@
+/*
+ *
+ * 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
+
+/**
+ * Manages a collection of targets, the game logic and a GUI.
+ * Also manage the `threshold`, used to differentiate ambient
+ * luminosity level and 'hit' luminosity level.
+ */
+class TargetHost : public ITargetHost {
+
+ /**
+ * Minimum light value to validate a hit.
+ * Shall be stored in EEPROM to allow a reset
+ * in same luminosity condition without reconfiguring
+ */
+ uint8_t thresholdCache;
+
+public:
+ void storeThreshold(uint8_t threshold);
+
+ /**
+ * Return value of difference needed between ambient light level and hit light
+ * level value [1;254]
+ */
+
+ uint8_t getThreshold();
+
+ Game *game;
+ BTEGui *ui;
+
+ TargetHost(Game *game, BTEGui *ui);
+
+ void ledOn() override;
+ void ledOff() override;
+ void reset() override;
+ void update() override;
+
+ /* Specific methods */
+
+ void setup();
+};
\ No newline at end of file
diff --git a/lib/Native/TestGui.hpp b/lib/Native/TestGui.hpp
index e277d5c..8498c15 100644
--- a/lib/Native/TestGui.hpp
+++ b/lib/Native/TestGui.hpp
@@ -15,24 +15,28 @@
* along with this program. If not, see .
*/
#pragma once
-#include
+#include
#include
-class TestGui : public ITargetGui {
+class TestGui : public ITargetUi {
public:
uint8_t targetState;
TestGui() : targetState(0) {}
- void hitTarget(ITargetGui::TARGET target) override { targetState |= (1 << target); }
+ void hitTarget(ITargetUi::TARGET target) override {
+ targetState |= (1 << target);
+ }
- bool isTargetHit(ITargetGui::TARGET target) override {
- return ( (targetState & (1 << target)) == (1 << target));
+ bool isTargetHit(ITargetUi::TARGET target) {
+ return ((targetState & (1 << target)) == (1 << target));
}
void setCurrentPlayer(uint8_t playerId) override {}
void restart() override {}
void resetTargets() override { targetState = 0; }
void displayPlayerInfo(const Player &player) override {}
+ void log(const char *) override {}
+ void log(uint8_t value) override {}
};
\ No newline at end of file
diff --git a/platformio.ini b/platformio.ini
index 4a04cbc..930f0f3 100755
--- a/platformio.ini
+++ b/platformio.ini
@@ -1,12 +1,9 @@
[env]
lib_compat_mode=off
+lib_ldf_mode=deep
-; these libraries are for AVR and simulation
-; targets only, so a reset of this property
-; is needed in the `native` configuration
-lib_deps =
-
+lib_deps =
#upload_protocol=custom
#upload_flags =
# -Pusb
@@ -18,8 +15,8 @@ lib_deps =
# -cavrisp2
# -cdragon_isp
# -cavrispmkII
+upload_command = avrdude -vv -b57600 -pm328p -c avrisp -Pcom11 $UPLOAD_FLAGS -U flash:w:$SOURCE:i
-upload_command = avrdude -vv -b57600 -pm328p -c avrisp -Pcom9 $UPLOAD_FLAGS -U flash:w:$SOURCE:i
# upload_protocol=custom
# upload_speed=115200
@@ -34,18 +31,24 @@ upload_command = avrdude -vv -b57600 -pm328p -c avrisp -Pcom9 $UPLOAD_FLAGS -U f
# '-b115200'
[env:native]
- lib_deps =
+ lib_deps =
${env.lib_deps}
+ SerialCommands=https://github.com/arcadien/Arduino-SerialCommands
fakeit=https://github.com/FabioBatSilva/ArduinoFake.git
platform=native
- build_type = release
+ build_type = debug
test_ignore = cross/*
- build_flags = -DNATIVE -O0
- debug_test = noarch/test_contactor
+ build_flags =
+ -DNATIVE
+ -std=gnu++17
+ -Og -ggdb3
+ debug_test = native/test_Target
build_src_filter = ${env.src_filter} - -
[env:native_debug]
- lib_deps = ${env.lib_deps}
+ lib_deps =
+ ${env.lib_deps}
+ SerialCommands=https://github.com/arcadien/Arduino-SerialCommands
fakeit=https://github.com/FabioBatSilva/ArduinoFake.git
platform=native
build_type = debug
@@ -54,21 +57,11 @@ upload_command = avrdude -vv -b57600 -pm328p -c avrisp -Pcom9 $UPLOAD_FLAGS -U f
debug_test = native/test_gun
build_src_filter = ${env.src_filter} - -
-[env:cross]
- lib_deps = ${env.lib_deps}
- test_framework = unity
- platform = atmelavr
- framework = arduino
- board = ATmega328P
- build_type = release
- test_ignore = native/*
- build_flags = -O0
- build_src_filter = ${env.src_filter} -
-
- [env:target]
- lib_deps =
+[env:target]
+ lib_deps =
${env.lib_deps}
- SerialCommands
+ SerialCommands=https://github.com/arcadien/Arduino-SerialCommands
+ test_framework = unity
framework = arduino
platform = atmelavr
board = ATmega328P
@@ -84,12 +77,18 @@ upload_command = avrdude -vv -b57600 -pm328p -c avrisp -Pcom9 $UPLOAD_FLAGS -U f
lib_deps =
adafruit/Adafruit SSD1306@^2.5.7
https://github.com/rocketscream/Low-Power#V1.81
+ ${env.lib_deps}
+
framework = arduino
platform = atmelavr
board = ATmega328P
+ board_build.mcu = atmega328p
+ board_build.f_cpu = 16000000L
build_type = release
- build_flags = -DAVR -O2
+ build_flags =
+ -DAVR
+ -Os
+ -std=gnu++17
test_ignore =
native/*
- noarch/*
- build_src_filter = ${env.src_filter} - -
+ build_src_filter = ${env.src_filter} - -
diff --git a/src/ConsoleTargets.cpp b/src/ConsoleTargets.cpp
index 6e6ee73..680f856 100644
--- a/src/ConsoleTargets.cpp
+++ b/src/ConsoleTargets.cpp
@@ -15,11 +15,13 @@
* along with this program. If not, see .
*/
-#include
-#include
#include
#include
+#include
+#include
+#include
+
void printLine() {
std::cout << std::endl
<< "----------------------------------------------------"
@@ -29,35 +31,41 @@ int main() {
std::cout << "Console Application" << std::endl;
BTEGui gui(std::cout);
- Game game(&gui);
+ Game game;
+ TargetHost host(&game, &gui);
printLine();
gui.displayPlayerInfo(*game.currentPlayer);
- std::cout << "Type 'H' if a target is hit" << std::endl;
+ std::cout << "Type 'T' to fire a system tick" << std::endl;
std::cout << "Type 'N' to switch to next player" << std::endl;
+ std::cout << "Type 0,1,2,3 or 4 to hit a target" << std::endl;
+ std::cout << "Type 'R' to reset the target host" << std::endl;
std::cout << "Type 'Q' to quit game" << std::endl;
while (true) {
- std::string str;
+ std::string str;
std::getline(std::cin, str);
- if (str == "H") {
- game.recordSucceededShoot();
-
+ if (str == "T") {
+ host.update();
+
+ } else if (str == "N") {
+ game.nextRound();
+ printLine();
+ std::cout << "Player " << game.currentPlayer->id + 1
+ << " turn! ( global round "
+ << (game.currentRound / Game::PLAYER_COUNT) << ")" << std::endl;
+
+ } else if (str == "R") {
+ host.reset();
+
} else if (str == "Q") {
printLine();
for (auto player : game.players) {
gui.displayPlayerInfo(player);
}
- break;
- } else if (str == "N") {
- game.nextRound();
- printLine();
- std::cout << "Player " << game.currentPlayer->id + 1
- << " turn! ( global round " << (game.currentRound / Game::PLAYER_COUNT) << ")"
- << std::endl;
}
}
}
\ No newline at end of file
diff --git a/src/TargetApp.cpp b/src/TargetApp.cpp
index 18a914e..a812fc5 100644
--- a/src/TargetApp.cpp
+++ b/src/TargetApp.cpp
@@ -15,164 +15,18 @@
* along with this program. If not, see .
*/
-#include
+#include
+#include
#include
-
-#include
-
-#define TARGET_A_PIN A0
-#define TARGET_Z_PIN A1
-#define TARGET_E_PIN A2
-#define TARGET_R_PIN A3
-#define TARGET_T_PIN A4
-
-#define LED_PIN 9
-
-char serial_command_buffer_[32];
-SerialCommands serial_commands_(&Serial, serial_command_buffer_,
- sizeof(serial_command_buffer_), "|", " ");
-void cmd_unrecognized(SerialCommands *sender, const char *cmd);
-void cmd_resetTargets(SerialCommands *sender);
-void cmd_setThreshold(SerialCommands *sender);
-void cmd_nextRound(SerialCommands *sender);
-void cmd_changePlayer(SerialCommands *sender);
-
-SerialCommand cmd_nextRound_("N", &cmd_nextRound);
-SerialCommand cmd_resetTargets_("R", &cmd_resetTargets);
-SerialCommand cmd_setThreshold_("T", &cmd_setThreshold);
-SerialCommand cmd_changePlayer_("P", &cmd_changePlayer);
-
-void serialPrintInfo(uint16_t value);
-void ledOn();
-void ledOff();
-
-// laser detection threshold
-uint16_t threshold;
-
-// ambient light ADC value
-uint16_t reference;
+#include
BTEGui gui;
Game game(&gui);
+TargetHost host(&game, &gui);
void setup() {
-
- pinMode(LED_PIN, OUTPUT);
- ledOff();
-
- pinMode(TARGET_A_PIN, INPUT);
- pinMode(TARGET_Z_PIN, INPUT);
- pinMode(TARGET_E_PIN, INPUT);
- pinMode(TARGET_R_PIN, INPUT);
- pinMode(TARGET_T_PIN, INPUT);
-
- serial_commands_.SetDefaultHandler(cmd_unrecognized);
- serial_commands_.AddCommand(&cmd_resetTargets_);
- serial_commands_.AddCommand(&cmd_setThreshold_);
- serial_commands_.AddCommand(&cmd_nextRound_);
- serial_commands_.AddCommand(&cmd_changePlayer_);
-
- analogRead(TARGET_A_PIN);
- analogRead(TARGET_Z_PIN);
- analogRead(TARGET_E_PIN);
- analogRead(TARGET_R_PIN);
- analogRead(TARGET_T_PIN);
-
- reference = 0;
-
- reference += analogRead(TARGET_A_PIN);
- reference += analogRead(TARGET_Z_PIN);
- reference += analogRead(TARGET_E_PIN);
- reference += analogRead(TARGET_R_PIN);
- reference += analogRead(TARGET_T_PIN);
- reference /= 5;
-
- // initial value sent by GUI at startup
- threshold = 500;
-
- Serial.begin(115200);
-
+ host.setup();
game.reset();
- serial_commands_.ReadSerial();
-}
-
-static void _recordHit() {
- game.recordSucceededShoot();
- ledOn();
- delay(100);
- ledOff();
-}
-
-static void _checkHit(uint16_t value, ITargetGui::TARGET target) {
- if (value > threshold) {
- if (!gui.isTargetHit(target)) {
- gui.hitTarget(target);
- _recordHit();
- }
- }
-}
-
-void loop() {
-
- uint16_t value1 = analogRead(A0);
- uint16_t value2 = analogRead(A1);
- uint16_t value3 = analogRead(A2);
- uint16_t value4 = analogRead(A3);
- uint16_t value5 = analogRead(A4);
-
- _checkHit(value1, ITargetGui::TARGET::One);
- _checkHit(value2, ITargetGui::TARGET::Two);
- _checkHit(value3, ITargetGui::TARGET::Three);
- _checkHit(value4, ITargetGui::TARGET::Four);
- _checkHit(value5, ITargetGui::TARGET::Five);
-
- serial_commands_.ReadSerial();
- delay(5);
-}
-
-void serialPrintInfo(uint16_t value) {
- if (value > 0) {
- Serial.print(F("HV: "));
- Serial.println(value);
- }
-}
-void cmd_unrecognized(SerialCommands *sender, const char *cmd) {
- sender->GetSerial()->print("Unrecognized command [");
- sender->GetSerial()->print(cmd);
- sender->GetSerial()->println("]");
-}
-void cmd_setThreshold(SerialCommands *sender) {
- uint16_t value = atoi(sender->Next());
- if (value > 0 && value != threshold) {
- threshold = value;
- Serial.print(F("VThresh: "));
- Serial.println(threshold);
- Serial.print(F("Vref: "));
- Serial.println(reference);
- }
-}
-void cmd_changePlayer(SerialCommands *sender) {
- uint8_t playerId = (uint8_t)atoi(sender->Next());
- game.changeCurrentPlayerTo(playerId);
-}
-void cmd_resetTargets(SerialCommands *sender) {
-
- (void)sender;
- game.reset();
- serialPrintInfo(0);
- ledOn();
- delay(200);
- ledOff();
- delay(200);
- ledOn();
- delay(200);
- ledOff();
-}
-void cmd_nextRound(SerialCommands *sender) {
- gui.resetTargets();
- game.nextRound();
- gui.setCurrentPlayer(game.currentPlayer->id);
}
-void ledOff() { digitalWrite(LED_PIN, HIGH); }
-void ledOn() { digitalWrite(LED_PIN, LOW); }
\ No newline at end of file
+void loop() { host.update(); }
\ No newline at end of file
diff --git a/test/native/test_BTEGui/BTEGui.cpp b/test/native/test_BTEGui/test_BTEGui.cpp
similarity index 88%
rename from test/native/test_BTEGui/BTEGui.cpp
rename to test/native/test_BTEGui/test_BTEGui.cpp
index 072e834..3281825 100644
--- a/test/native/test_BTEGui/BTEGui.cpp
+++ b/test/native/test_BTEGui/test_BTEGui.cpp
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-#include
+#include
#include
#include
#include
@@ -28,46 +28,46 @@ void expect_target_to_manage_5_targets() {
BTEGui cut(fakeSerial);
std::string actual;
- cut.hitTarget(ITargetGui::TARGET::One);
+ cut.hitTarget(ITargetUi::TARGET::One);
std::string expectedLine = "*AR255G255B255*";
std::getline(fakeSerial, actual);
TEST_ASSERT_EQUAL_STRING_MESSAGE(expectedLine.c_str(), actual.c_str(),
"Hit target shall be white");
- cut.hitTarget(ITargetGui::TARGET::Two);
+ cut.hitTarget(ITargetUi::TARGET::Two);
expectedLine = "*ZR255G255B255*";
std::getline(fakeSerial, actual);
TEST_ASSERT_EQUAL_STRING_MESSAGE(expectedLine.c_str(), actual.c_str(),
"Hit target shall be white");
- cut.hitTarget(ITargetGui::TARGET::Three);
+ cut.hitTarget(ITargetUi::TARGET::Three);
expectedLine = "*ER255G255B255*";
std::getline(fakeSerial, actual);
TEST_ASSERT_EQUAL_STRING_MESSAGE(expectedLine.c_str(), actual.c_str(),
"Hit target shall be white");
- cut.hitTarget(ITargetGui::TARGET::Four);
+ cut.hitTarget(ITargetUi::TARGET::Four);
expectedLine = "*RR255G255B255*";
std::getline(fakeSerial, actual);
TEST_ASSERT_EQUAL_STRING_MESSAGE(expectedLine.c_str(), actual.c_str(),
"Hit target shall be white");
- cut.hitTarget(ITargetGui::TARGET::Five);
+ cut.hitTarget(ITargetUi::TARGET::Five);
expectedLine = "*TR255G255B255*";
std::getline(fakeSerial, actual);
TEST_ASSERT_EQUAL_STRING_MESSAGE(expectedLine.c_str(), actual.c_str(),
"Hit target shall be white");
- TEST_ASSERT_TRUE(cut.isTargetHit(ITargetGui::TARGET::One));
- TEST_ASSERT_TRUE(cut.isTargetHit(ITargetGui::TARGET::Two));
- TEST_ASSERT_TRUE(cut.isTargetHit(ITargetGui::TARGET::Three));
- TEST_ASSERT_TRUE(cut.isTargetHit(ITargetGui::TARGET::Four));
- TEST_ASSERT_TRUE(cut.isTargetHit(ITargetGui::TARGET::Five));
+ TEST_ASSERT_TRUE(cut.isTargetHit(ITargetUi::TARGET::One));
+ TEST_ASSERT_TRUE(cut.isTargetHit(ITargetUi::TARGET::Two));
+ TEST_ASSERT_TRUE(cut.isTargetHit(ITargetUi::TARGET::Three));
+ TEST_ASSERT_TRUE(cut.isTargetHit(ITargetUi::TARGET::Four));
+ TEST_ASSERT_TRUE(cut.isTargetHit(ITargetUi::TARGET::Five));
}
void expect_gui_to_display_player_info() {
diff --git a/test/native/test_LdrTarget/test_LdrTarget.cpp b/test/native/test_LdrTarget/test_LdrTarget.cpp
new file mode 100644
index 0000000..71d58fd
--- /dev/null
+++ b/test/native/test_LdrTarget/test_LdrTarget.cpp
@@ -0,0 +1,37 @@
+/*
+ *
+ * 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 .
+ */
+
+/**
+ *
+ * This test suite is for targetHost implementation with LDRTarget
+ *
+ */
+
+#include
+using namespace fakeit;
+
+#include
+#include
+#include
+
+#include
+
+int main(int, char **) {
+ UNITY_BEGIN();
+ UNITY_END();
+ return 0;
+}
\ No newline at end of file
diff --git a/test/native/test_Targethost/mockTools.hpp b/test/native/test_Targethost/mockTools.hpp
new file mode 100644
index 0000000..2050fda
--- /dev/null
+++ b/test/native/test_Targethost/mockTools.hpp
@@ -0,0 +1,86 @@
+#include
+using namespace fakeit;
+
+#include
+#include
+#include
+
+#include
+
+#define MOCKED_EEPROM_READ \
+ OverloadedMethod(ArduinoFake(EEPROM), read, uint8_t(int))
+
+#define MOCKED_EEPROM_UPDATE \
+ OverloadedMethod(ArduinoFake(EEPROM), update, void(int, uint8_t))
+
+void mockGuiForSetup(Mock &mockGui);
+void mockPrintAndPrinln();
+void mockForCommandTest(Mock &mockGui);
+
+void mockForCommandTest(Mock &mockGui, uint8_t TRESHOLD_ADDRESS) {
+ //
+ // setup, provide a value of 10 for threshold
+ // If serial command interpretation does not work,
+ // threshold value at end of test will be 10.
+ //
+ When(Method(ArduinoFake(), pinMode)).AlwaysReturn();
+ When(MOCKED_EEPROM_READ.Using(TRESHOLD_ADDRESS)).AlwaysReturn(10);
+ When(MOCKED_EEPROM_UPDATE).AlwaysReturn();
+
+ //
+ // blink
+ //
+ When(Method(ArduinoFake(), digitalWrite)).AlwaysReturn();
+ When(Method(ArduinoFake(), delay)).AlwaysReturn();
+ When(Method(ArduinoFake(), analogRead)).AlwaysReturn(1000);
+
+ //
+ // gui notification
+ //
+ mockGuiForSetup(mockGui);
+ When(Method(mockGui, setCurrentPlayer)).AlwaysReturn();
+ When(Method(mockGui, resetTargets)).AlwaysReturn();
+ When(Method(mockGui, displayPlayerInfo)).AlwaysReturn();
+
+ When(OverloadedMethod(ArduinoFake(Serial), begin, void(unsigned long)))
+ .AlwaysReturn();
+
+ When(Method(ArduinoFake(Serial), end)).AlwaysReturn();
+ When(Method(ArduinoFake(Serial), flush)).AlwaysReturn();
+ mockPrintAndPrinln();
+}
+
+void mockGuiForSetup(Mock &mockGui) {
+ When(Method(mockGui, restart)).AlwaysReturn();
+ When(Method(mockGui, displayPlayerInfo)).AlwaysReturn();
+ When(Method(mockGui, setCurrentPlayer)).AlwaysReturn();
+ When(Method(mockGui, resetTargets)).AlwaysReturn();
+ When(Method(mockGui, hitTarget)).AlwaysReturn();
+ When(OverloadedMethod(mockGui, log, void(const char *))).AlwaysReturn();
+ When(OverloadedMethod(mockGui, log, void(uint8_t))).AlwaysReturn();
+}
+
+void mockPrintAndPrinln() {
+
+ // clang-format off
+ When(OverloadedMethod(ArduinoFake(Print), print, size_t(char))).AlwaysReturn();
+ When(OverloadedMethod(ArduinoFake(Print), print, size_t(const char *))).AlwaysReturn();
+ When(OverloadedMethod(ArduinoFake(Print), print, size_t(unsigned char, int))).AlwaysReturn();
+ When(OverloadedMethod(ArduinoFake(Print), print, size_t(int, int))).AlwaysReturn();
+ When(OverloadedMethod(ArduinoFake(Print), print, size_t(long, int))).AlwaysReturn();
+ When(OverloadedMethod(ArduinoFake(Print), print, size_t(double, int))).AlwaysReturn();
+ When(OverloadedMethod(ArduinoFake(Print), print, size_t(unsigned int, int))).AlwaysReturn();
+ When(OverloadedMethod(ArduinoFake(Print), print, size_t(unsigned long, int))).AlwaysReturn();
+
+ When(OverloadedMethod(ArduinoFake(Print), println, size_t())).AlwaysReturn();
+ When(OverloadedMethod(ArduinoFake(Print), println, size_t(char))).AlwaysReturn();
+ When(OverloadedMethod(ArduinoFake(Print), println, size_t(const char *))).AlwaysReturn();
+ When(OverloadedMethod(ArduinoFake(Print), println, size_t(unsigned char, int))).AlwaysReturn();
+ When(OverloadedMethod(ArduinoFake(Print), println, size_t(int, int))).AlwaysReturn();
+ When(OverloadedMethod(ArduinoFake(Print), println, size_t(long, int))).AlwaysReturn();
+ When(OverloadedMethod(ArduinoFake(Print), println, size_t(double, int))).AlwaysReturn();
+ When(OverloadedMethod(ArduinoFake(Print), println, size_t(unsigned int, int))).AlwaysReturn();
+ When(OverloadedMethod(ArduinoFake(Print), println, size_t(unsigned long, int))).AlwaysReturn();
+
+ // clang-format on
+}
\ No newline at end of file
diff --git a/test/native/test_Targethost/test_TargetHost.cpp b/test/native/test_Targethost/test_TargetHost.cpp
new file mode 100644
index 0000000..d4bb5e6
--- /dev/null
+++ b/test/native/test_Targethost/test_TargetHost.cpp
@@ -0,0 +1,270 @@
+/*
+ *
+ * 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 .
+ */
+
+/**
+ *
+ * This test suite is for targetHost implementation with LDRTarget
+ *
+ */
+
+#include
+using namespace fakeit;
+
+#include
+#include
+#include
+
+#include
+
+#include "mockTools.hpp"
+
+void tearDown() {}
+void setUp() { ArduinoFakeReset(); }
+
+static const uint8_t TRESHOLD_ADDRESS = 0;
+
+void expect_threshold_to_be_storable_in_eeprom() {
+
+ // return LSB then MSB
+ When(MOCKED_EEPROM_READ.Using(TRESHOLD_ADDRESS)).Return(20);
+ When(MOCKED_EEPROM_UPDATE).AlwaysReturn();
+
+ Mock mockGui;
+ Game game(&mockGui.get());
+ TargetHost app(&game, &mockGui.get());
+
+ app.storeThreshold(20);
+ TEST_ASSERT_EQUAL(20, app.getThreshold());
+
+ Verify(MOCKED_EEPROM_UPDATE.Using(TRESHOLD_ADDRESS, 20)).Once();
+}
+
+void expect_setup_to_configure_status_led() {
+ Mock mockGui;
+ ITargetUi &ui = mockGui.get();
+ Game game(&ui);
+ TargetHost app(&game, &ui);
+ When(Method(ArduinoFake(), pinMode)).Return();
+ When(Method(ArduinoFake(), analogRead)).AlwaysReturn(0);
+ When(Method(ArduinoFake(), digitalWrite)).AlwaysReturn();
+ When(MOCKED_EEPROM_READ).AlwaysReturn(0);
+ When(OverloadedMethod(ArduinoFake(Serial), begin, void(unsigned long)))
+ .AlwaysReturn();
+
+ mockGuiForSetup(mockGui);
+
+ app.setup();
+
+ static const uint8_t LED_PIN = 9;
+ Verify(Method(ArduinoFake(), pinMode).Using(LED_PIN, OUTPUT)).Once();
+}
+
+void expect_ambient_level_to_be_sampled_at_startup_for_each_target() {
+ Mock mockGui;
+ ITargetUi &ui = mockGui.get();
+ Game game(&ui);
+ TargetHost app(&game, &ui);
+
+ When(Method(ArduinoFake(), pinMode)).Return();
+ When(MOCKED_EEPROM_READ).AlwaysReturn(0);
+ When(OverloadedMethod(ArduinoFake(Serial), begin, void(unsigned long)))
+ .AlwaysReturn();
+
+ When(Method(ArduinoFake(), digitalWrite)).AlwaysReturn();
+ When(Method(ArduinoFake(), analogRead)).AlwaysReturn(1000);
+
+ mockGuiForSetup(mockGui);
+
+ app.setup();
+
+ Verify(Method(ArduinoFake(), pinMode)).AtLeastOnce();
+
+ // Note: 5 reads for calibration
+ Verify(Method(ArduinoFake(), analogRead).Using(A0)).Exactly(5);
+ Verify(Method(ArduinoFake(), analogRead).Using(A1)).Exactly(5);
+ Verify(Method(ArduinoFake(), analogRead).Using(A2)).Exactly(5);
+ Verify(Method(ArduinoFake(), analogRead).Using(A3)).Exactly(5);
+ Verify(Method(ArduinoFake(), analogRead).Using(A4)).Exactly(5);
+}
+
+void expect_all_targets_to_be_checked_during_loop() {
+
+ const uint8_t pins[] = {A0, A1, A2, A3, A4};
+ const uint8_t LED_PIN = 9;
+
+ for (uint8_t targetIndex = 0; targetIndex < 5; targetIndex++) {
+
+ ArduinoFakeReset();
+
+ Mock mockGui;
+ ITargetUi &ui = mockGui.get();
+ Game game(&ui);
+ TargetHost app(&game, &ui);
+
+ // ARRANGE
+
+ // threshold is 100
+ When(MOCKED_EEPROM_READ.Using(TRESHOLD_ADDRESS)).Return(100);
+
+ // and target under test luminosity is 150, other target luminosity is 0
+ When(Method(ArduinoFake(), analogRead)).AlwaysReturn(0);
+
+ // override only for 'analogRead(pins[targetIndex])'
+ When(Method(ArduinoFake(), analogRead).Using(pins[targetIndex]))
+ .AlwaysReturn(150);
+
+ // blink
+ When(Method(ArduinoFake(), digitalWrite)).AlwaysReturn();
+ When(Method(ArduinoFake(), delay)).AlwaysReturn();
+
+ // gui notification
+ When(Method(mockGui, hitTarget)).AlwaysReturn();
+
+ // No serial commands
+ When(Method(ArduinoFake(Serial), available)).AlwaysReturn(0);
+
+ // ACT
+ app.loop();
+
+ // ASSERT
+ // luminosity of all target has been read
+ Verify(Method(ArduinoFake(), analogRead).Using(pins[targetIndex])).Once();
+ Verify(Method(ArduinoFake(), analogRead).Using(Ne(pins[targetIndex])))
+ .Exactly(4_Times);
+ }
+}
+
+void expect_gui_to_be_notified_when_a_target_is_hit() {
+ Mock mockGui;
+ ITargetUi &ui = mockGui.get();
+ Game game(&ui);
+ TargetHost app(&game, &ui);
+
+ // Run initialization and two loop() call.
+ // Ensure GUI is notified *once* when a target is hit
+
+ //
+ // setup, provide a value of 10 for threshold
+ //
+ When(Method(ArduinoFake(), pinMode)).AlwaysReturn();
+ When(MOCKED_EEPROM_READ.Using(TRESHOLD_ADDRESS)).AlwaysReturn(10);
+ When(OverloadedMethod(ArduinoFake(Serial), begin, void(unsigned long)))
+ .AlwaysReturn();
+
+ // Setup GUI mock
+ mockGuiForSetup(mockGui);
+
+ // No serial commands
+ When(Method(ArduinoFake(Serial), available)).AlwaysReturn(0);
+
+ //
+ // analog sampling, twice during setup then once during each loops
+ //
+ When(Method(ArduinoFake(), analogRead)).AlwaysReturn(1000);
+
+ // ambiant at 99, no hit at 100
+ When(Method(ArduinoFake(), analogRead).Using(A0))
+ .Return(0, 99, 99, 99, 99, 100, 100);
+
+ // ambiant at 108, no hit at 100
+ When(Method(ArduinoFake(), analogRead).Using(A1))
+ .Return(0, 108, 108, 108, 108, 100, 100);
+
+ // ambiant at 130, *hit*, then 'already hit'
+ When(Method(ArduinoFake(), analogRead).Using(A2))
+ .Return(0, 130, 130, 130, 130, 141, 141);
+
+ // ambiant at 103, no hit
+ When(Method(ArduinoFake(), analogRead).Using(A3))
+ .Return(0, 103, 103, 103, 103, 103, 103);
+
+ // ambiant at 96, *hit* then no hit
+ When(Method(ArduinoFake(), analogRead).Using(A4))
+ .Return(0, 96, 96, 96, 96, 200, 96);
+
+ app.setup();
+ app.loop();
+ app.loop();
+
+ Verify(Method(ArduinoFake(), pinMode)).AtLeastOnce();
+ Verify(Method(ArduinoFake(), analogRead)).Exactly(35);
+ Verify(Method(mockGui, hitTarget)).Exactly(2_Times);
+}
+
+void expect_threshold_to_be_settable_via_serial_command() {
+ Mock mockGui;
+ ITargetUi &ui = mockGui.get();
+ Game game(&ui);
+ TargetHost app(&game, &ui);
+
+ mockForCommandTest(mockGui, TRESHOLD_ADDRESS);
+
+ //
+ // incoming serial command
+ //
+ When(Method(ArduinoFake(Serial), available)).Return(5, 4, 3, 2, 1, 0);
+ When(Method(ArduinoFake(Serial), read)).Return('T', ' ', '9', '6', '|');
+
+ app.setup();
+ app.loop();
+
+ Verify(Method(ArduinoFake(Serial), read)).Exactly(5_Times);
+
+ static const uint8_t expectedThresholdValue = 96;
+ TEST_ASSERT_EQUAL(expectedThresholdValue, app.getThreshold());
+
+ // check UI feedback
+ Verify(OverloadedMethod(mockGui, log, void(const char *))).Exactly(2_Times);
+ Verify(OverloadedMethod(mockGui, log, void(uint8_t))).Exactly(1_Times);
+ Verify(MOCKED_EEPROM_UPDATE).Once();
+}
+
+void expect_player_to_be_changeable_via_serial_command() {
+ Mock mockGui;
+ Game game(&mockGui.get());
+ TargetHost app(&game, &mockGui.get());
+
+ mockForCommandTest(mockGui, TRESHOLD_ADDRESS);
+
+ //
+ // incoming serial command
+ //
+ When(Method(ArduinoFake(Serial), available)).Return(4, 3, 2, 1, 0);
+ When(Method(ArduinoFake(Serial), read)).Return('P', ' ', '2', '|');
+
+ app.setup();
+ app.loop();
+
+ Verify(Method(ArduinoFake(Serial), read)).Exactly(4_Times);
+
+ static const uint8_t expectedPlayerId = 2;
+ TEST_ASSERT_EQUAL(expectedPlayerId, app.game->currentPlayer->id);
+}
+
+int main(int, char **) {
+ UNITY_BEGIN();
+ RUN_TEST(expect_threshold_to_be_storable_in_eeprom);
+ RUN_TEST(expect_setup_to_configure_status_led);
+ RUN_TEST(expect_ambient_level_to_be_sampled_at_startup_for_each_target);
+ RUN_TEST(expect_all_targets_to_be_checked_during_loop);
+ RUN_TEST(expect_threshold_to_be_settable_via_serial_command);
+ RUN_TEST(expect_player_to_be_changeable_via_serial_command);
+ RUN_TEST(expect_gui_to_be_notified_when_a_target_is_hit);
+
+ UNITY_END();
+ return 0;
+}
\ No newline at end of file
diff --git a/test/noarch/test_contactor/contactor.cpp b/test/native/test_contactor/test_Contactor.cpp
similarity index 95%
rename from test/noarch/test_contactor/contactor.cpp
rename to test/native/test_contactor/test_Contactor.cpp
index 38bc3d5..cecaf45 100644
--- a/test/noarch/test_contactor/contactor.cpp
+++ b/test/native/test_contactor/test_Contactor.cpp
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-#include
+#include
#include
#include
diff --git a/test/native/test_gun/gun.cpp b/test/native/test_gun/test_Gun.cpp
similarity index 100%
rename from test/native/test_gun/gun.cpp
rename to test/native/test_gun/test_Gun.cpp
diff --git a/test/noarch/test_Player/player.cpp b/test/noarch/test_Player/test_Player.cpp
similarity index 100%
rename from test/noarch/test_Player/player.cpp
rename to test/noarch/test_Player/test_Player.cpp
diff --git a/test/noarch/test_game/game.cpp b/test/noarch/test_game/test_Game.cpp
similarity index 100%
rename from test/noarch/test_game/game.cpp
rename to test/noarch/test_game/test_Game.cpp