/////////////////////////////////////////////////
//
// D-I-Y Polishing Station
//
// Copyright (c) 2017 Rey Medina
//
//
// ACKNOWLEDGEMENTS
// This project will not be possible without the prior work of the following persons:
//
// Angelo Fiorillo - Kitchen Timer code
// Matthias Hertel - OneButton library and code
// Nick Gammon - Rotary Encoder code
//
/////////////////////////////////////////////////
#include "OneButton.h"
// #include <Time.h>
#include <TimeLib.h>
#include <LiquidCrystal_I2C.h>
#include <Adafruit_NeoPixel.h>
#include <Stepper.h>
// Setup a new OneButton on pin A2.
OneButton EncButton;
// Button actions
#define btnClear 0
#define btnClicked 1
#define btnDblClicked 2
#define btnHeldStart 3
#define btnHeld 4
#define btnReleased 5
int btnLastAction = btnClear;
// Rotary encoder variables
volatile boolean blnRotated;
#define PinEncCLK 5
#define PinEncDATA 2
#define INTERRUPT 0 // that is, pin 2
int EncState;
#define RotateCW 1
#define RotateCCW 2
int iLastDir;
// Configure LCD
LiquidCrystal_I2C lcd(0x27, 16, 2); // Set the LCD I2C address
// Define icons
byte speaker[8] = {
B00001,
B00011,
B00111,
B10111,
B10111,
B00111,
B00011,
B00001
};
byte soundOn[8] = {
B01000,
B00100,
B10010,
B01010,
B01010,
B10010,
B00100,
B01000
};
byte soundOff[8] = {
B00000,
B00000,
B10100,
B01000,
B10100,
B00000,
B00000,
B00000
};
const int icon_speaker = 1;
const int icon_soundOn = 2;
const int icon_soundOff = 3;
const int icon_hourglass = 4;
const int icon_timer = 5;
bool blnSoundEnabled = true;
// Timer variables
int setupHours = 0; // How many hours will count down when started
int setupMinutes = 0; // How many minutes will count down when started
int setupSeconds = 0; // How many seconds will count down when started
time_t setupTime = 0;
int currentHours = 0;
int currentMinutes = 0;
int currentSeconds = 0;
time_t currentTime = 0;
time_t startTime = 0;
time_t elapsedTime = 0;
// Modes
const int MODE_IDLE = 0;
const int MODE_SETUP = 1;
const int MODE_RUNNING = 2;
const int MODE_RINGING = 3;
int currentMode = MODE_IDLE;
int dataSelection = 0;
// Buzzer pin config
#define buzzerPin 10
// humidifier pin config
#define relayPin 12
bool HumidifierRunning = false;
// LED colors
#define PixelPin 13
const int intMaxLEDColors = 7;
int intCurrLED = 1;
int intWipeWait = 0;
Adafruit_NeoPixel strip = Adafruit_NeoPixel(12, PixelPin, NEO_GRB + NEO_KHZ800);
// initialize the stepper library on pins 8 through 11:
//const int stepsPerRevolution = 200; // Nema 14
const int stepsPerRevolution = 1000;
Stepper myStepper(stepsPerRevolution, 6, 7, 8, 9);
int intPolishProgress = 0;
#define TICKVAL 7
//////////////////////////////////////////////////////////////////////////////////
//
// setup
//
//////////////////////////////////////////////////////////////////////////////////
void setup() {
Serial.begin(9600);
// link the Encoder Button functions.
EncButton.setup(A2, INPUT_PULLUP, true);
EncButton.attachClick(click1);
EncButton.attachDoubleClick(doubleclick1);
EncButton.attachLongPressStart(longPressStart1);
EncButton.attachLongPressStop(longPressStop1);
EncButton.attachDuringLongPress(longPress1);
// setup rotation interrupt
digitalWrite(PinEncCLK, HIGH); // enable pull-ups
digitalWrite(PinEncDATA, HIGH);
attachInterrupt(INTERRUPT, EncRotateISR, CHANGE);
// configure buzzer pin
pinMode(buzzerPin, OUTPUT);
// Initialize LCD for 16 chars, 2 lines
lcd.init();
// create custom icons
lcd.createChar(1, speaker);
lcd.createChar(2, soundOn);
lcd.createChar(3, soundOff);
// set up humidifier pin
pinMode(relayPin, OUTPUT);
btnLastAction = btnClear;
// Neo Pixel setup
strip.begin();
strip.show(); // Initialize all pixels to 'off'
// set stepper speed
myStepper.setSpeed(10);
Serial.begin(9600);
} // setup
//////////////////////////////////////////////////////////////////////////////////
//
// loop
//
//////////////////////////////////////////////////////////////////////////////////
void loop() {
// keep watching the push buttons:
EncButton.tick();
// if (blnRotated) {
// switch (currentMode) {
// case MODE_IDLE:
// // toggle beep on/off
// blnSoundEnabled = (!blnSoundEnabled);
// break;
// case MODE_SETUP:
// if (iLastDir == RotateCCW) {
// switch (dataSelection) {
// case 0: // hours
// setupHours--;
// if (setupHours == -1) {
// setupHours = 1; // Max. hours. Original code set to 12 but 2hrs should be more than enough to polish
// }
// break;
// case 1: // minutes
// setupMinutes--;
// if (setupMinutes == -1) {
// setupMinutes = 59;
// }
// break;
// case 2: // seconds
// setupSeconds--;
// if (setupSeconds == -1) {
// setupSeconds = 59;
// }
// break;
// }
// }
// if (iLastDir == RotateCW) {
// switch (dataSelection) {
// case 0: // hours
// setupHours++;
// // if(setupHours == 13)
// if (setupHours == 2) {
// setupHours = 0;
// }
// break;
// case 1: // minutes
// setupMinutes++;
// if (setupMinutes == 60) {
// setupMinutes = 0;
// }
// break;
// case 2: // seconds
// setupSeconds++;
// if (setupSeconds == 60) {
// setupSeconds = 0;
// }
// break;
// }
// }
// break;
// case MODE_RUNNING:
// // Cycle through LED colors
// if (iLastDir == RotateCW) {
// intCurrLED++;
// if (intCurrLED > intMaxLEDColors) intCurrLED = 0;
// }
// if (iLastDir == RotateCCW) {
// intCurrLED--;
// if (intCurrLED < 0) intCurrLED = intMaxLEDColors;
// }
// TurnLedON();
// Serial.println(intCurrLED);
// break;
// case MODE_RINGING:
// currentMode = MODE_IDLE;
// break;
// }
// iLastDir = 0;
// blnRotated = false;
// } // end if blnRotated
// // Button management
// if (btnLastAction > 0) {
// switch (currentMode) {
// case MODE_IDLE:
// if (btnLastAction == btnReleased) {
// currentMode = MODE_SETUP;
// }
// if (btnLastAction == btnClicked) {
// if (setupHours + setupMinutes + setupSeconds > 0) {
// currentMode = currentMode == MODE_IDLE ? MODE_RUNNING : MODE_IDLE;
// if (currentMode == MODE_RUNNING) {
// // STARTING TIMER!
// startTime = now();
// TurnLedON();
// }
// } else {
// // lcd.setCursor(0, 0);
// // lcd.print(formatLCDstring("Timer not set"));
// // delay(1500);
// }
// }
// break;
// case MODE_SETUP:
// if (btnLastAction == btnClicked) {
// // Select next data to adjust
// dataSelection++;
// if (dataSelection == 3) {
// dataSelection = 0;
// }
// }
// if (btnLastAction == btnReleased) {
// // Exit setup mode
// setupTime = setupSeconds + (60 * setupMinutes) + (3600 * setupHours);
// currentHours = setupHours;
// currentMinutes = setupMinutes;
// currentSeconds = setupSeconds;
// dataSelection = 0;
// currentMode = MODE_IDLE;
// }
// break;
// case MODE_RUNNING:
// if (btnLastAction == btnClicked) {
// Reset();
// TurnLedOFF();
// currentMode = MODE_IDLE;
// }
// break;
// case MODE_RINGING:
// if (btnLastAction == btnClicked) {
// currentMode = MODE_IDLE;
// }
// break;
// }
// btnLastAction = btnClear;
// } // end if btnLastAction
// /*
// * Mode management
// */
// switch (currentMode) {
// case MODE_IDLE:
// digitalWrite(relayPin, HIGH);
// HumidifierRunning = false;
// intPolishProgress = 0;
// break;
// }
// /*
// * Time management
// */
// switch (currentMode) {
// case MODE_IDLE:
// // turn off LEDs
// noTone(buzzerPin);
// myStepper.step(0);
// case MODE_SETUP:
// // NOP
// break;
// case MODE_RUNNING:
// currentTime = setupTime - (now() - startTime);
// myStepper.step(20);
// // Check if humidifier is currently running. If not, turn it on
// if (!HumidifierRunning) {
// digitalWrite(relayPin, LOW);
// HumidifierRunning = true;
// }
// if (currentTime <= 0) {
// currentMode = MODE_RINGING;
// TurnLedOFF();
// }
// break;
// case MODE_RINGING:
// if (HumidifierRunning) {
// digitalWrite(relayPin, HIGH);
// HumidifierRunning = false;
// }
// if (blnSoundEnabled) {
// for (int intRepeat = 0; intRepeat < 1; intRepeat++) {
// tone(buzzerPin, 2100);
// delay(80);
// noTone(buzzerPin);
// delay(80);
// }
// delay(2000);
// }
// break;
// }
// /*
// * LCD management
// */
// //lcd.clear();
// lcd.setCursor(0, 0);
// switch (currentMode) {
// case MODE_IDLE:
// //lcd.backlight();
// lcd.print(formatLCDstring("READY"));
// lcd.setCursor(0, 1);
// lcd.print(formatTimeDigits(currentHours));
// lcd.print(":");
// lcd.print(formatTimeDigits(currentMinutes));
// lcd.print(":");
// lcd.print(formatTimeDigits(currentSeconds));
// lcd.print(" ");
// DisplaySoundConfig();
// break;
// case MODE_SETUP:
// lcd.print("Set timer: ");
// switch (dataSelection) {
// case 0:
// lcd.print("HRS ");
// break;
// case 1:
// lcd.print("MINS");
// break;
// case 2:
// lcd.print("SECS");
// break;
// }
// lcd.setCursor(0, 1);
// lcd.print(formatTimeDigits(setupHours));
// lcd.print(":");
// lcd.print(formatTimeDigits(setupMinutes));
// lcd.print(":");
// lcd.print(formatTimeDigits(setupSeconds));
// lcd.print(" ");
// break;
// case MODE_RUNNING:
// DisplayPolishProgress();
// lcd.setCursor(0, 1);
// if (hour(currentTime) < 10) lcd.print("0");
// lcd.print(hour(currentTime));
// lcd.print(":");
// if (minute(currentTime) < 10) lcd.print("0");
// lcd.print(minute(currentTime));
// lcd.print(":");
// if (second(currentTime) < 10) lcd.print("0");
// lcd.print(second(currentTime));
// break;
// case MODE_RINGING:
// lcd.print(formatLCDstring("DONE POLISHING"));
// lcd.setCursor(0, 1);
// lcd.print(" ");
// break;
// }
} // loop
/////////////////////////////////////
//
// Encoder rotation callback functions
//
/////////////////////////////////////
void EncRotateISR() {
EncState = 0;
EncState = EncState + digitalRead(PinEncCLK);
if (EncState == 0) return;
EncState <<= 1;
EncState = EncState + digitalRead(PinEncDATA);
if (digitalRead(PinEncDATA) == digitalRead(PinEncCLK)) iLastDir = RotateCW;
else iLastDir = RotateCCW;
blnRotated = true;
} // EncRotateISR
/////////////////////////////////////
//
// Encoder Button callback functions
//
/////////////////////////////////////
// This function will be called when the EncButton was pressed 1 time (and no 2. button press followed).
void click1() {
Serial.println("Encoder Button click.");
btnLastAction = btnClicked;
} // click1
// This function will be called when the EncButton was pressed 2 times in a short timeframe.
void doubleclick1() {
Serial.println("Encoder Button doubleclick.");
btnLastAction = btnDblClicked;
} // doubleclick1
// This function will be called once, when the EncButton is pressed for a long time.
void longPressStart1() {
Serial.println("Encoder Button longPress start");
btnLastAction = btnHeldStart;
} // longPressStart1
// This function will be called often, while the EncButton is pressed for a long time.
void longPress1() {
Serial.println("Encoder Button longPress...");
btnLastAction = btnHeld;
} // longPress1
// This function will be called once, when the EncButton is released after beeing pressed for a long time.
void longPressStop1() {
Serial.println("Encoder Button longPress stop");
btnLastAction = btnReleased;
} // longPressStop1
/////////////////////////////////////
//
// Other routines
//
/////////////////////////////////////
char* formatTimeDigits(int num) {
static char strOut[3];
if (num >= 0 && num < 100) {
sprintf(strOut, "%02d", num);
} else {
strcpy(strOut, "XX");
}
return strOut;
}
char* formatLCDstring(char* strIn) {
static char strOut[16];
sprintf(strOut, "%-16s", strIn);
return strOut;
}
void DisplaySoundConfig() {
lcd.setCursor(14, 1);
lcd.write(icon_speaker);
lcd.setCursor(15, 1);
if (blnSoundEnabled) {
lcd.write(icon_soundOn);
} else {
lcd.write(icon_soundOff);
}
} // DisplaySoundConfig()
void Reset() {
currentMode = MODE_IDLE;
currentHours = setupHours;
currentMinutes = setupMinutes;
currentSeconds = setupSeconds;
}
///////////////////////////////
//
// Neo Pixel code
//
///////////////////////////////
// Fill the dots one after the other with a color
void colorWipe(uint32_t c, uint8_t wait) {
for (uint16_t i = 0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, c);
strip.show();
delay(wait);
}
}
void TurnLedON() {
switch (intCurrLED) {
case 0:
// turn off
TurnLedOFF();
break;
case 1:
colorWipe(strip.Color(100, 100, 100), intWipeWait); // White
break;
case 2:
colorWipe(strip.Color(150, 0, 0), intWipeWait); // Red
break;
case 3:
colorWipe(strip.Color(0, 150, 0), intWipeWait); // Green
break;
case 4:
colorWipe(strip.Color(0, 0, 150), intWipeWait); // Blue
break;
case 5:
colorWipe(strip.Color(150, 50, 0), intWipeWait); // Yellow
break;
case 6:
colorWipe(strip.Color(150, 0, 100), intWipeWait); // Purple
break;
case 7:
colorWipe(strip.Color(0, 150, 150), intWipeWait); // Cyan
break;
}
}
void TurnLedOFF() {
colorWipe(strip.Color(0, 0, 0), 0);
}
void DisplayPolishProgress() {
intPolishProgress++;
// Serial.println(intPolishProgress);
if (intPolishProgress > TICKVAL * 4) intPolishProgress = 0;
if (intPolishProgress <= TICKVAL) lcd.print("Polishing ");
if (intPolishProgress > TICKVAL && intPolishProgress <= TICKVAL * 2) lcd.print("Polishing. ");
if (intPolishProgress > TICKVAL * 2 && intPolishProgress <= TICKVAL * 3) lcd.print("Polishing.. ");
if (intPolishProgress > TICKVAL * 3 && intPolishProgress <= TICKVAL * 4) lcd.print("Polishing... ");
if (intPolishProgress > TICKVAL * 4 && intPolishProgress <= TICKVAL * 5) lcd.print("Polishing.... ");
if (intPolishProgress > TICKVAL * 5 && intPolishProgress <= TICKVAL * 6) lcd.print("Polishing..... ");
if (intPolishProgress > TICKVAL * 6 && intPolishProgress <= TICKVAL * 7) lcd.print("Polishing...... ");
if (intPolishProgress > TICKVAL * 7) lcd.print("Polishing.......");
}