From ddd609147e2faeac150c30835d7bbb2ac980baf3 Mon Sep 17 00:00:00 2001 From: Aurelien Labrosse Date: Fri, 24 Nov 2023 15:23:01 +0000 Subject: [PATCH] wip --- .github/workflows/main.yml | 10 +- lib/Domain/{ => Contactor}/Contactor.cpp | 2 +- lib/Domain/{ => Contactor}/Contactor.hpp | 0 lib/Domain/Game.cpp | 13 +- lib/Domain/Game.hpp | 6 +- lib/Domain/Gun/Button.hpp | 2 +- lib/Domain/Gun/Gun.hpp | 2 +- lib/Domain/Gun/Trigger.hpp | 2 +- lib/Domain/Target/ITarget.hpp | 44 +++ lib/Domain/Target/ITargetUi.hpp | 36 +++ lib/Domain/Target/LDRTarget.cpp | 92 ++++++ lib/Domain/Target/LDRTarget.hpp | 57 ++++ lib/Domain/{ => TargetHost}/BTEGui.cpp | 25 +- lib/Domain/{ => TargetHost}/BTEGui.hpp | 34 ++- .../ITargetHost.hpp} | 54 ++-- lib/Domain/TargetHost/TargetHost.cpp | 142 +++++++++ lib/Domain/TargetHost/TargetHost.hpp | 62 ++++ lib/Native/TestGui.hpp | 14 +- platformio.ini | 55 ++-- src/ConsoleTargets.cpp | 38 ++- src/TargetApp.cpp | 158 +--------- .../{BTEGui.cpp => test_BTEGui.cpp} | 22 +- test/native/test_LdrTarget/test_LdrTarget.cpp | 37 +++ test/native/test_Targethost/mockTools.hpp | 86 ++++++ .../test_Targethost/test_TargetHost.cpp | 270 ++++++++++++++++++ .../test_contactor/test_Contactor.cpp} | 2 +- .../native/test_gun/{gun.cpp => test_Gun.cpp} | 0 .../{player.cpp => test_Player.cpp} | 0 .../test_game/{game.cpp => test_Game.cpp} | 0 29 files changed, 971 insertions(+), 294 deletions(-) rename lib/Domain/{ => Contactor}/Contactor.cpp (94%) rename lib/Domain/{ => Contactor}/Contactor.hpp (100%) create mode 100644 lib/Domain/Target/ITarget.hpp create mode 100644 lib/Domain/Target/ITargetUi.hpp create mode 100644 lib/Domain/Target/LDRTarget.cpp create mode 100644 lib/Domain/Target/LDRTarget.hpp rename lib/Domain/{ => TargetHost}/BTEGui.cpp (89%) rename lib/Domain/{ => TargetHost}/BTEGui.hpp (74%) rename lib/Domain/{ITargetGui.hpp => TargetHost/ITargetHost.hpp} (54%) create mode 100644 lib/Domain/TargetHost/TargetHost.cpp create mode 100644 lib/Domain/TargetHost/TargetHost.hpp rename test/native/test_BTEGui/{BTEGui.cpp => test_BTEGui.cpp} (88%) create mode 100644 test/native/test_LdrTarget/test_LdrTarget.cpp create mode 100644 test/native/test_Targethost/mockTools.hpp create mode 100644 test/native/test_Targethost/test_TargetHost.cpp rename test/{noarch/test_contactor/contactor.cpp => native/test_contactor/test_Contactor.cpp} (95%) rename test/native/test_gun/{gun.cpp => test_Gun.cpp} (100%) rename test/noarch/test_Player/{player.cpp => test_Player.cpp} (100%) rename test/noarch/test_game/{game.cpp => test_Game.cpp} (100%) 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