diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b2d534d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.sublime-workspace +.pioenvs +.piolibdeps \ No newline at end of file diff --git a/Buzzer.cpp b/Buzzer.cpp deleted file mode 100644 index c9f355a..0000000 --- a/Buzzer.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "Buzzer.h" -#include -#include "Config.h" - -void buzzStartup() -{ - tone(PinBuzzer, 1000); - delay(50); - noTone(PinBuzzer); -} - - -void buzzSelect() -{ - tone(PinBuzzer, 1000); - delay(1); - noTone(PinBuzzer); -} - - -void buzzClick() -{ - tone(PinBuzzer, 1000); - delay(25); - noTone(PinBuzzer); -} - - -void buzzCompleted() -{ - for (int i = 0; i < 3; i++) - { - tone(PinBuzzer, 1000); - delay(250); - noTone(PinBuzzer); - - delay(500); - } -} - - -void buzzMemoryCleared() -{ - for (int i = 0; i < 5; i++) - { - tone(PinBuzzer, 1000); - delay(25); - noTone(PinBuzzer); - - delay(250); - } -} - diff --git a/Buzzer.h b/Buzzer.h deleted file mode 100644 index bc42739..0000000 --- a/Buzzer.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef __Buzzer -#define __Buzzer - -void buzzStartup(); -void buzzSelect(); -void buzzClick(); -void buzzCompleted(); -void buzzMemoryCleared(); - -#endif diff --git a/Config.cpp b/Config.cpp deleted file mode 100644 index 77acd5f..0000000 --- a/Config.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "Config.h" - -byte LCDCharArrow[8] = { - B00000, - B01000, - B01100, - B01110, - B01100, - B01000, - B00000, -}; - diff --git a/Config.h b/Config.h deleted file mode 100644 index d69e990..0000000 --- a/Config.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef __Config -#define __Config - -#include "Arduino.h" - - -static const int PinLCDRS = 7; -static const int PinLCDEN = 8; -static const int PinLCDDB4 = 9; -static const int PinLCDDB5 = 10; -static const int PinLCDDB6 = 11; -static const int PinLCDDB7 = 12; -static const int PinEncoderClock = 2; -static const int PinEncoderData = 3; -static const int PinButton = 4; -static const int PinBuzzer = 5; -static const int PinLED = 6; - -// You probably don't wanna change these without a proper review of the code, since most of it assumes 16x2 anyways -static const int LCDWidth = 16; -static const int LCDHeight = 2; - -static const int EncoderSensitivity = 4; -static const int SmallStep = 1; -static const int LargeStepTreshold = 60; -static const int LargeStep = 10; - -static const int MenuTimeout = 2000; - - -extern byte LCDCharArrow[8]; - -#endif diff --git a/ExposureTimer.cpp b/ExposureTimer.cpp deleted file mode 100644 index 06d4160..0000000 --- a/ExposureTimer.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "ExposureTimer.h" -#include - -unsigned int ExposureTime = 0; -unsigned long ExposureTimerStart = 0; - -void ResetExposureTime() -{ - EEPROM.get(0, ExposureTime); -} - - -void StartExposureTimer(unsigned long currentTime) -{ - EEPROM.put(0, ExposureTime); - ExposureTimerStart = currentTime; -} - diff --git a/ExposureTimer.h b/ExposureTimer.h deleted file mode 100644 index c35652d..0000000 --- a/ExposureTimer.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef __ExposureTimer -#define __ExposureTimer - -#include "Arduino.h" - -extern unsigned int ExposureTime; -extern unsigned long ExposureTimerStart; - - -void ResetExposureTime(); -void StartExposureTimer(unsigned long currentTime); - -#endif diff --git a/ScreenCountdown.cpp b/ScreenCountdown.cpp deleted file mode 100644 index 8779439..0000000 --- a/ScreenCountdown.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "ScreenCountdown.h" -#include "ScreenSetTime.h" -#include "ExposureTimer.h" -#include "Config.h" -#include "Buzzer.h" - - -void ScreenCountdown::printRemainingTime() -{ - getDisplay()->setCursor(0, 1); - printTime(ExposureTime - ((getCurrentTime() - ExposureTimerStart) / 1000)); -} - - -void ScreenCountdown::onShow() -{ - mLastDisplayed = -1; - - getDisplay()->setCursor(0, 0); - getDisplay()->print("Exposing... "); - - printRemainingTime(); - digitalWrite(PinLED, HIGH); -} - - -void ScreenCountdown::onHide() -{ - digitalWrite(PinLED, LOW); -} - - -void ScreenCountdown::onButton() -{ - // TODO Confirmation? - buzzClick(); - getScreenManager()->show(); -} - - -void ScreenCountdown::onEncoder(long lastPosition, long newPosition) -{ - // TODO Allow adding / removing time? -} - - -void ScreenCountdown::onTick() -{ - int elapsed = (getCurrentTime() - ExposureTimerStart) / 1000; - - if (elapsed >= ExposureTime) - { - getDisplay()->setCursor(0, 0); - getDisplay()->print("Done! "); - - printRemainingTime(); - digitalWrite(PinLED, LOW); - - buzzCompleted(); - - ExposureTimerStart = 0; - getScreenManager()->show(); - } - else if (elapsed != mLastDisplayed) - { - printRemainingTime(); - mLastDisplayed = elapsed; - } -} - diff --git a/ScreenCountdown.h b/ScreenCountdown.h deleted file mode 100644 index 48cb77b..0000000 --- a/ScreenCountdown.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef __ScreenCountdown -#define __ScreenCountdown - -#include "ScreenManager.h" - -/* - * Countdown screen - * Shows the remaining time. - */ -class ScreenCountdown : public BaseScreen -{ - private: - int mLastDisplayed; - - protected: - void printRemainingTime(); - - public: - ScreenCountdown(ScreenManager* screenManager) : BaseScreen(screenManager) { } - - void onShow(); - void onHide(); - - void onButton(); - void onEncoder(long lastPosition, long newPosition); - void onTick(); -}; - -#endif diff --git a/ScreenManager.cpp b/ScreenManager.cpp deleted file mode 100644 index f637161..0000000 --- a/ScreenManager.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "ScreenManager.h" -#include "Config.h" - - -ScreenManager* BaseScreen::getScreenManager() -{ - return mScreenManager; -} - -unsigned long BaseScreen::getCurrentTime() -{ - return mScreenManager->getCurrentTime(); -} - -LiquidCrystal* BaseScreen::getDisplay() -{ - return mScreenManager->getDisplay(); -} - - -void BaseScreen::printTime(int value) -{ - String minutes = String(value / 60); - String seconds = String(value % 60); - int textLength = minutes.length() + 1 + 2; - - getDisplay()->print(minutes); - getDisplay()->print(":"); - - if (seconds.length() == 1) - getDisplay()->print("0"); - - getDisplay()->print(seconds); - - for (int space = textLength + 1; space < LCDWidth; space++) - getDisplay()->print(" "); -} - diff --git a/ScreenMenu.cpp b/ScreenMenu.cpp deleted file mode 100644 index e677a39..0000000 --- a/ScreenMenu.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include "ScreenMenu.h" -#include "ScreenSetTime.h" -#include "ScreenCountdown.h" -#include "ExposureTimer.h" -#include "Config.h" -#include "Buzzer.h" - - -void ScreenMenu::updateLastActivity() -{ - mLastActivity = getCurrentTime(); -} - - -void ScreenMenu::printExposureTime() -{ - getDisplay()->setCursor(0, 1); - printTime(ExposureTime); -} - - -void ScreenMenu::printMenuCursor() -{ - getDisplay()->setCursor(0, 0); - getDisplay()->write(mSelected == 0 ? (byte)0 : ' '); - - getDisplay()->setCursor(9, 0); - getDisplay()->write(mSelected == 1 ? (byte)0 : ' '); -} - - -void ScreenMenu::onShow() -{ - updateLastActivity(); - mSelected = 0; - - getDisplay()->setCursor(0, 0); - getDisplay()->print(" Start Reset "); - - printMenuCursor(); - printExposureTime(); -} - - -void ScreenMenu::onHide() -{ -} - - -void ScreenMenu::onButton() -{ - buzzClick(); - - switch (mSelected) - { - case 0: - digitalWrite(PinLED, HIGH); - - StartExposureTimer(getCurrentTime()); - getScreenManager()->show(); - break; - - case 1: - ResetExposureTime(); - getScreenManager()->show(); - } -} - - -void ScreenMenu::onEncoder(long lastPosition, long newPosition) -{ - updateLastActivity(); - - if (newPosition > lastPosition) - { - if (mSelected < 1) - { - buzzSelect(); - mSelected++; - printMenuCursor(); - } - } - else - { - if (mSelected > 0) - { - buzzSelect(); - mSelected--; - printMenuCursor(); - } - } -} - - -void ScreenMenu::onTick() -{ - if (getCurrentTime() - mLastActivity >= MenuTimeout) - getScreenManager()->show(); -} - diff --git a/ScreenMenu.h b/ScreenMenu.h deleted file mode 100644 index cbf2764..0000000 --- a/ScreenMenu.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef __ScreenMenu -#define __ScreenMenu - -#include "ScreenManager.h" - -/* - * Menu screen - * Allows starting the timer or resetting the time to the last used value. - */ -class ScreenMenu : public BaseScreen -{ - private: - int mSelected; - unsigned long mLastActivity; - - protected: - void updateLastActivity(); - - void printExposureTime(); - void printMenuCursor(); - - public: - ScreenMenu(ScreenManager* screenManager) : BaseScreen(screenManager) { } - - void onShow(); - void onHide(); - - void onButton(); - void onEncoder(long lastPosition, long newPosition); - void onTick(); -}; - -#endif diff --git a/ScreenSetTime.cpp b/ScreenSetTime.cpp deleted file mode 100644 index e0c3d3c..0000000 --- a/ScreenSetTime.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "ScreenSetTime.h" -#include "ScreenMenu.h" -#include "ExposureTimer.h" -#include "Buzzer.h" -#include "Config.h" - - -void ScreenSetTime::printExposureTime() -{ - getDisplay()->setCursor(0, 1); - printTime(ExposureTime); -} - - -void ScreenSetTime::onShow() -{ - getDisplay()->setCursor(0, 0); - getDisplay()->print("Exposure time: "); - - printExposureTime(); -} - - -void ScreenSetTime::onHide() -{ -} - - -void ScreenSetTime::onButton() -{ - buzzClick(); - getScreenManager()->show(); -} - - -void ScreenSetTime::onEncoder(long lastPosition, long newPosition) -{ - buzzSelect(); - - if (newPosition > lastPosition) - ExposureTime += ExposureTime >= LargeStepTreshold ? LargeStep : SmallStep; - else if (ExposureTime > 0) - ExposureTime -= ExposureTime > LargeStepTreshold ? LargeStep : SmallStep; - - printExposureTime(); -} - - -void ScreenSetTime::onTick() -{ -} - diff --git a/ScreenSetTime.h b/ScreenSetTime.h deleted file mode 100644 index fca59e9..0000000 --- a/ScreenSetTime.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef __ScreenSetTime -#define __ScreenSetTime - -#include "ScreenManager.h" - -/* - * Time screen - * Allows changing of the exposure time. Pressing the button will switch to the menu. - */ -class ScreenSetTime : public BaseScreen -{ - protected: - void printExposureTime(); - - public: - ScreenSetTime(ScreenManager* screenManager) : BaseScreen(screenManager) { } - - void onShow(); - void onHide(); - - void onButton(); - void onEncoder(long lastPosition, long newPosition); - void onTick(); -}; - -#endif diff --git a/UVControl.sublime-project b/UVControl.sublime-project new file mode 100644 index 0000000..b01586b --- /dev/null +++ b/UVControl.sublime-project @@ -0,0 +1,9 @@ +{ + "folders": + [ + { + "path": ".", + "file_exclude_patterns": ["*.sublime-project"] + } + ] +} \ No newline at end of file diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..c2fb43d --- /dev/null +++ b/build.ps1 @@ -0,0 +1 @@ +& platformio run \ No newline at end of file diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..3ed92f3 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,24 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; http://docs.platformio.org/page/projectconf.html + +[env:attiny85] +platform = atmelavr +board = pro16MHzatmega328 +framework = arduino + +;upload_protocol = stk500v1 +;upload_flags = -P$UPLOAD_PORT -b$UPLOAD_SPEED +;upload_speed = 19200 + +board_f_cpu = 16000000L + +lib_deps = + Bounce2 + Encoder diff --git a/src/Buzzer.cpp b/src/Buzzer.cpp new file mode 100644 index 0000000..59441e3 --- /dev/null +++ b/src/Buzzer.cpp @@ -0,0 +1,57 @@ +#include "buzzer.h" +#include +#include "config.h" +#include "state.h" + + +void Buzzer::playNote(uint16_t frequency, uint16_t duration) +{ + tone(PinBuzzer, frequency); + delay(duration); + noTone(PinBuzzer); +} + + +void Buzzer::startup() +{ + if (GetBuzzer() == BuzzerSetting::CompletedButtonStartup) + playNote(1000, 50); +} + + +void Buzzer::select() +{ + if (GetBuzzer() <= BuzzerSetting::CompletedButton) + playNote(1000, 1); +} + + +void Buzzer::click() +{ + if (GetBuzzer() <= BuzzerSetting::CompletedButton) + playNote(1000, 25); +} + + +void Buzzer::completed() +{ + if (GetBuzzer() <= BuzzerSetting::Completed) + { + for (int i = 0; i < 3; i++) + { + playNote(1000, 250); + delay(500); + } + } +} + + +void Buzzer::memoryCleared() +{ + for (int i = 0; i < 5; i++) + { + playNote(1000, 25); + delay(250); + } +} + diff --git a/src/Buzzer.h b/src/Buzzer.h new file mode 100644 index 0000000..fdc2127 --- /dev/null +++ b/src/Buzzer.h @@ -0,0 +1,19 @@ +#ifndef __buzzer +#define __buzzer + +#include + +class Buzzer +{ + protected: + static void playNote(uint16_t frequency, uint16_t duration); + + public: + static void startup(); + static void select(); + static void click(); + static void completed(); + static void memoryCleared(); +}; + +#endif diff --git a/src/Config.cpp b/src/Config.cpp new file mode 100644 index 0000000..8ab1b4a --- /dev/null +++ b/src/Config.cpp @@ -0,0 +1,71 @@ +#include "Config.h" + +uint8_t LCDCharArrowRightMap[8] = { + B00000, + B01000, + B01100, + B01110, + B01100, + B01000, + B00000, +}; + +uint8_t LCDCharArrowLeftMap[8] = { + B00000, + B00100, + B01100, + B11100, + B01100, + B00100, + B00000, +}; + +uint8_t LCDCharArrowRightHollowMap[8] = { + B00000, + B01000, + B00100, + B00010, + B00100, + B01000, + B00000, +}; + +uint8_t LCDCharArrowLeftHollowMap[8] = { + B00000, + B00100, + B01000, + B10000, + B01000, + B00100, + B00000, +}; + +uint8_t LCDCharUpDownMap[8] = { + B00000, + B00100, + B01110, + B00000, + B01110, + B00100, + B00000, +}; + +uint8_t LCDCharUpMap[8] = { + B00000, + B00100, + B01110, + B00000, + B00000, + B00000, + B00000, +}; + +uint8_t LCDCharDownMap[8] = { + B00000, + B00000, + B00000, + B00000, + B01110, + B00100, + B00000, +}; diff --git a/src/Config.h b/src/Config.h new file mode 100644 index 0000000..864e056 --- /dev/null +++ b/src/Config.h @@ -0,0 +1,48 @@ +#ifndef __Config +#define __Config + +#include + +const uint8_t PinLCDRS = 7; +const uint8_t PinLCDEN = 8; +const uint8_t PinLCDDB4 = 9; +const uint8_t PinLCDDB5 = 10; +const uint8_t PinLCDDB6 = 11; +const uint8_t PinLCDDB7 = 12; +const uint8_t PinEncoderClock = 2; +const uint8_t PinEncoderData = 3; +const uint8_t PinButton = 4; +const uint8_t PinBuzzer = 5; +const uint8_t PinLED = 6; + +// Note: an LCD size of at least 16x2 is assumed for all text to fit +const uint8_t LCDWidth = 16; +const uint8_t LCDHeight = 2; + +const uint8_t EncoderSensitivity = 4; +const uint8_t SmallStep = 1; +const uint8_t LargeStepTreshold = 60; +const uint8_t LargeStep = 10; +const uint8_t IntensityStep = 5; + + +const uint32_t DefaultExposureTime = 60; +const uint8_t DefaultExposureIntensity = 100; + +const uint8_t LCDCharArrowRight = 0; +const uint8_t LCDCharArrowLeft = 1; +const uint8_t LCDCharArrowRightHollow = 2; +const uint8_t LCDCharArrowLeftHollow = 3; +const uint8_t LCDCharUpDown = 4; +const uint8_t LCDCharUp = 5; +const uint8_t LCDCharDown = 6; + +extern uint8_t LCDCharArrowRightMap[8]; +extern uint8_t LCDCharArrowLeftMap[8]; +extern uint8_t LCDCharArrowRightHollowMap[8]; +extern uint8_t LCDCharArrowLeftHollowMap[8]; +extern uint8_t LCDCharUpDownMap[8]; +extern uint8_t LCDCharUpMap[8]; +extern uint8_t LCDCharDownMap[8]; + +#endif diff --git a/src/display.cpp b/src/display.cpp new file mode 100644 index 0000000..a677338 --- /dev/null +++ b/src/display.cpp @@ -0,0 +1,153 @@ +#include "display.h" +#include "config.h" + + +void LCDPrintLine(LiquidCrystal* display, uint8_t y, const char* value, uint8_t margin) +{ + display->setCursor(margin, y); + + uint8_t width = LCDWidth - (2 * margin); + + if (value != NULL) + { + uint8_t length = strlen(value); + + if (length >= width) + { + char* character = (char*)value; + for (uint8_t i = 0; i < width; i++) + { + display->write(byte(*character)); + character++; + } + } + else + { + display->print(value); + width -= length; + + while (width > 0) + { + display->write(' '); + width--; + } + } + } + else + { + for (uint8_t i = 0; i < width; i++) + display->write(' '); + } +} + + +void LCDPrintLineCentered(LiquidCrystal* display, uint8_t y, const char* value, uint8_t margin) +{ + display->setCursor(margin, y); + + uint8_t width = LCDWidth - (2 * margin); + + if (value != NULL) + { + uint8_t length = strlen(value); + + if (length >= width) + { + char* character = (char*)value; + for (uint8_t i = 0; i < width; i++) + { + display->write(byte(*character)); + character++; + } + } + else + { + uint8_t offset = (width - length) / 2; + width -= offset; + + for (uint8_t i = 0; i < offset; i++) + display->write(' '); + + display->print(value); + width -= length; + + while (width > 0) + { + display->write(' '); + width--; + } + } + } + else + { + for (uint8_t i = 0; i < width; i++) + display->write(' '); + } +} + + +const char* UniqueString(const char* value) +{ + char* result = new char[strlen(value) + 1]; + return strcpy(result, value); +} + + +#define ASCII0 0x30 + + +const char* FormatTime(uint16_t time) +{ + uint16_t minutes = time / 60; + uint8_t seconds = time % 60; + + char* value = new char[9]; + itoa(minutes, value, 10); + + uint8_t length = strlen(value); + value[length] = ':'; length++; + + value[length] = (seconds / 10) + ASCII0; length++; + value[length] = (seconds % 10) + ASCII0; length++; + value[length] = 0; + + return value; +} + + +const char* FormatPercentage(uint8_t percentage) +{ + char* value = new char[6]; + + + itoa(percentage, value, 10); + + uint8_t length = strlen(value); + value[length] = '%'; + value[length + 1] = 0; + + return value; +} + + +const char* FormatPercentageFixedWidth(uint8_t percentage) +{ + char* value = new char[5]; + + if (percentage > 99) + { + value[0] = '1'; + value[1] = '0'; + } + else + { + value[0] = ' '; + value[1] = percentage > 9 ? (percentage / 10) + ASCII0 : ' '; + } + + value[2] = (percentage % 10) + ASCII0; + value[3] = '%'; + value[4] = 0; + + return value; +} diff --git a/src/display.h b/src/display.h new file mode 100644 index 0000000..ca07d92 --- /dev/null +++ b/src/display.h @@ -0,0 +1,16 @@ +#ifndef __display +#define __display + +#include +#include + +void LCDPrintLine(LiquidCrystal* display, uint8_t y, const char* value, uint8_t margin = 0); +void LCDPrintLineCentered(LiquidCrystal* display, uint8_t y, const char* value, uint8_t margin = 0); + +const char* UniqueString(const char* value); + +const char* FormatTime(uint16_t time); +const char* FormatPercentage(uint8_t percentage); +const char* FormatPercentageFixedWidth(uint8_t percentage); + +#endif \ No newline at end of file diff --git a/UVControl.ino b/src/main.cpp similarity index 52% rename from UVControl.ino rename to src/main.cpp index a98b0d5..cdf68ff 100644 --- a/UVControl.ino +++ b/src/main.cpp @@ -1,24 +1,24 @@ +#include #include #include #include -#include -#include "Config.h" -#include "ScreenManager.h" -#include "ScreenSetTime.h" -#include "Buzzer.h" -#include "ExposureTimer.h" +#include "config.h" +#include "screen.h" +#include "screen/menu.h" +#include "buzzer.h" +#include "state.h" LiquidCrystal lcd(PinLCDRS, PinLCDEN, PinLCDDB4, PinLCDDB5, PinLCDDB6, PinLCDDB7); -// Before uploading the sketch, upload it once with ClearEEPROM defined to -// zero out the memory. -//#define ClearEEPROM -#ifndef ClearEEPROM +// Before uploading the sketch, upload it once with ResetEEPROM defined to +// write the default values to the EEPROM +//#define ResetEEPROM +#ifndef ResetEEPROM ScreenManager* screenManager; -unsigned long currentTime; +uint32_t currentTime; Encoder encoder(PinEncoderData, PinEncoderClock); Bounce button = Bounce(); @@ -34,19 +34,25 @@ void setup() button.attach(PinButton); button.interval(5); - ResetExposureTime(); + LoadSettings(); - lcd.createChar(0, LCDCharArrow); + lcd.createChar(LCDCharArrowRight, LCDCharArrowRightMap); + lcd.createChar(LCDCharArrowLeft, LCDCharArrowLeftMap); + lcd.createChar(LCDCharArrowRightHollow, LCDCharArrowRightHollowMap); + lcd.createChar(LCDCharArrowLeftHollow, LCDCharArrowLeftHollowMap); + lcd.createChar(LCDCharUpDown, LCDCharUpDownMap); + lcd.createChar(LCDCharUp, LCDCharUpMap); + lcd.createChar(LCDCharDown, LCDCharDownMap); lcd.begin(LCDWidth, LCDHeight); screenManager = new ScreenManager(&lcd, ¤tTime); - screenManager->show(); + screenManager->show(); - buzzStartup(); + Buzzer::startup(); } -long lastPosition = 0; +int32_t lastPosition = 0; bool isPressed = false; @@ -54,11 +60,11 @@ void loop() { currentTime = millis(); button.update(); - - long newPosition = encoder.read(); + + int32_t newPosition = encoder.read(); if (abs(newPosition - lastPosition) >= EncoderSensitivity) { - screenManager->getCurrent()->onEncoder(lastPosition, newPosition); + screenManager->getCurrent()->onEncoder(lastPosition, newPosition); lastPosition = newPosition; } @@ -78,23 +84,31 @@ void loop() #else +#include + void setup() { pinMode(PinBuzzer, OUTPUT); lcd.begin(LCDWidth, LCDHeight); - for (int i = 0 ; i < EEPROM.length() ; i++) + for (uint16_t i = 0 ; i < EEPROM.length() ; i++) { - EEPROM.write(i, 0); + EEPROM.update(i, 0); } + SetExposureTime(DefaultExposureTime); + SetExposureIntensity(DefaultExposureIntensity); + SetBuzzer(BuzzerSetting::CompletedButtonStartup); + SaveSettings(); + lcd.setCursor(0, 0); lcd.print("Memory cleared"); - buzzMemoryCleared(); + Buzzer::memoryCleared(); } void loop() { } + #endif diff --git a/src/menu/intensity.cpp b/src/menu/intensity.cpp new file mode 100644 index 0000000..224c823 --- /dev/null +++ b/src/menu/intensity.cpp @@ -0,0 +1,41 @@ +#include "intensity.h" +#include "config.h" +#include "state.h" +#include "display.h" +#include "buzzer.h" + + +const char* IntensityMenuItem::getTitle() +{ + return UniqueString("Intensity"); +} + + +const char* IntensityMenuItem::getValue() +{ + return FormatPercentageFixedWidth(GetExposureIntensity()); +} + + +bool IntensityMenuItem::canIncrement() +{ + return GetExposureIntensity() < 100; +} + + +bool IntensityMenuItem::canDecrement() +{ + return GetExposureIntensity() > IntensityStep; +} + + +void IntensityMenuItem::incrementValue() +{ + SetExposureIntensity(GetExposureIntensity() + IntensityStep); +} + + +void IntensityMenuItem::decrementValue() +{ + SetExposureIntensity(GetExposureIntensity() - IntensityStep); +} \ No newline at end of file diff --git a/src/menu/intensity.h b/src/menu/intensity.h new file mode 100644 index 0000000..132d73b --- /dev/null +++ b/src/menu/intensity.h @@ -0,0 +1,22 @@ +#ifndef __intensitymenuitem +#define __intensitymenuitem + +#include "screen/menu.h" + +class IntensityMenuItem : public MenuItem +{ + public: + IntensityMenuItem() : MenuItem() { } + + const char* getTitle(); + const char* getValue(); + + bool editable() { return true; } + + bool canIncrement(); + bool canDecrement(); + void incrementValue(); + void decrementValue(); +}; + +#endif \ No newline at end of file diff --git a/src/menu/sound.cpp b/src/menu/sound.cpp new file mode 100644 index 0000000..83f4262 --- /dev/null +++ b/src/menu/sound.cpp @@ -0,0 +1,56 @@ +#include "sound.h" +#include "config.h" +#include "state.h" +#include "display.h" +#include "buzzer.h" + + +const char* SoundMenuItem::getTitle() +{ + return UniqueString("Sound"); +} + + +const char* SoundMenuItem::getValue() +{ + switch (GetBuzzer()) + { + case BuzzerSetting::CompletedButtonStartup: + return UniqueString("All"); + + case BuzzerSetting::CompletedButton: + return UniqueString("Alarm/button"); + + case BuzzerSetting::Completed: + return UniqueString("Alarm only"); + + case BuzzerSetting::None: + return UniqueString("None"); + } + + return NULL; +} + + +bool SoundMenuItem::canIncrement() +{ + return GetBuzzer() > BuzzerSetting::First; +} + + +bool SoundMenuItem::canDecrement() +{ + return GetBuzzer() < BuzzerSetting::Last; +} + + +void SoundMenuItem::incrementValue() +{ + SetBuzzer((BuzzerSetting)((uint8_t)GetBuzzer() - 1)); +} + + +void SoundMenuItem::decrementValue() +{ + SetBuzzer((BuzzerSetting)((uint8_t)GetBuzzer() + 1)); +} \ No newline at end of file diff --git a/src/menu/sound.h b/src/menu/sound.h new file mode 100644 index 0000000..65b1203 --- /dev/null +++ b/src/menu/sound.h @@ -0,0 +1,22 @@ +#ifndef __soundmenuitem +#define __soundmenuitem + +#include "screen/menu.h" + +class SoundMenuItem : public MenuItem +{ + public: + SoundMenuItem() : MenuItem() { } + + const char* getTitle(); + const char* getValue(); + + bool editable() { return true; } + + bool canIncrement(); + bool canDecrement(); + void incrementValue(); + void decrementValue(); +}; + +#endif \ No newline at end of file diff --git a/src/menu/start.cpp b/src/menu/start.cpp new file mode 100644 index 0000000..6c038ec --- /dev/null +++ b/src/menu/start.cpp @@ -0,0 +1,42 @@ +#include "start.h" +#include "state.h" +#include "display.h" +#include "screen/countdown.h" + +const char* StartMenuItem::getTitle() +{ + return UniqueString("Start"); +} + + +const char* StartMenuItem::getValue() +{ + const char* time = FormatTime(GetExposureTime()); + const char* intensity = FormatPercentage(GetExposureIntensity()); + + uint8_t timeLength = strlen(time); + uint8_t intensityLength = strlen(intensity); + + char* value = new char[timeLength + 3 + intensityLength + 1]; + + strcpy(value, time); + delete[] time; + + uint8_t offset = timeLength; + + value[offset] = ' '; offset++; + value[offset] = '@'; offset++; + value[offset] = ' '; offset++; + + strcpy(value + offset, intensity); + delete[] intensity; + + return value; +} + + +void StartMenuItem::execute(ScreenManager* screenManager, uint32_t currentTime) +{ + StartExposureTimer(currentTime); + screenManager->show(); +} diff --git a/src/menu/start.h b/src/menu/start.h new file mode 100644 index 0000000..7b5a6c6 --- /dev/null +++ b/src/menu/start.h @@ -0,0 +1,19 @@ +#ifndef __startmenuitem +#define __startmenuitem + +#include "screen/menu.h" + +class StartMenuItem : public MenuItem +{ + public: + StartMenuItem() : MenuItem() { } + + const char* getTitle(); + const char* getValue(); + + bool editable() { return false; } + + void execute(ScreenManager* screenManager, uint32_t currentTime); +}; + +#endif \ No newline at end of file diff --git a/src/menu/time.cpp b/src/menu/time.cpp new file mode 100644 index 0000000..6fd0c8b --- /dev/null +++ b/src/menu/time.cpp @@ -0,0 +1,42 @@ +#include "time.h" +#include "config.h" +#include "state.h" +#include "display.h" +#include "buzzer.h" + +const char* TimeMenuItem::getTitle() +{ + return UniqueString("Time"); +} + + +const char* TimeMenuItem::getValue() +{ + return FormatTime(GetExposureTime()); +} + + +bool TimeMenuItem::canIncrement() +{ + return GetExposureTime() < (uint16_t)-1; +} + + +bool TimeMenuItem::canDecrement() +{ + return GetExposureTime() > SmallStep; +} + + +void TimeMenuItem::incrementValue() +{ + uint16_t exposureTime = GetExposureTime(); + SetExposureTime(exposureTime + (exposureTime >= LargeStepTreshold ? LargeStep : SmallStep)); +} + + +void TimeMenuItem::decrementValue() +{ + uint16_t exposureTime = GetExposureTime(); + SetExposureTime(exposureTime - (exposureTime > LargeStepTreshold ? LargeStep : SmallStep)); +} \ No newline at end of file diff --git a/src/menu/time.h b/src/menu/time.h new file mode 100644 index 0000000..265bf5e --- /dev/null +++ b/src/menu/time.h @@ -0,0 +1,22 @@ +#ifndef __timemenuitem +#define __timemenuitem + +#include "screen/menu.h" + +class TimeMenuItem : public MenuItem +{ + public: + TimeMenuItem() : MenuItem() { } + + const char* getTitle(); + const char* getValue(); + + bool editable() { return true; } + + bool canIncrement(); + bool canDecrement(); + void incrementValue(); + void decrementValue(); +}; + +#endif \ No newline at end of file diff --git a/src/screen.cpp b/src/screen.cpp new file mode 100644 index 0000000..9014894 --- /dev/null +++ b/src/screen.cpp @@ -0,0 +1,19 @@ +#include "screen.h" +#include "config.h" + + +ScreenManager* BaseScreen::getScreenManager() +{ + return mScreenManager; +} + +uint32_t BaseScreen::getCurrentTime() +{ + return mScreenManager->getCurrentTime(); +} + +LiquidCrystal* BaseScreen::getDisplay() +{ + return mScreenManager->getDisplay(); +} + diff --git a/ScreenManager.h b/src/screen.h similarity index 64% rename from ScreenManager.h rename to src/screen.h index d0dce59..921b85b 100644 --- a/ScreenManager.h +++ b/src/screen.h @@ -1,9 +1,8 @@ -#ifndef __ScreenManager -#define __ScreenManager +#ifndef __screen +#define __screen #include - class ScreenManager; @@ -13,24 +12,23 @@ class BaseScreen ScreenManager* mScreenManager; protected: - ScreenManager* getScreenManager(); - unsigned long getCurrentTime(); + ScreenManager* getScreenManager(); + uint32_t getCurrentTime(); LiquidCrystal* getDisplay(); - - void printTime(int value); - public: BaseScreen(ScreenManager* screenManager) { mScreenManager = screenManager; } + virtual ~BaseScreen() {} + virtual void onShow() = 0; virtual void onHide() = 0; - + virtual void onButton() = 0; - virtual void onEncoder(long lastPosition, long newPosition) = 0; + virtual void onEncoder(int32_t lastPosition, int32_t newPosition) = 0; virtual void onTick() = 0; }; @@ -40,25 +38,25 @@ class ScreenManager { private: LiquidCrystal* mDisplay; - unsigned long* mCurrentTime; - + uint32_t* mCurrentTime; + BaseScreen* mCurrent = NULL; - + public: - ScreenManager(LiquidCrystal* display, unsigned long* currentTime) + ScreenManager(LiquidCrystal* display, uint32_t* currentTime) { mDisplay = display; mCurrentTime = currentTime; } - - + + inline BaseScreen* getCurrent() { return mCurrent; } - inline unsigned long getCurrentTime() + inline uint32_t getCurrentTime() { return *mCurrentTime; } @@ -66,17 +64,17 @@ class ScreenManager inline LiquidCrystal* getDisplay() { return mDisplay; - } - - - template void ScreenManager::show() + } + + + template void show() { if (mCurrent != NULL) { mCurrent->onHide(); - delete(mCurrent); + delete mCurrent; } - + mCurrent = new T(this); mCurrent->onShow(); } diff --git a/src/screen/countdown.cpp b/src/screen/countdown.cpp new file mode 100644 index 0000000..5e6ef18 --- /dev/null +++ b/src/screen/countdown.cpp @@ -0,0 +1,78 @@ +#include "countdown.h" +#include "screen/menu.h" +#include "display.h" +#include "state.h" +#include "config.h" +#include "buzzer.h" + + +inline uint32_t intDivCeil(uint32_t x, uint32_t y) +{ + return x / y + (x % y != 0); +} + + +void CountdownScreen::printRemainingTime() +{ + LCDPrintLineCentered(getDisplay(), 1, FormatTime(mLastDisplayed)); +} + + +void CountdownScreen::onShow() +{ + LCDPrintLineCentered(getDisplay(), 0, "Exposing..."); + + uint32_t remaining = GetExposureTimeRemaining(getCurrentTime()); + mLastDisplayed = intDivCeil(remaining, 1000); + + printRemainingTime(); + + analogWrite(PinLED, map(GetExposureIntensity(), 0, 100, 0, 255)); +} + + +void CountdownScreen::onHide() +{ + digitalWrite(PinLED, LOW); +} + + +void CountdownScreen::onButton() +{ + // TODO Confirmation? + Buzzer::click(); + getScreenManager()->show(); +} + + +void CountdownScreen::onEncoder(int32_t lastPosition, int32_t newPosition) +{ + // TODO Allow adding / removing time? +} + + +void CountdownScreen::onTick() +{ + uint32_t remaining = GetExposureTimeRemaining(getCurrentTime()); + remaining = intDivCeil(remaining, 1000); + + if (remaining == 0) + { + mLastDisplayed = 0; + + LCDPrintLineCentered(getDisplay(), 0, "Done!"); + printRemainingTime(); + digitalWrite(PinLED, LOW); + + Buzzer::completed(); + + ResetExposureTimer(); + getScreenManager()->show(); + } + else if (remaining != mLastDisplayed) + { + mLastDisplayed = remaining; + printRemainingTime(); + } +} + diff --git a/src/screen/countdown.h b/src/screen/countdown.h new file mode 100644 index 0000000..82e68f1 --- /dev/null +++ b/src/screen/countdown.h @@ -0,0 +1,29 @@ +#ifndef __countdown +#define __countdown + +#include "screen.h" + +/* + * Countdown screen + * Shows the remaining time. + */ +class CountdownScreen : public BaseScreen +{ + private: + uint32_t mLastDisplayed; + + protected: + void printRemainingTime(); + + public: + CountdownScreen(ScreenManager* screenManager) : BaseScreen(screenManager) { } + + void onShow(); + void onHide(); + + void onButton(); + void onEncoder(int32_t lastPosition, int32_t newPosition); + void onTick(); +}; + +#endif diff --git a/src/screen/menu.cpp b/src/screen/menu.cpp new file mode 100644 index 0000000..10052ff --- /dev/null +++ b/src/screen/menu.cpp @@ -0,0 +1,195 @@ +#include "screen/menu.h" +#include "config.h" +#include "buzzer.h" +#include "display.h" +#include "state.h" + +#include "menu/start.h" +#include "menu/time.h" +#include "menu/intensity.h" +#include "menu/sound.h" + + +MenuScreen::MenuScreen(ScreenManager* screenManager) : BaseScreen(screenManager) +{ + mCount = 4; + mItems = new MenuItem*[mCount]; + + mItems[0] = new StartMenuItem(); + mItems[1] = new TimeMenuItem(); + mItems[2] = new IntensityMenuItem(); + mItems[3] = new SoundMenuItem(); +} + + +MenuScreen::~MenuScreen() +{ + for (uint8_t i = 0; i < mCount; i++) + delete mItems[i]; + + delete[] mItems; +} + + +void MenuScreen::onShow() +{ + printFullUpdate(); +} + + +void MenuScreen::onHide() +{ +} + + +void MenuScreen::printFullUpdate() +{ + printTitle(); + printScrollIndicators(); + printValue(); +} + + +void MenuScreen::printTitle() +{ + const char* title = mItems[mSelected]->getTitle(); + LCDPrintLineCentered(getDisplay(), 0, title, 1); + + if (title != NULL) + delete[] title; +} + + +void MenuScreen::printScrollIndicators() +{ + LiquidCrystal* display = getDisplay(); + + display->setCursor(0, 0); + if (mSelected > 0) + display->write(mEditing ? LCDCharArrowLeftHollow : LCDCharArrowLeft); + else + display->write(' '); + + display->setCursor(LCDWidth - 1, 0); + if (mSelected < mCount - 1) + display->write(mEditing ? LCDCharArrowRightHollow : LCDCharArrowRight); + else + display->write(' '); +} + + +void MenuScreen::printValue() +{ + LiquidCrystal* display = getDisplay(); + + const char* value = mItems[mSelected]->getValue(); + + if (mEditing && value != NULL) + { + uint8_t valueLength = strlen(value); + char* editingValue = new char[valueLength + 5]; + + editingValue[0] = ' '; + editingValue[1] = ' '; + strcpy(editingValue + 2, value); + editingValue[valueLength + 2] = ' '; + + bool canIncrement = mItems[mSelected]->canIncrement(); + bool canDecrement = mItems[mSelected]->canDecrement(); + + if (canIncrement && canDecrement) + editingValue[valueLength + 3] = LCDCharUpDown; + else if (canIncrement) + editingValue[valueLength + 3] = LCDCharUp; + else if (canDecrement) + editingValue[valueLength + 3] = LCDCharDown; + else + editingValue[valueLength + 3] = ' '; + + editingValue[valueLength + 4] = 0; + + LCDPrintLineCentered(display, 1, editingValue); + + delete[] editingValue; + } + else + LCDPrintLineCentered(display, 1, value); + + if (value != NULL) + delete[] value; +} + + + +void MenuScreen::onButton() +{ + if (mItems[mSelected]->editable()) + { + Buzzer::select(); + + if (mEditing) + SaveSettings(); + + mEditing = !mEditing; + printScrollIndicators(); + printValue(); + } + else + { + mItems[mSelected]->execute(getScreenManager(), getCurrentTime()); + } +} + + +void MenuScreen::onEncoder(int32_t lastPosition, int32_t newPosition) +{ + if (mEditing) + { + if (newPosition > lastPosition) + { + if (mItems[mSelected]->canIncrement()) + { + mItems[mSelected]->incrementValue(); + Buzzer::select(); + } + } + else + { + if (mItems[mSelected]->canDecrement()) + { + mItems[mSelected]->decrementValue(); + Buzzer::select(); + } + } + + printValue(); + } + else + { + if (newPosition > lastPosition) + { + if (mSelected < mCount - 1) + { + Buzzer::select(); + mSelected++; + + printFullUpdate(); + } + } + else + { + if (mSelected > 0) + { + Buzzer::select(); + mSelected--; + + printFullUpdate(); + } + } + } +} + + +void MenuScreen::onTick() +{ +} diff --git a/src/screen/menu.h b/src/screen/menu.h new file mode 100644 index 0000000..da7f004 --- /dev/null +++ b/src/screen/menu.h @@ -0,0 +1,53 @@ +#ifndef __menuscreen +#define __menuscreen + +#include "screen.h" + +class MenuItem +{ + public: + virtual ~MenuItem() { } + + virtual const char* getTitle() = 0; + virtual const char* getValue() { return NULL; } + + virtual bool editable() { return false; } + + // Editable = true + virtual bool canIncrement() { return true; } + virtual bool canDecrement() { return true; } + virtual void incrementValue() { } + virtual void decrementValue() { } + + // Editable = false + virtual void execute(ScreenManager* screenManager, uint32_t currentTime) { } +}; + +class MenuScreen : public BaseScreen +{ + private: + uint8_t mCount; + MenuItem** mItems; + uint8_t mSelected = 0; + bool mEditing = false; + + protected: + void printFullUpdate(); + + void printTitle(); + void printScrollIndicators(); + void printValue(); + + public: + MenuScreen(ScreenManager* screenManager); + ~MenuScreen(); + + void onShow(); + void onHide(); + + void onButton(); + void onEncoder(int32_t lastPosition, int32_t newPosition); + void onTick(); +}; + +#endif \ No newline at end of file diff --git a/src/state.cpp b/src/state.cpp new file mode 100644 index 0000000..08372e6 --- /dev/null +++ b/src/state.cpp @@ -0,0 +1,105 @@ +#include "state.h" +#include "config.h" +#include + +uint16_t ExposureTime = DefaultExposureTime; +uint8_t ExposureIntensity = DefaultExposureIntensity; +uint32_t ExposureTimerStart = 0; +BuzzerSetting Buzzer = BuzzerSetting::CompletedButtonStartup; + + +uint16_t GetExposureTime() +{ + return ExposureTime; +} + + +void SetExposureTime(uint16_t value) +{ + if (value < SmallStep) + ExposureTime = SmallStep; + else + ExposureTime = value; +} + + +uint8_t GetExposureIntensity() +{ + return ExposureIntensity; +} + + +void SetExposureIntensity(uint8_t value) +{ + if (value > 100) + ExposureIntensity = 100; + else if (value < IntensityStep) + ExposureIntensity = IntensityStep; + else + ExposureIntensity = value; +} + + +BuzzerSetting GetBuzzer() +{ + return Buzzer; +} + + +void SetBuzzer(BuzzerSetting value) +{ + Buzzer = value; +} + + +void LoadSettings() +{ + uint16_t offset = 0; + EEPROM.get(offset, ExposureTime); + SetExposureTime(ExposureTime); + + offset += sizeof(ExposureTime); + + EEPROM.get(offset, ExposureIntensity); + SetExposureIntensity(ExposureIntensity); + + offset += sizeof(ExposureIntensity); + + EEPROM.get(offset, Buzzer); + if (Buzzer < BuzzerSetting::First || Buzzer > BuzzerSetting::Last) + Buzzer = BuzzerSetting::CompletedButtonStartup; +} + + +void SaveSettings() +{ + uint16_t offset = 0; + EEPROM.put(offset, ExposureTime); + offset += sizeof(ExposureTime); + + EEPROM.put(offset, ExposureIntensity); + offset += sizeof(ExposureIntensity); + + EEPROM.put(offset, (uint8_t)Buzzer); +} + + +void StartExposureTimer(uint32_t currentTime) +{ + ExposureTimerStart = currentTime; +} + + +void ResetExposureTimer() +{ + ExposureTimerStart = 0; +} + + +uint16_t GetExposureTimeRemaining(uint32_t currentTime) +{ + uint32_t elapsed = (currentTime - ExposureTimerStart); + uint32_t exposureTimeMs = ExposureTime * 1000; + + return elapsed <= exposureTimeMs ? exposureTimeMs - elapsed : 0; +} \ No newline at end of file diff --git a/src/state.h b/src/state.h new file mode 100644 index 0000000..0b35c4f --- /dev/null +++ b/src/state.h @@ -0,0 +1,34 @@ +#ifndef __state +#define __state + +#include + +enum BuzzerSetting +{ + CompletedButtonStartup = 0, + CompletedButton = 1, + Completed = 2, + None = 3, + + First = CompletedButtonStartup, + Last = None +}; + +uint16_t GetExposureTime(); +void SetExposureTime(uint16_t value); + +uint8_t GetExposureIntensity(); +void SetExposureIntensity(uint8_t value); + +BuzzerSetting GetBuzzer(); +void SetBuzzer(BuzzerSetting value); + + +void LoadSettings(); +void SaveSettings(); + +void StartExposureTimer(uint32_t currentTime); +void ResetExposureTimer(); +uint16_t GetExposureTimeRemaining(uint32_t currentTime); + +#endif diff --git a/upload.ps1 b/upload.ps1 new file mode 100644 index 0000000..9b3c625 --- /dev/null +++ b/upload.ps1 @@ -0,0 +1 @@ +& platformio run --target upload \ No newline at end of file