diff --git a/Globals.cpp b/Globals.cpp index dca2ff1..6359cde 100644 --- a/Globals.cpp +++ b/Globals.cpp @@ -2,6 +2,7 @@ #include "Globals.h" #include "StateHandler.h" #include "LowPower.h" +#include "EEPROM.h" SegmentDisplay* display; @@ -15,6 +16,13 @@ uint32_t hits = 0; unsigned long currentTime; AbstractStateHandler* currentState; +byte shotCounterOffset; +byte hitCounterOffset; + + +#define counterTreshold (uint32_t)10000 +uint32_t lastShotCounterIteration; +uint32_t lastHitCounterIteration; void setCurrentState(AbstractStateHandler* value) { @@ -36,7 +44,7 @@ void update() // Count the shot no matter which state we're in if (buttonArmed->changed() && !buttonArmed->pressed()) - shots++; + addShot(); } @@ -72,4 +80,89 @@ void sleep() buttonB->update(currentTime); buttonB->block(); } +} + + +#define counterLocation(offset) 2 + (offset * sizeof(uint32_t)) + +void readCounters() +{ + shotCounterOffset = EEPROM.read(0); + hitCounterOffset = EEPROM.read(1); + + EEPROM.get(counterLocation(shotCounterOffset), shots); + EEPROM.get(counterLocation(hitCounterOffset), hits); + + lastShotCounterIteration = shots / counterTreshold; + lastHitCounterIteration = hits / counterTreshold; +} + + +void setShots(uint32_t value) +{ + shots = value; + + uint32_t newShotCounterIteration = shots / counterTreshold; + if (newShotCounterIteration != lastShotCounterIteration || value == 0) + { + shotCounterOffset++; + if (counterLocation(shotCounterOffset) > EEPROM.length() - sizeof(uint32_t)) + { + shotCounterOffset = 0; + if (shotCounterOffset == hitCounterOffset) + shotCounterOffset++; + } + + EEPROM.write(0, shotCounterOffset); + lastShotCounterIteration = newShotCounterIteration; + } + + EEPROM.write(counterLocation(shotCounterOffset), shots); +} + + +void addShot() +{ + setShots(shots + 1); +} + + +void setHits(uint32_t value) +{ + hits = value; + + uint32_t newHitCounterIteration = hits / counterTreshold; + if (newHitCounterIteration != lastHitCounterIteration || value == 0) + { + hitCounterOffset++; + if (counterLocation(hitCounterOffset) > EEPROM.length() - sizeof(uint32_t)) + { + hitCounterOffset = 0; + if (hitCounterOffset == shotCounterOffset) + hitCounterOffset++; + } + + EEPROM.write(1, hitCounterOffset); + lastHitCounterIteration = newHitCounterIteration; + } + + EEPROM.write(counterLocation(hitCounterOffset), hits); +} + + +void addHit() +{ + setHits(hits + 1); +} + + +void resetShots() +{ + setShots(0); +} + + +void resetHits() +{ + setHits(0); } \ No newline at end of file diff --git a/Globals.h b/Globals.h index 9dab6c3..593205d 100644 --- a/Globals.h +++ b/Globals.h @@ -21,4 +21,10 @@ void setCurrentState(AbstractStateHandler* value); void update(); void sleep(); +void readCounters(); +void addShot(); +void addHit(); +void resetShots(); +void resetHits(); + #endif \ No newline at end of file diff --git a/NerfStatTrak.ino b/NerfStatTrak.ino index d13b994..ec436f8 100644 --- a/NerfStatTrak.ino +++ b/NerfStatTrak.ino @@ -20,6 +20,7 @@ void setup() display->setLatchPin(NerfLatchPin); display->setDigits(NerfDigits); display->begin(); + display->clear(); // Configure buttons @@ -33,6 +34,7 @@ void setup() buttonArmed->init(NerfButtonArmed, LOW); + readCounters(); setCurrentState(new DefaultState()); } diff --git a/NerfStatTrakConfig.h b/NerfStatTrakConfig.h index 347ced4..223172d 100644 --- a/NerfStatTrakConfig.h +++ b/NerfStatTrakConfig.h @@ -46,4 +46,11 @@ #define NerfDefaultSleepTime 10000 +/** + * Reset state configuration +**/ +#define NerfResetIntroTime 1000 +#define NerfResetOutroTime 2000 + + #endif \ No newline at end of file diff --git a/StateDefault.cpp b/StateDefault.cpp index f7006c7..f0ac553 100644 --- a/StateDefault.cpp +++ b/StateDefault.cpp @@ -3,6 +3,7 @@ #include "Globals.h" #include "StateLoseTheGame.h" #include "StateUserUnknown.h" +#include "StateReset.h" bool DefaultState::ShowHits = true; @@ -24,10 +25,10 @@ void DefaultState::loop() return; } - if (buttonA->changed() && !buttonA->pressed()) + // A short (triggered on release) + if (buttonA->changed() && !buttonA->pressed() && buttonA->pressedMillis(currentTime) <= NerfButtonLongPressThreshold) { - // A (triggered on release) - hits++; + addHit(); if (!DefaultState::ShowHits) setShowHits(true); @@ -35,6 +36,12 @@ void DefaultState::loop() lastAction = currentTime; } + // A long + if (buttonA->pressed() && buttonA->pressedMillis(currentTime) > NerfButtonLongPressThreshold) + { + setCurrentState(new ResetState()); + } + // B short (triggered on release) if (buttonB->changed() && !buttonB->pressed() && buttonB->pressedMillis(currentTime) <= NerfButtonLongPressThreshold) { diff --git a/StateDefault.h b/StateDefault.h index f330fd5..6506b27 100644 --- a/StateDefault.h +++ b/StateDefault.h @@ -10,7 +10,8 @@ * Start by showing the current counter mode (hits or shots), * then display the value. * - * A: Add hit + * A short: Add hit + * A long: Go to reset state * B short: Toggle between hits and shots and restart state * A + B: Display "Error user unknwn" * B long: Display "You lost the game" animation (losethegame.com - you're welcome!) diff --git a/StateReset.cpp b/StateReset.cpp new file mode 100644 index 0000000..9cfc483 --- /dev/null +++ b/StateReset.cpp @@ -0,0 +1,114 @@ +#include "StateReset.h" +#include "Globals.h" +#include "NerfStatTrakConfig.h" +#include "StateDefault.h" + + +void ResetState::setup() +{ + intro = true; + introStart = currentTime; + + outro = false; + selected = 0; +} + + +void ResetState::loop() +{ + if (intro) + { + if (currentTime - introStart >= NerfResetIntroTime) + intro = false; + + char text[] = "Reset"; + display->writeTextLeft(text); + return; + } + + if (outro) + { + loopOutro(); + return; + } + + + if (buttonA->changed() && !buttonA->pressed()) + { + switch (selected) + { + case 0: + setCurrentState(new DefaultState()); + return; + + case 1: + resetHits(); + break; + + case 2: + resetShots(); + break; + + case 3: + resetHits(); + resetShots(); + break; + } + + outroStart = currentTime; + outro = true; + return; + } + + if (buttonB->changed() && !buttonB->pressed()) + { + selected++; + if (selected > 3) + selected = 0; + } + + + switch (selected) + { + case 0: + { + char text[] = "Cancel"; + display->writeTextLeft(text); + break; + } + + case 1: + { + char text[] = "Hits"; + display->writeTextLeft(text); + break; + } + + case 2: + { + char text[] = "Shots"; + display->writeTextLeft(text); + break; + } + + case 3: + { + char text[] = "Both"; + display->writeTextLeft(text); + break; + } + } +} + + +void ResetState::loopOutro() +{ + if (currentTime - outroStart >= NerfResetOutroTime) + { + setCurrentState(new DefaultState()); + return; + } + + char text[] = "Done"; + display->writeTextLeft(text); +} diff --git a/StateReset.h b/StateReset.h new file mode 100644 index 0000000..9199b6c --- /dev/null +++ b/StateReset.h @@ -0,0 +1,31 @@ +#ifndef StateReset_h +#define StateReset_h + +#include "StateHandler.h" +#include "Arduino.h" + + +/** + * Reset state + * + * Shows a menu providing reset options. A confirms the + * selection, B switches to the next. +**/ +class ResetState : public AbstractStateHandler +{ + private: + bool intro; + unsigned long introStart; + bool outro; + unsigned long outroStart; + byte selected; + + protected: + void loopOutro(); + + public: + void setup(); + void loop(); +}; + +#endif \ No newline at end of file diff --git a/reset/NerfStatTrakReset.ino b/reset/NerfStatTrakReset.ino new file mode 100644 index 0000000..1e3e5c8 --- /dev/null +++ b/reset/NerfStatTrakReset.ino @@ -0,0 +1,51 @@ +#include "EEPROM.h" + +/* + + How the EEPROM is used: + + + First byte: shot counter offset + Second byte: hit counter offset + + Each counter is a 32-bit unsigned integer (4 bytes). When it reaches a treshold + which is < 100,000 (the recommended EEPROM cell write limit) a new offset is calculated. + This way we ensure no cell is written over 100,000 times (assuming the EEPROM is + new) and you can click away until you run out of EEPROM. + + + For initialisation set ShotCounterOffset and HitCounterOffset to + different values. Keep in mind that Offset * 4 must not be greater than + the amount of available EEPROM! + + Shot/HitCounterInitial defines the default value to write for + each counters. + +*/ +#define ShotCounterOffset 0 +#define HitCounterOffset 1 + +#define ShotCounterInitial 0 +#define HitCounterInitial 0 + +#define counterLocation(offset) 2 + (offset * sizeof(uint32_t)) + +void setup() +{ + EEPROM.update(0, ShotCounterOffset); + EEPROM.update(1, HitCounterOffset); + + EEPROM.put(counterLocation(ShotCounterOffset), (uint32_t)ShotCounterInitial); + EEPROM.put(counterLocation(HitCounterOffset), (uint32_t)HitCounterInitial); + + pinMode(LED_BUILTIN, OUTPUT); +} + + +void loop() +{ + digitalWrite(LED_BUILTIN, HIGH); + delay(1000); + digitalWrite(LED_BUILTIN, LOW); + delay(1000); +}