#define debug
/*
A simple Pong game.
https://notabug.org/Maverick/WokwiPong
Based on Arduino Pong by eholk
https://github.com/eholk/Arduino-Pong
Basic OLED menu
https://wokwi.com/projects/323967017900048980
*/
#include <DHT.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SSD1306_NO_SPLASH
#include "Button.h"
#include "Clock.h"
#include "config.h"
#include <Servo.h>
//----------------------------------------------
// PINS
//----------------------------------------------
#define ENCODER_CLK 2
#define ENCODER_DT 3
#define ENCODER_SW 4
#define DHTpin 6
#define relay1pin 7
#define relay2pin 8
#define relay3pin 9
#define relay4pin 10
#define servoPin 5
#define buzzerPin 11
//----------------------------------------------
// OLED
//----------------------------------------------
#define OLED_ADDR 0x3C // OLED i2c address
#define SCREEN_WIDTH 128 // OLED display width, in pixels (usually 128)
#define SCREEN_HEIGHT 64 // OLED display height, in pixels (64 for larger oLEDs)
#define OLED_RESET -1 // Reset pin gpio (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display = Adafruit_SSD1306(128, 64, &Wire);
//----------------------------------------------
//
//----------------------------------------------
Clock clock;
//----------------------------------------------
// Buttons
//----------------------------------------------
Button encButton(ENCODER_SW);
//----------------------------------------------
// DHT Temperature
//----------------------------------------------
//#define DHTTYPE DHT11 // DHT 11
#define DHTTYPE DHT22 // DHT 22 (AM2302), AM2321
//#define DHTTYPE DHT21 // DHT 21 (AM2301)
DHT dht(DHTpin, DHTTYPE);
//----------------------------------------------
// Vent Positions
//----------------------------------------------
#define ventOpenPos 0
#define ventClosedPos 180
//----------------------------------------------
Servo servoVent; // Define servoVent as a Servo
//----------------------------------------------
typedef struct {
uint32_t total;
uint32_t ok;
uint32_t crc_error;
uint32_t time_out;
uint32_t connect;
uint32_t ack_l;
uint32_t ack_h;
uint32_t unknown;
} savedData;
savedData saved;
//----------------------------------------------
int encoder = 0; // encoder counter
int lastClk = HIGH;
bool time12hr = true;
bool pm = false;
bool setupAlarm = false;
unsigned long currenttime = 0;
unsigned long prevtime = 0;
unsigned long prevDHTtime = 0;
//----------------------------------------------
int MenuNr = 0; // Menu number
int SwMenu = 0; // Switch menu (Sub menu's in the main menu's)
//----------------------------------------------
// Settings Misc
//----------------------------------------------
const int serialDebug = 1;
const int iLED = 2; // onboard indicator led gpio pin
#define BUTTONPRESSEDSTATE 0 // rotary encoder gpio pin logic level when the button is pressed (usually 0)
#define DEBOUNCEDELAY 20 // debounce delay for button inputs
const int menuTimeout = 10; // menu inactivity timeout (seconds)
const bool menuLargeText = 0; // show larger text when possible (if struggling to read the small text)
const int maxmenuItems = 12; // max number of items used in any of the menus (keep as low as possible to save memory)
const int itemTrigger = 2; // rotary encoder - counts per tick (varies between encoders usually 1 or 2)
const int topLine = 18; // y position of lower area of the display (18 with two colour displays)
const byte lineSpace1 = 9; // line spacing for textsize 1 (small text)
const byte lineSpace2 = 17; // line spacing for textsize 2 (large text)
const int displayMaxLines = 5; // max lines that can be displayed in lower section of display in textsize1 (5 on larger oLeds)
const int MaxmenuTitleLength = 10; // max characters per line when using text size 2 (usually 10)
//==========================================================================================
// forward declarations
//------------------------------------------------------------------------------------------
void doEncoder();
void demoMenu();
void menuActions();
void value1();
void menuValues();
void reUpdateButton();
void serviceMenu();
int serviceValue(bool _blocking);
void createList(String _title, int _noOfElements, String *_list);
void displayMessage(String _title, String _message);
void resetMenu();
//==========================================================================================
// Function Defines
//------------------------------------------------------------------------------------------
#define lights(s) { digitalWrite(relay1pin,s); }
#define heater(s) { digitalWrite(relay2pin,s); }
#define humidity(s) { digitalWrite(relay3pin,s); }
#define fan(s) { digitalWrite(relay4pin,s); }
#define lightOn { digitalWrite(relay1pin,true); }
#define lightOff { digitalWrite(relay1pin,false); }
#define heaterOn { digitalWrite(relay2pin,true); }
#define heaterOff { digitalWrite(relay2pin,false); }
#define humidityOn { digitalWrite(relay3pin,true); }
#define humidityOff { digitalWrite(relay3pin,false); }
#define fanOn { digitalWrite(relay4pin,true); }
#define fanOff { digitalWrite(relay4pin,false); }
#define closeVent { servoVent.write(ventClosedPos); }
#define openVent { servoVent.write(ventOpenPos); }
#define ventPos(s) { int i = map(s,0,100,0,180); servoVent.write(i); }
//----------------------------------------------
#ifdef debug
#define PRINTln(s) { Serial.println(s); }
#define PRINT(s) { Serial.print(s); }
#else
#define PRINTln(s) { Serial.print(""); }
#define PRINT(s) { Serial.print(""); }
#endif
//==========================================================================================
// SETUP
//------------------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
PRINTln("debug enabled");
dht.begin();
clock.begin();
encButton.begin();
encButton.set_repeat(500, 200);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
// Display the splash screen (we're legally required to do so)
display.display();
unsigned long start = millis();
pinMode(relay1pin, OUTPUT); // lights relay
pinMode(relay2pin, OUTPUT); // heater relay
pinMode(relay3pin, OUTPUT); // humidity relay
pinMode(relay4pin, OUTPUT); // fan relay
pinMode(buzzerPin, OUTPUT);
servoVent.attach(servoPin); //Attach Servo to pin
closeVent; //Move Servo to min possition
delay(1000);
openVent; //Move Servo to max possition
delay(1000);
ventPos(50); //Move Servo possition 0-100%
pinMode(ENCODER_CLK, INPUT);
pinMode(ENCODER_DT, INPUT);
attachInterrupt(digitalPinToInterrupt(ENCODER_CLK), readEncoder, FALLING);
display.clearDisplay();
display.display();
singleTone();
}
//==========================================================================================
void readEncoder() {
int dtValue = digitalRead(ENCODER_DT);
if (dtValue == HIGH) {
encoder++;
PRINT(encoder);
PRINTln("\t Rotated clockwise ⏩");
}
if (dtValue == LOW) {
encoder--;
PRINT(encoder);
PRINTln("\t Rotated counterclockwise ⏪");
}
}
//==========================================================================================
// LOOP
//------------------------------------------------------------------------------------------
void loop() {
bool update_needed = false;
currenttime = millis();
/*
display.setTextColor(WHITE, BLACK);
display.setCursor(0, 56);
display.print(mcu_score);
display.setCursor(122, 56);
display.print(player_score);
display.display();
*/
//clockLoop();
if (currenttime > prevDHTtime + 5000) {
dhtLoop();
prevDHTtime = currenttime;
}
//*************************** menu controls ***************************//
if (SwMenu == 0) {
MenuNr = encoder;
if (encoder < 0)encoder = 0;
if (encoder > 3)encoder = 3;
if (MenuNr == 2) {
if (encButton.pressed()) {
setupAlarm = true;
PRINTln("set alarm hour");
SwMenu = 1; // set alarm hour
}
}
}
if (MenuNr == 2) {
if (SwMenu == 1) { // change alarm hour
if (encoder < 1)encoder = 24;
if (encoder > 24)encoder = 1;
if (encButton.pressed()) {
setupAlarm = true;
PRINTln("set alarm minute");
SwMenu = 2; // set alarm minute
}
}
if (SwMenu == 2) { // change alarm minute
if (encoder < 0)encoder = 59;
if (encoder > 59)encoder = 0;
if (encButton.pressed()) {
setupAlarm = true;
PRINTln("done setting alarm");
SwMenu = 0; // set alarm done
setupAlarm = false;
}
}
}
//*************************** main page ***************************//
switch (MenuNr) {
case 0:
switch (SwMenu) {
case 0:
clockState();
break;
case 1:
break;
}
break;
case 1:
switch (SwMenu) {
case 0:
alarmStatusState();
break;
case 1:
break;
break;
case 2:
break;
}
break;
case 2:
switch (SwMenu) {
case 0:
alarmTimeState();
break;
case 1:
alarmTimeState();
break;
case 2:
alarmTimeState();
break;
}
break;
case 3:
switch (SwMenu) {
case 0:
snoozeState();
break;
case 1:
break;
}
break;
case 4:
switch (SwMenu) {
case 0:
break;
case 1:
break;
}
break;
}
}
//==========================================================================================
void singleTone() {
tone(11, 200, 25);
delay(25);
noTone(11);
}
//------------------------------------------------------------------------------------------
void player_scoreTone() {
tone(11, 200, 25);
delay(50);
noTone(11);
delay(25);
tone(11, 250, 25);
delay(25);
noTone(11);
}
//==========================================================================================
void dhtLoop() {
// Reading temperature or humidity takes about 250 milliseconds!
// Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
float h = dht.readHumidity();
// Read temperature as Celsius (the default)
float t = dht.readTemperature();
// Read temperature as Fahrenheit (isFahrenheit = true)
float f = dht.readTemperature(true);
// Check if any reads failed and exit early (to try again).
if (isnan(h) || isnan(t) || isnan(f)) {
PRINTln(F("Failed to read from DHT sensor!"));
return;
}
// Compute heat index in Fahrenheit (the default)
float hif = dht.computeHeatIndex(f, h);
// Compute heat index in Celsius (isFahreheit = false)
float hic = dht.computeHeatIndex(t, h, false);
PRINTln(F("------------------------------------------------"));
PRINT(F("Humidity: "));
PRINT(h);
PRINT(F("% Temperature: "));
PRINT(t);
PRINT(F("°C/"));
PRINT(f);
PRINTln(F("°F"));
PRINT(F("Heat index: "));
PRINT(hic);
PRINT(F("°C "));
PRINT(hif);
PRINTln(F("°F"));
PRINTln(F("------------------------------------------------"));
}
//=================================================================================================
//=================================================================================================
//=================================================================================================
//=================================================================================================
//---------------------------------------------------------------------------------------------------
enum DisplayState {
DisplayClock,
DisplayAlarmStatus,
DisplayAlarmTime,
DisplayAlarmActive,
DisplaySnooze,
};
DisplayState displayState = DisplayClock;
//---------------------------------------------------------------------------------------------------
long lastStateChange = 0;
//---------------------------------------------------------------------------------------------------
/*
void changeDisplayState(DisplayState newValue) {
displayState = newValue;
lastStateChange = millis();
}
*/
//---------------------------------------------------------------------------------------------------
long millisSinceStateChange() {
return millis() - lastStateChange;
}
//---------------------------------------------------------------------------------------------------
void displayTime() {
DateTime now = clock.now();
bool blinkState = now.second() % 2 == 0;
display.setTextSize(3); // Normal 1:1 pixel scale
display.setTextColor(WHITE, BLACK);
display.setCursor(0, 0);
int hrTime = now.hour();
if (time12hr)
if (hrTime > 12) {
hrTime = hrTime - 12;
pm = true;
}
if (hrTime < 10) display.print(" ");
display.print(hrTime);
display.print(":");
int minTime = now.minute();
if (minTime < 10) display.print("0");
display.print(now.minute());
if (time12hr) if (pm) display.print("pm"); else display.print("am");
display.display();
}
//---------------------------------------------------------------------------------------------------
void clockState() {
displayTime();
/*
if (alarmButton.read() == Button::RELEASED && clock.alarmActive()) {
// Read alarmButton has_changed() to clear its state
alarmButton.has_changed();
//changeDisplayState(DisplayAlarmActive);
displayState = DisplayAlarmActive;
lastStateChange = millis();
return;
}
if (hourButton.pressed()) {
clock.incrementHour();
}
if (minuteButton.pressed()) {
clock.incrementMinute();
}
if (alarmButton.pressed()) {
clock.toggleAlarm();
//changeDisplayState(DisplayAlarmStatus);
displayState = DisplayAlarmStatus;
lastStateChange = millis();
}
*/
}
//---------------------------------------------------------------------------------------------------
void alarmStatusState() {
display.clearDisplay();
display.setTextSize(2); //pixel scale
display.setTextColor(WHITE, BLACK);
display.setCursor(0, 0);
display.print("Alarm: ");
display.print(clock.alarmEnabled() ? "on" : "off");
display.display();
//sevseg.setChars(clock.alarmEnabled() ? " on" : " off");
if (millisSinceStateChange() > ALARM_STATUS_DISPLAY_TIME) {
//changeDisplayState(clock.alarmEnabled() ? DisplayAlarmTime : DisplayClock);
if (clock.alarmEnabled()) {
displayState = DisplayAlarmTime;
} else {
displayState = DisplayClock;
}
lastStateChange = millis();
return;
}
}
//---------------------------------------------------------------------------------------------------
void alarmTimeState() {
DateTime now = clock.now();
bool blinkState = now.second() % 2 == 0;
DateTime alarm = clock.alarmTime();
//sevseg.setNumber(alarm.hour() * 100 + alarm.minute(), -1);
/*
if (millisSinceStateChange() > ALARM_HOUR_DISPLAY_TIME || alarmButton.pressed()) {
//changeDisplayState(DisplayClock);
displayState = DisplayClock;
lastStateChange = millis();
return;
}
if (hourButton.pressed()) {
clock.incrementAlarmHour();
lastStateChange = millis();
}
if (minuteButton.pressed()) {
clock.incrementAlarmMinute();
lastStateChange = millis();
}
if (alarmButton.pressed()) {
//changeDisplayState(DisplayClock);
displayState = DisplayClock;
lastStateChange = millis();
}
*/
display.setTextSize(2); // Normal 1:1 pixel scale
display.setTextColor(WHITE, BLACK);
display.setCursor(0, 0);
display.print("Alarm");
display.setTextSize(3); // Normal 1:1 pixel scale
display.setTextColor(WHITE, BLACK);
display.setCursor(0, 32);
int hrTime = alarm.hour();
if (time12hr)
if (hrTime > 12) {
hrTime = hrTime - 12;
pm = true;
}
if (hrTime < 10) display.print(" ");
display.print(hrTime);
if (SwMenu = 1 && blinkState) {
display.setCursor(0, 32);
display.print(" ");
}
display.print(":");
if (SwMenu = 2 && blinkState) {
display.setCursor(0, 32);
display.print(" ");
} else {
int minTime = alarm.minute();
if (minTime < 10) display.print("0");
display.print(now.minute());
}
if (time12hr) if (pm) display.print("pm"); else display.print("am");
display.display();
}
//---------------------------------------------------------------------------------------------------
void alarmState() {
displayTime();
/*
if (alarmButton.read() == Button::RELEASED) {
alarmTone.play();
}
if (alarmButton.pressed()) {
alarmTone.stop();
}
if (alarmButton.released()) {
alarmTone.stop();
bool longPress = alarmButton.repeat_count() > 0;
if (longPress) {
clock.stopAlarm();
//changeDisplayState(DisplayClock);
displayState = DisplayClock;
lastStateChange = millis();
} else {
clock.snooze();
//changeDisplayState(DisplaySnooze);
displayState = DisplaySnooze;
lastStateChange = millis();
}
}
*/
}
//---------------------------------------------------------------------------------------------------
void snoozeState() {
//sevseg.setChars("****");
if (millisSinceStateChange() > SNOOZE_DISPLAY_TIME) {
//changeDisplayState(DisplayClock);
displayState = DisplayClock;
lastStateChange = millis();
return;
}
}
//---------------------------------------------------------------------------------------------------
void clockSetup() {
bool resistorsOnSegments = false;
bool updateWithDelays = false;
bool leadingZeros = true;
bool disableDecPoint = true;
}
//---------------------------------------------------------------------------------------------------
void clockLoop() {
switch (displayState) {
case DisplayClock:
clockState();
break;
case DisplayAlarmStatus:
alarmStatusState();
break;
case DisplayAlarmTime:
alarmTimeState();
break;
case DisplayAlarmActive:
alarmState();
break;
case DisplaySnooze:
snoozeState();
break;
}
}