Hackster is hosting Hackster Holidays, Ep. 7: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 7 on Friday!
Erik de Ruiter
Published © GPL3+

Bathroom Ventilation Fan Controller v1.0

Now 9 months in use and performing so well... ! An Arduino controller to *very effectively* keep the humidity to an set level.

IntermediateFull instructions provided8 hours10,476
Bathroom Ventilation Fan Controller v1.0

Things used in this project

Hardware components

Arduino Nano R3
Arduino Nano R3
×1
Adafruit BME280 sensor
×1
OLED display 128x64 SPI
BUYING A CHINESE DISPLAY WILL WORK but you need to figure out which setting of the library you need. See the end part of the sketch!
×1
Axxatronic case with ventilation holes
Amazing case, small and with ventilation holes.
×1
Button
you'll have to find or make the key-caps yourself... Mine are 18mm tall
×3
wire to pcb connector
×1
NPN transistor
×1
2K2 resistor (or 3K3 or 4K7)
×1
Relay - Panasonic APF10205
×1

Story

Read more

Custom parts and enclosures

Files to make the project on strip-board

the board will fit in the mentioned Axxatronic case

all the Libraries needed

If you have problems with libraries, try to rename your existing library folder and create a new one with the contents of this ZIP file.

Sketch in HEX file format to upoad directly to the AtMega328P chip

NOT TESTED! but worth a try. Look here for a tutorial: https://www.youtube.com/watch?v=Wcaql0jtlUg
One of the many tutorials out there.

VScode/PlatformIO project folder of the Bathroom Fan Controller

If you want to use VScode with PlatformIO extension, then here is a complete 'Project' folder which you can open in PlatformIO.

Schematics

Bathroom Fan Controller Schematic

Connect the High voltage parts at your own risk! Be sure to work safely with 230V grid voltage

Code

UPDATE v1.12: added PIR sensor input to prevent OLED burg-in

C/C++
Although when asking other said that OLED should NOT burn-in, I experienced my OLED to decay after only a few months. So by adding a very small, cheap and simple PIR sensor the display will turn on when detecting movement and turn off after 1 minute when no movement is detected. PIR input is at PIN 4 of the Arduino.
//  888888b.            888    888
//  888  "88b           888    888
//  888  .88P           888    888
//  8888888K.   8888b.  888888 88888b.  888d888 .d88b.   .d88b.  88888b.d88b.
//  888  "Y88b     "88b 888    888 "88b 888P"  d88""88b d88""88b 888 "888 "88b
//  888    888 .d888888 888    888  888 888    888  888 888  888 888  888  888
//  888   d88P 888  888 Y88b.  888  888 888    Y88..88P Y88..88P 888  888  888
//  8888888P"  "Y888888  "Y888 888  888 888     "Y88P"   "Y88P"  888  888  888
//
//
//
//  8888888888                      .d8888b.                    888                    888 888
//  888                            d88P  Y88b                   888                    888 888
//  888                            888    888                   888                    888 888
//  8888888  8888b.  88888b.       888         .d88b.  88888b.  888888 888d888 .d88b.  888 888  .d88b.  888d888
//  888         "88b 888 "88b      888        d88""88b 888 "88b 888    888P"  d88""88b 888 888 d8P  Y8b 888P"
//  888     .d888888 888  888      888    888 888  888 888  888 888    888    888  888 888 888 88888888 888
//  888     888  888 888  888      Y88b  d88P Y88..88P 888  888 Y88b.  888    Y88..88P 888 888 Y8b.     888
//  888     "Y888888 888  888       "Y8888P"   "Y88P"  888  888  "Y888 888     "Y88P"  888 888  "Y8888  888
//
/*

* Version 1.12 - Last change: 2021-06-19

Version 1.12 - added PIR sensor to prevent OLED display burn-in

I looked for a good solution to keep the humidity level down in our bathroom.  We already have (already >20 years) a very good and silent fan but it is operated manually and sometimes we forgot to turn it on and/or off.
So looking around, I found there are only a few options. Yes you can buy a fan with build in controller but they are expensive and the manual settings are very limited. 
A stand alone humidity controller/switch was much harder to find! I only found a mechanical switch for just under 100 euros.  

The solution and description

As I am very fond of the Arduino, I (again) decided to make myself the things I need, in this case a "Bathroom Fan Controller" (for lack of a better name)
The controller has the following function and options:

    Measure Relative Humidity and temperature. (duh.)
    Turn a Fan on (via a relay) and switching it off when the humidity has dropped.
    OPTIONAL: The Fan will stay on for a selectable time after humidity has dropped. (decrease the humidity a bit more)
    Manually turn the Fan ON for 15m, 30m, 1, 2, 3, 4, 5, 6 or 12 hours.
    (useful for smelly events...)
    Manually turn the Fan Controller system OFF for 30m, 1, 2, 4, 8 or 12 hours.
    (want to go to bed but the noisy fan is on?  turn it off!)
    Turn the Fan Controller system OFF completely until turning ON manually.
    (vacation time!)
    User settings are stored in EEPROM and preserved after reset/power fail.

USER SETTINGS MENU:

    - Threshold: from 40%RH to 95%RH
    - Hysteresis: from 3%RH to 9%RH
    - Fan off delay: from 0 (NO delay) to 60 minutes.

BUTTONS:

    There are 3 buttons, from top to bottom these are:
    - ON / UP
    - OFF / DOWN
    - SELECT
    - on the side of the case: system RESET button


Explanation of the display of the controller...

    At the top-left on the display you see the CURRENT HUMIDITY value, updated every second. The percent (%) sign will blink to indicate this.
    on the top-right we have the humidity THRESHOLD value.
    below the threshold value you'll see the set HYSTERESIS value (optional)
    at the bottom right, the current TEMPERATURE is displayed.
    at the bottom left a Fan icon will indicate when the Fan is turned on. right of that icon a text 'DELAY' is displayed if the fan-off delay is activated.


Explanation of the system

No event / system IDLE:
The humidity and temperature is measured and updated every second, indicated by a blinking '%' character next to the measured humidity value.

The sensor is *very* sensitive and also *very* accurate!  So it will react fast and reliable on changing conditions. 

    NOTE: If you decide to use a sensor from China then this will be a different matter. Cheap AND reliable/precise is simply not possible. 

 
Event: humidity has risen equal or above the threshold:
When the current humidity reaches the threshold value, the Fan (relais) will switch on, indicated by a FAN SYMBOL at the lower left of the display.
The Fan will stay on until the humidity level has dropped below the threshold *minus the Hysteresis value*.  So if the threshold is 70% and the Hysteresis is 5, then the fan will shut off at 65% Relative humidity. 
NOTE: Obviously the hysteresis is very important! If not used you would have a fan switching off and on around the threshold value.

Event: humidity has dropped below the threshold value *minus Hysteresis*:
When the humidity level drop below the threshold plus hysteresis value, the fan will turn OFF.  Example: threshold=70 and hysteresis=5, then the fan will stop at a threshold level of 65. 
EXCEPT: if you have set a FAN OFF DELAY time then the fan will remain on for a user determined time (menu setting)

Manual interventions:
I purposely build in several useful features not found in commercial controllers (AFAIK). For example: 
- you have made the WC happy but the smell is not to be desired... Then you can turn on the fan manually for a set time.
- You want to go to bed but the fan is on because the humidity level is too high but the noise of the fan is disturbing. Then you can turn the system off for a set time, after which it will continue to measure and switch on when needed. Ventilation is important to keep mould away so this way you can't forget to turn the system on again.
- You are going on holiday: turn the system off completely. This seems obvious but with build in sensors in a fan this is not always possible

Explanation of the BUTTONS

    ON / UP: 
    - SYSTEM IDLE (fan OFF): when pressed the fan will turn ON for a set time, starting at 15 minutes. press UP again to increase fan ON time in pre-determined steps. (Maximum 12 hours)
    - SYSTEM OFF: turn system ON again
    - SYSTEM MANUALY SWITCHED OFF: system returns to SYSTEM IDLE state
    - MENU ACTIVE: when pressed the value is increased, hold to fast increase value.
    OFF / DOWN:
    - SYSTEM IDLE (fan OFF): when pressed, the system will SHUT DOWN for the set time, starting at 30 minutes. Press DOWN again to increase the shut down time in pre-determined steps. (Maximum 12 hours)
    - FAN IS ON or  FAN OFF DELAY active: stop the fan, then same as SYSTEM IDLE
    - ANY STATE (except MENU): when button is pressed for >1 second, the system is turned off completely until being turned ON again by pressing ON button.
    - MENU ACTIVE: when pressed the value is decreased, hold to fast decrease value.
    SELECT:
    when the button is pressed for >1 second, the user MENU is displayed
    - Set threshold: from 40%RH to 95%RH
    - Hysteresis: from 3%RH to 9%RH
    - Fan off delay: from 0 (no delay) to 60 minutes.


I had 2 cheap I2C 128x64pixel OLED screens in a drawer. maybe a bit tiny but way better than a 
20x2 LCD screen... Very bright an crisp displays, these OLED things... 

To get descent fonts, I use the amazing 8U2G font library from Oli Kraus
https://github.com/olikraus/U8g2_Arduino

This font library consumes a *lot* of memory but the result is great... I managed to get all
code in the Arduino Uno (atmega328). 

I maybe over-commented this sketch but I'm a NOT a programma myself so I want to: 1. make changes
in the future easier for myself and 2. help others to understand what the heck the code means.
Experienced programmers may make this sketch way better but it does it's job, that's the beauty
of the Arduino plaform: even beginners can enjoy coding and grow and be more efficient later on.



============ BSD License for Bathroom Fan Controller ============

Copyright (c) 2021, Erik de Ruiter, The Netherlands
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list 
  of conditions and the following disclaimer.
  
* Redistributions in binary form must reproduce the above copyright notice, this 
  list of conditions and the following disclaimer in the documentation and/or other 
  materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  
*/

#include <Arduino.h>
#include <Wire.h>
#include <SPI.h>
#include <EEPROMex.h>
#include <U8g2lib.h>
#include <Adafruit_BME280.h>
#include <OneButton.h>

// *comment-out the #define line below if you don't want to see the
// *Hysteresis value and symbol on the OLED display
#define DISPLAY_HYSTERESIS

// home made icons for the display defined here. I used GIMP: made a new file,
// say 20x20 pixels, used the 'pen' to paint the image. When finished: menu
// [IMAGE]>[crop to selection]. Then menu [FILE]>[export as] and renamed the
// file, CHANGING THE EXTENSION TO .XBM (!)
// Then open this saved file with a text editor and paste all in the sketch.
// NOTE!!: I added in the line starting with 'static' this:
// 'const' and 'U8X8_PROGMEM', see below.

// percent icon
#define percent_width 10
#define percent_height 9
static const unsigned char percent_bits[] U8X8_PROGMEM = {
    0x0c, 0x02, 0x12, 0x01, 0x92, 0x00, 0x4c, 0x00, 0x20, 0x00, 0x90, 0x01,
    0x48, 0x02, 0x44, 0x02, 0x82, 0x01};
// percent icon BLACK (to 'erase' the icon for blinking it)
#define percent_width 10
#define percent_height 9
static const unsigned char black_bits[] U8X8_PROGMEM = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
// degree + celcius icon
#define celcius_width 12
#define celcius_height 13
static const unsigned char celcius_bits[] U8X8_PROGMEM = {
    0x0e, 0x00, 0x91, 0x07, 0x51, 0x08, 0x51, 0x00, 0x4e, 0x00, 0x40, 0x00,
    0x40, 0x00, 0x40, 0x08, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00};
// fan icon
#define fan_width 16
#define fan_height 16
static const unsigned char fan_bits[] U8X8_PROGMEM = {
    0xf0, 0x00, 0xf8, 0x01, 0xf8, 0x03, 0xf0, 0x63, 0xe0, 0xf3, 0xc0, 0xf9,
    0xdc, 0xff, 0x7e, 0xfe, 0x7f, 0x7e, 0xff, 0x3b, 0x9f, 0x03, 0xcf, 0x07,
    0xc6, 0x0f, 0xc0, 0x1f, 0x80, 0x1f, 0x00, 0x0f};
// hand icon for MANUAL_ON mode indicator
#define hand_width 33
#define hand_height 41
static const unsigned char hand_bits[] U8X8_PROGMEM = {
    0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0xc0, 0x07, 0x00, 0x00, 0x00, 0xc0,
    0xc7, 0x01, 0x00, 0x00, 0xc7, 0xe7, 0x03, 0x00, 0x80, 0xcf, 0xe7, 0x03,
    0x00, 0x80, 0xcf, 0xe7, 0x03, 0x00, 0x80, 0xcf, 0xe7, 0xe3, 0x00, 0x80,
    0xcf, 0xe7, 0xf3, 0x01, 0x80, 0xcf, 0xe7, 0xf3, 0x01, 0x80, 0xcf, 0xe7,
    0xf3, 0x01, 0x80, 0xcf, 0xe7, 0xf3, 0x01, 0x80, 0xcf, 0xe7, 0xf3, 0x01,
    0x80, 0xcf, 0xe7, 0xf3, 0x01, 0x80, 0xcf, 0xe7, 0xf3, 0x01, 0x80, 0xcf,
    0xe7, 0xf3, 0x01, 0x8e, 0xcf, 0xe7, 0xf3, 0x01, 0x9f, 0xcf, 0xe7, 0xf3,
    0x01, 0x9f, 0xff, 0xff, 0xf3, 0x01, 0x9f, 0xff, 0xff, 0xff, 0x01, 0xbf,
    0xff, 0xff, 0xff, 0x01, 0xbf, 0xff, 0xff, 0xff, 0x01, 0xff, 0xff, 0xff,
    0xff, 0x01, 0xfe, 0xc3, 0x7f, 0xf8, 0x01, 0xfe, 0x83, 0x3f, 0xf8, 0x01,
    0xfe, 0x03, 0x1f, 0xf8, 0x01, 0xfc, 0x03, 0x0e, 0xf8, 0x01, 0xfc, 0x23,
    0x84, 0xf8, 0x01, 0xfc, 0x63, 0xc0, 0xf8, 0x01, 0xf8, 0xe3, 0xe0, 0xf8,
    0x01, 0xf8, 0xe3, 0xf1, 0xf8, 0x01, 0xf0, 0xe3, 0xfb, 0xf8, 0x01, 0xf0,
    0xe3, 0xff, 0xf8, 0x01, 0xe0, 0xe3, 0xff, 0xf8, 0x00, 0xe0, 0xe3, 0xff,
    0xf8, 0x00, 0xc0, 0xe3, 0xff, 0xf8, 0x00, 0xc0, 0xff, 0xff, 0x7f, 0x00,
    0x80, 0xff, 0xff, 0x7f, 0x00, 0x80, 0xff, 0xff, 0x3f, 0x00, 0x00, 0xff,
    0xff, 0x1f, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0x00, 0x00, 0xfc, 0xff, 0x07,
    0x00};
// up arrow for menu
#define upArrow_width 12
#define upArrow_height 15
static const unsigned char upArrow_bits[] U8X8_PROGMEM = {
    0x60, 0x00, 0xf0, 0x00, 0xf8, 0x01, 0xfc, 0x03, 0xfe, 0x07, 0xff, 0x0f,
    0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01,
    0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01};
// down arrow for menu
#define downArrow_width 12
#define downArrow_height 15
static const unsigned char downArrow_bits[] U8X8_PROGMEM = {
    0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01,
    0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xff, 0x0f, 0xfe, 0x07, 0xfc, 0x03,
    0xf8, 0x01, 0xf0, 0x00, 0x60, 0x00};
// hysteresis icon
#ifdef DISPLAY_HYSTERESIS
#define hysteresis_width 11
#define hysteresis_height 11
static const unsigned char hysteresis_bits[] U8X8_PROGMEM = {
    0xf8, 0x07, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00,
    0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0xff, 0x00};
#endif
// stopwatch icon
#define stopwatch_width 24
#define stopwatch_height 24
static const unsigned char stopwatch_bits[] U8X8_PROGMEM = {
    0x00, 0x7e, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x3c, 0x00, 0x18, 0x18, 0x18,
    0x0c, 0x7e, 0x30, 0x9e, 0x81, 0x79, 0x7a, 0x18, 0x5e, 0x10, 0x00, 0x08,
    0x10, 0x18, 0x08, 0x08, 0x18, 0x10, 0x08, 0x18, 0x10, 0x04, 0x18, 0x20,
    0x04, 0x18, 0x20, 0x14, 0xf8, 0x2b, 0x14, 0xf8, 0x2b, 0x04, 0x00, 0x20,
    0x04, 0x00, 0x20, 0x08, 0x00, 0x10, 0x08, 0x00, 0x10, 0x10, 0x00, 0x08,
    0x10, 0x00, 0x08, 0x60, 0x18, 0x06, 0x80, 0x81, 0x01, 0x00, 0x7e, 0x00};

//  8888888b. 8888888 888b    888                                 .d888 d8b
//  888   Y88b  888   8888b   888                                d88P"  Y8P
//  888    888  888   88888b  888                                888
//  888   d88P  888   888Y88b 888       .d8888b .d88b.  88888b.  888888 888  .d88b.
//  8888888P"   888   888 Y88b888      d88P"   d88""88b 888 "88b 888    888 d88P"88b
//  888         888   888  Y88888      888     888  888 888  888 888    888 888  888
//  888         888   888   Y8888      Y88b.   Y88..88P 888  888 888    888 Y88b 888
//  888       8888888 888    Y888       "Y8888P "Y88P"  888  888 888    888  "Y88888
//                                                                               888
//                                                                          Y8b d88P
//                                                                           "Y88P"

// These are all the Arduino PIN connections... Of course definition of the I2C pins
// A4 and A5 are not needed but added here for convenience.
#define PIN_RELAIS 13
#define PIN_DISPLAY_CLOCK 12
#define PIN_DISPLAY_DATA 11
#define PIN_DISPLAY_CS 10
#define PIN_DISPLAY_DC 9
#define PIN_DISPLAY_RESET 8
#define PIN_BUTTON_DOWN 7
#define PIN_BUTTON_UP 6
#define PIN_BUTTON_SELECT 5
#define PIR_SENSOR 4
#define PIN_I2C_CLOCK A5
#define PIN_I2C_DATA A4

//                            d8b          888      888
//                            Y8P          888      888
//                                         888      888
//  888  888  8888b.  888d888 888  8888b.  88888b.  888  .d88b.  .d8888b
//  888  888     "88b 888P"   888     "88b 888 "88b 888 d8P  Y8b 88K
//  Y88  88P .d888888 888     888 .d888888 888  888 888 88888888 "Y8888b.
//   Y8bd8P  888  888 888     888 888  888 888 d88P 888 Y8b.          X88
//    Y88P   "Y888888 888     888 "Y888888 88888P"  888  "Y8888   88888P'
//

float sensorTemp = 0;
int sensorHumidity = 0;
int sensorHumidityFraction = 0;
byte humidityHysteresis = 5 /*Rel.Humidity*/;
byte humidityThreshold = 0;

bool btnSelectClickEvent = false;
bool btnUpClickEvent = false;
bool btnDownClickEvent = false;
bool btnSelectHoldEvent = false;
bool btnUpHoldEvent = false;
bool btnDownHoldEvent = false;
bool btnUpDuringHoldEvent = false;
bool btnDownDuringHoldEvent = false;

// humidity/temperature sensor
unsigned long previousSensorReadTime = 0;
bool sensorIsRead = false;
bool humidityLevelTooHigh = false;

// PIR sensor
unsigned long previousPIRsensorReadTime = 0;
bool turnOledDisplayOFF = false;

unsigned int fanCountdown = 0;
unsigned int fanRunTime = 0;
unsigned int fanRunStartTime = 0;
unsigned int fanManualOnRunTime = 15 /*minutes*/;

unsigned int fanDisabledTime = 0;
unsigned int fanDisabledStartTime = 0;
unsigned int fanDisabledRunTime = 30 /*minutes*/;

byte fanSwitchOffDelayTime = 30 /*minutes*/;

int timeHours = 0;
int timeMinutes = 0;
int timeSeconds = 0;

char bufferTime[6];

// set Arduino Uno EEPROM membase to store the user data
const int memBase = 350;

//           888       d8b                   888
//           888       Y8P                   888
//           888                             888
//   .d88b.  88888b.  8888  .d88b.   .d8888b 888888 .d8888b
//  d88""88b 888 "88b "888 d8P  Y8b d88P"    888    88K
//  888  888 888  888  888 88888888 888      888    "Y8888b.
//  Y88..88P 888 d88P  888 Y8b.     Y88b.    Y88b.       X88
//   "Y88P"  88888P"   888  "Y8888   "Y8888P  "Y888  88888P'
//                     888
//                    d88P
//                  888P"

// Setup new OneButton Objects
OneButton buttonSelect(/*PIN*/ PIN_BUTTON_SELECT, /*INPUT_PULLUP*/ true);
OneButton buttonUP(/*PIN*/ PIN_BUTTON_UP, /*INPUT_PULLUP*/ true);
OneButton buttonDown(/*PIN*/ PIN_BUTTON_DOWN, /*INPUT_PULLUP*/ true);

// Object OLED screen 128x64 pixels with SPI interface
// ! NOTE:  In my case connecting the display using I2C resulted in erratic behaviour
// ! due to static electricity(??). SPI proved to be much more stable in my case.
//
// NOTE2: Some displays support FLIP MODE so you can rotate the display output.
// change the 'U8G2_R0' in the constructor below:
//
// U8G2_R0 = no rotation,
// U8G2_R1 = 90 degree clockwise rotation,
// U8G2_R2 = 180 degree clockwise rotation,
// U8G2_R3 = 270 degree clockwise rotation.
//
U8G2_SSD1306_128X64_NONAME_F_4W_SW_SPI u8g2(U8G2_R0,
                                            /* clock=*/PIN_DISPLAY_CLOCK,
                                            /* data=*/PIN_DISPLAY_DATA,
                                            /* cs=*/PIN_DISPLAY_CS,
                                            /* dc=*/PIN_DISPLAY_DC,
                                            /* reset=*/PIN_DISPLAY_RESET);

// Object BME280 sensor
Adafruit_BME280 bme; // I2C

// State Machine States
typedef enum FSM
{
  IDLE_FAN_OFF,
  FAN_ON,
  MANUAL_ON,
  MANUAL_OFF,
  DISABLE,
  FAN_OFF_DELAY,
  SET_THRESHOLD,
  SET_HYSTERESIS,
  SET_SWITCH_OFF_DELAY,
} FSM;

FSM state = IDLE_FAN_OFF; // no action when starting

//   .d8888b.           888                        .d88 88b.
//  d88P  Y88b          888                       d88P" "Y88b
//  Y88b.               888                      d88P     Y88b
//   "Y888b.    .d88b.  888888 888  888 88888b.  888       888
//      "Y88b. d8P  Y8b 888    888  888 888 "88b 888       888
//        "888 88888888 888    888  888 888  888 Y88b     d88P
//  Y88b  d88P Y8b.     Y88b.  Y88b 888 888 d88P  Y88b. .d88P
//   "Y8888P"   "Y8888   "Y888  "Y88888 88888P"    "Y88 88P"
//                                      888
//                                      888
//                                      888
/******************************************************************************/
void setup()
{
  bme.begin();
  u8g2.begin();
  Wire.begin();
  /*clockFrequency: the value (in Hertz) of desired communication clock. 
  Accepted values are 100000 (standard mode) and 400000 (fast mode). 
  Some processors also support 10000 (low speed mode), 
  1000000 (fast mode plus) and 3400000 (high speed mode). 
  Please refer to the specific processor documentation to make sure 
  the desired mode is supported. */
  // if you set at 400000, display will mess up occasionally
  Wire.setClock(100000);

  pinMode(PIN_DISPLAY_RESET, OUTPUT);
  pinMode(PIN_RELAIS, OUTPUT);
  pinMode(PIR_SENSOR, INPUT);

  // Buttons...
  // link the myClickFunction function to be called on a button click event.
  buttonSelect.attachClick(buttonSelectClick);
  buttonUP.attachClick(buttonUpClick);
  buttonDown.attachClick(buttonDownClick);

  // link the myClickFunction function to be called on a button hold event.
  buttonUP.attachDuringLongPress(buttonUpDuringLongPress);
  buttonDown.attachDuringLongPress(buttonDownDuringLongPress);

  // link the myClickFunction function to be called on a button START hold event.
  buttonSelect.attachLongPressStart(buttonSelectLongPress);
  buttonUP.attachLongPressStart(buttonUpLongPress);
  buttonDown.attachLongPressStart(buttonDownLongPress);

  // set 50 msec. debouncing time. Default is 50 msec.
  buttonSelect.setDebounceTicks(50);
  buttonUP.setDebounceTicks(50);
  buttonDown.setDebounceTicks(50);

  // read EEPROM values. new memory often has 255 as memory content so we perform a rudimentary
  // check to see if the memory locations has never been used before. if so, set default values
  // memBase is the start EEPROM address (see variables)
  // An Interger value take 2 Bytes to store it in EEPROM so we need to take that in account.
  EEPROM.readInt(memBase) > 100 ? humidityThreshold = 65 : humidityThreshold = EEPROM.readInt(memBase);
  EEPROM.readInt(memBase + 2) > 10 ? humidityHysteresis = 4 : humidityHysteresis = EEPROM.readInt(memBase + 2);
  EEPROM.readInt(memBase + 4) > 60 ? fanSwitchOffDelayTime = 30 : fanSwitchOffDelayTime = EEPROM.readInt(memBase + 4);
}

//  888                                  .d88 88b.
//  888                                 d88P" "Y88b
//  888                                d88P     Y88b
//  888      .d88b.   .d88b.  88888b.  888       888
//  888     d88""88b d88""88b 888 "88b 888       888
//  888     888  888 888  888 888  888 Y88b     d88P
//  888     Y88..88P Y88..88P 888 d88P  Y88b. .d88P
//  88888888 "Y88P"   "Y88P"  88888P"    "Y88 88P"
//                            888
//                            888
//                            888

/******************************************************************************/
void loop()
{
  // keep watching the push button:
  buttonSelect.tick();
  buttonUP.tick();
  buttonDown.tick();
  // update sensor IDLE_FAN_OFFment
  readSensor();
  //check PIR sensor
  checkPIRsensor();
  // run Finite State Machine
  runFSM();
}

//  8888888888   .d8888b.      888b     d888
//  888         d88P  Y88b     8888b   d8888
//  888         Y88b.          88888b.d88888
//  8888888      "Y888b.       888Y88888P888
//  888             "Y88b.     888 Y888P 888
//  888               "888     888  Y8P  888
//  888     d8b Y88b  d88P d8b 888   "   888 d8b
//  888     Y8P  "Y8888P"  Y8P 888       888 Y8P
//
/******************************************************************************/
void runFSM()
{
  switch (state)
  {
    //  d8b      888 888
    //  Y8P      888 888
    //           888 888
    //  888  .d88888 888  .d88b.
    //  888 d88" 888 888 d8P  Y8b
    //  888 888  888 888 88888888
    //  888 Y88b 888 888 Y8b.
    //  888  "Y88888 888  "Y8888
    //
    /***************************************************************************/
  case IDLE_FAN_OFF:
    // default state, show main display
    displayUpdate();

    //check for need to turn fan on
    checkHumidityLevel();
    if (humidityLevelTooHigh == true)
    {
      state = FAN_ON;
    }

    // check if MANUAL_ON mode is required ('ON' button click event)
    if (btnUpClickEvent == true)
    {
      // set Fan start timer before switching state
      fanRunStartTime /*=seconds*/ = (millis() / 1000);
      state = MANUAL_ON;
    }

    // check if MANUAL_OFF mode is required, so turning OFF the humidity
    // controller for a selected time
    if (btnDownClickEvent == true)
    {
      // set Fan start timer before switching state
      fanDisabledStartTime /*=seconds*/ = (millis() / 1000);
      state = MANUAL_OFF;
    }

    // check if DOWN button is being hold, turning OFF the system
    if (btnDownHoldEvent == true)
    {
      state = DISABLE;
    }

    if (btnSelectHoldEvent == true)
    {
      state = SET_THRESHOLD;
    }

    // reset all button events
    // * You NEED to have this button reset code in every state else the
    // * button event(s) will transfer over to the new state with unwanted resuls
    btnSelectClickEvent = false;
    btnSelectHoldEvent = false;
    btnUpClickEvent = false;
    btnUpHoldEvent = false;
    btnDownClickEvent = false;
    btnDownHoldEvent = false;
    btnUpDuringHoldEvent = false;
    btnDownDuringHoldEvent = false;

    break;

    //   .d888
    //  d88P"
    //  888
    //  888888 8888b.  88888b.        .d88b.  88888b.
    //  888       "88b 888 "88b      d88""88b 888 "88b
    //  888   .d888888 888  888      888  888 888  888
    //  888   888  888 888  888      Y88..88P 888  888
    //  888   "Y888888 888  888       "Y88P"  888  888
    //
  /***************************************************************************/
  case FAN_ON:
    displayUpdate();
    turnFanOn();
    checkHumidityLevel();

    //check for need to turn fan off
    if (humidityLevelTooHigh == false)
    {
      // set Fan off-delay timer before switching state
      fanRunStartTime /*=seconds*/ = (millis() / 1000);
      state = FAN_OFF_DELAY;
    }

    if (btnSelectHoldEvent == true)
    {
      turnFanOff();
      state = SET_THRESHOLD;
    }

    // check if MANUAL_OFF mode is required, so turning OFF the humidity
    // controller for a selected time
    if (btnDownClickEvent == true)
    {
      // set Fan start timer before switching state
      fanDisabledStartTime /*=seconds*/ = (millis() / 1000);
      state = MANUAL_OFF;
    }
    // check if DOWN button is being hold, turning OFF the system
    if (btnDownHoldEvent == true)
    {
      state = DISABLE;
    }

    // reset all button events
    // * You NEED to have this button reset code in every state else the
    // * button event(s) will transfer over to the new state with unwanted resuls
    btnSelectClickEvent = false;
    btnSelectHoldEvent = false;
    btnUpClickEvent = false;
    btnUpHoldEvent = false;
    btnDownClickEvent = false;
    btnDownHoldEvent = false;
    btnUpDuringHoldEvent = false;
    btnDownDuringHoldEvent = false;

    break;

    //                                                    888
    //                                                    888
    //                                                    888
    //  88888b.d88b.   8888b.  88888b.  888  888  8888b.  888       .d88b.  88888b.
    //  888 "888 "88b     "88b 888 "88b 888  888     "88b 888      d88""88b 888 "88b
    //  888  888  888 .d888888 888  888 888  888 .d888888 888      888  888 888  888
    //  888  888  888 888  888 888  888 Y88b 888 888  888 888      Y88..88P 888  888
    //  888  888  888 "Y888888 888  888  "Y88888 "Y888888 888       "Y88P"  888  888
    //
  /***************************************************************************/
  case MANUAL_ON:
    displayUpdate();
    turnFanOn();
    // fanRunStartTime was set to the current millis() value in the previous State
    // so now we can compare this 'start' time with the time passed using the current
    // millis() value. the max. time possible is 12 hours which is 12hx3600s=43200s
    // so it will fit in the unsigned INT variables.  If you want longer run times,
    // be sure to use LONG variables.
    fanRunTime /*=seconds*/ = (millis() / 1000) - (fanRunStartTime /*=seconds*/);

    // check the FAN 'ON' duration timer
    if ((fanRunTime /*=seconds*/ / 60) /*=converted to minutes*/ >= fanManualOnRunTime /*=minutes*/)
    {
      turnFanOff();
      // reset to default value before exit
      fanManualOnRunTime = 15;
      // go to new state
      state = IDLE_FAN_OFF;
    }

    // check if we want to exit the MANUAL ON mode
    if (btnDownClickEvent == true)
    {
      turnFanOff();
      // reset to default value before exit
      fanManualOnRunTime = 15;
      // go to new state
      state = IDLE_FAN_OFF;
    }

    // check if DOWN button is being hold, turning OFF the system
    if (btnDownHoldEvent == true)
    {
      // reset to default value before exit
      fanManualOnRunTime = 15;
      // go to new state
      state = DISABLE;
    }

    // if UP button is pressed while in MANUAL_ON mode, cycle through different off delay times
    if (btnUpClickEvent == true)
    {
      switch (fanManualOnRunTime)
      {
      case 15:
        fanManualOnRunTime = 30;
        break;
      case 30:
        fanManualOnRunTime = 60;
        break;
      case 60:
        fanManualOnRunTime = 90;
        break;
      case 90:
        fanManualOnRunTime = 120;
        break;
      case 120:
        fanManualOnRunTime = 180;
        break;
      case 180:
        fanManualOnRunTime = 240;
        break;
      case 240:
        fanManualOnRunTime = 300;
        break;
      case 300:
        fanManualOnRunTime = 360;
        break;
      case 360:
        fanManualOnRunTime = 720;
        break;
      case 720:
        fanManualOnRunTime = 15;
        break;
      }
    }

    // reset all button events
    // * You NEED to have this button reset code in every state else the
    // * button event(s) will transfer over to the new state with unwanted resuls
    btnSelectClickEvent = false;
    btnSelectHoldEvent = false;
    btnUpClickEvent = false;
    btnUpHoldEvent = false;
    btnDownClickEvent = false;
    btnDownHoldEvent = false;
    btnUpDuringHoldEvent = false;
    btnDownDuringHoldEvent = false;

    break; //case MANUAL_ON

    //                                                    888                .d888  .d888
    //                                                    888               d88P"  d88P"
    //                                                    888               888    888
    //  88888b.d88b.   8888b.  88888b.  888  888  8888b.  888       .d88b.  888888 888888
    //  888 "888 "88b     "88b 888 "88b 888  888     "88b 888      d88""88b 888    888
    //  888  888  888 .d888888 888  888 888  888 .d888888 888      888  888 888    888
    //  888  888  888 888  888 888  888 Y88b 888 888  888 888      Y88..88P 888    888
    //  888  888  888 "Y888888 888  888  "Y88888 "Y888888 888       "Y88P"  888    888
    //
    //
  /***************************************************************************/
  case MANUAL_OFF:
    displayUpdate();
    turnFanOff();
    // fanRunStartTime was set to the current millis() value in the previous State
    // so now we can compare this 'start' time with the time passed using the current
    // millis() value. the max. time possible is 12 hours which is 12hx3600s=43200s
    // so it will fit in the unsigned INT variables.  If you want longer run times,
    // be sure to use LONG variables.
    fanDisabledTime /*=seconds*/ = (millis() / 1000) - (fanDisabledStartTime /*=seconds*/);

    // check the FAN 'OFF' duration timer
    if ((fanDisabledTime /*=seconds*/ / 60) /*=converted to minutes*/ >= fanDisabledRunTime /*=minutes*/)
    {
      // reset to default value before exit
      fanDisabledRunTime = 30;
      // go to new state
      state = IDLE_FAN_OFF;
    }

    // check if return to normal operational mode is required ('ON' button click event)
    if (btnUpClickEvent == true)
    {
      // reset to default value before exit
      fanDisabledRunTime = 30;
      // go to new state
      state = IDLE_FAN_OFF;
    }

    // check if DOWN button is being hold, turning OFF the system
    if (btnDownHoldEvent == true)
    {
      // reset to default value before exit
      fanDisabledRunTime = 30;
      // go to new state
      state = DISABLE;
    }

    // if DOWN button is pressed while in MANUAL_OFF mode, cycle through different off delay times
    if (btnDownClickEvent == true)
    {
      switch (fanDisabledRunTime)
      {
      case 30:
        fanDisabledRunTime = 60;
        break;
      case 60:
        fanDisabledRunTime = 120;
        break;
      case 120:
        fanDisabledRunTime = 240;
        break;
      case 240:
        fanDisabledRunTime = 480;
        break;
      case 480:
        fanDisabledRunTime = 720;
        break;
      case 720:
        fanDisabledRunTime = 30;
        break;
      }
    }

    // reset all button events
    // * You NEED to have this button reset code in every state else the
    // * button event(s) will transfer over to the new state with unwanted resuls
    btnSelectClickEvent = false;
    btnSelectHoldEvent = false;
    btnUpClickEvent = false;
    btnUpHoldEvent = false;
    btnDownClickEvent = false;
    btnDownHoldEvent = false;
    btnUpDuringHoldEvent = false;
    btnDownDuringHoldEvent = false;
    break;

    //       888 d8b                   888      888
    //       888 Y8P                   888      888
    //       888                       888      888
    //   .d88888 888 .d8888b   8888b.  88888b.  888  .d88b.
    //  d88" 888 888 88K          "88b 888 "88b 888 d8P  Y8b
    //  888  888 888 "Y8888b. .d888888 888  888 888 88888888
    //  Y88b 888 888      X88 888  888 888 d88P 888 Y8b.
    //   "Y88888 888  88888P' "Y888888 88888P"  888  "Y8888
    //
  /***************************************************************************/
  case DISABLE:
    displayUpdate();
    turnFanOff();
    // check if UP button is clicked to turn the system on again
    if (btnUpClickEvent == true)
    {
      // go to new state
      state = IDLE_FAN_OFF;
    }
    // reset all button events
    // * You NEED to have this button reset code in every state else the
    // * button event(s) will transfer over to the new state with unwanted resuls
    btnSelectClickEvent = false;
    btnSelectHoldEvent = false;
    btnUpClickEvent = false;
    btnUpHoldEvent = false;
    btnDownClickEvent = false;
    btnDownHoldEvent = false;
    btnUpDuringHoldEvent = false;
    btnDownDuringHoldEvent = false;

    break;

    //   .d888                                 .d888  .d888           888          888
    //  d88P"                                 d88P"  d88P"            888          888
    //  888                                   888    888              888          888
    //  888888 8888b.  88888b.        .d88b.  888888 888888       .d88888  .d88b.  888  8888b.  888  888
    //  888       "88b 888 "88b      d88""88b 888    888         d88" 888 d8P  Y8b 888     "88b 888  888
    //  888   .d888888 888  888      888  888 888    888         888  888 88888888 888 .d888888 888  888
    //  888   888  888 888  888      Y88..88P 888    888         Y88b 888 Y8b.     888 888  888 Y88b 888
    //  888   "Y888888 888  888       "Y88P"  888    888          "Y88888  "Y8888  888 "Y888888  "Y88888
    //                                                                                               888
    //                                                                                          Y8b d88P
  /***************************************************************************/
  case FAN_OFF_DELAY:
    displayUpdate();

    fanRunTime /*=seconds*/ = (millis() / 1000) - (fanRunStartTime /*=seconds*/);

    // check if humidity level did rise above the threshold level *during* delay.
    // if so, cancel FAN_OFF_DELAY and go to FAN_ON state again.
    checkHumidityLevel();
    if (humidityLevelTooHigh == true)
    {
      state = FAN_ON;
    }

    // check the FAN off-delay timer
    if ((fanRunTime /*=seconds*/ / 60) >= fanSwitchOffDelayTime /*=minutes*/)
    {
      turnFanOff();
      // go to new state
      state = IDLE_FAN_OFF;
    }

    // check if MANUAL_ON fan off is equired ('OFF' button click event)
    if (btnDownClickEvent == true)
    {
      turnFanOff();
      // go to new state
      state = IDLE_FAN_OFF;
    }

    // check if DOWN button is being hold, turning OFF the system
    if (btnDownHoldEvent == true)
    {
      state = DISABLE;
    }

    // reset all button events
    // * You NEED to have this button reset code in every state else the
    // * button event(s) will transfer over to the new state with unwanted resuls
    btnSelectClickEvent = false;
    btnSelectHoldEvent = false;
    btnUpClickEvent = false;
    btnUpHoldEvent = false;
    btnDownClickEvent = false;
    btnDownHoldEvent = false;
    btnUpDuringHoldEvent = false;
    btnDownDuringHoldEvent = false;
    break;

    //                    888         888    888                               888               888      888
    //                    888         888    888                               888               888      888
    //                    888         888    888                               888               888      888
    //  .d8888b   .d88b.  888888      888888 88888b.  888d888 .d88b.  .d8888b  88888b.   .d88b.  888  .d88888
    //  88K      d8P  Y8b 888         888    888 "88b 888P"  d8P  Y8b 88K      888 "88b d88""88b 888 d88" 888
    //  "Y8888b. 88888888 888         888    888  888 888    88888888 "Y8888b. 888  888 888  888 888 888  888
    //       X88 Y8b.     Y88b.       Y88b.  888  888 888    Y8b.          X88 888  888 Y88..88P 888 Y88b 888
    //   88888P'  "Y8888   "Y888       "Y888 888  888 888     "Y8888   88888P' 888  888  "Y88P"  888  "Y88888
    //
    //
  /***************************************************************************/
  case SET_THRESHOLD:
    displayUpdate();

    if ((btnUpClickEvent == true || btnUpDuringHoldEvent == true) && humidityThreshold < 95)
    {
      humidityThreshold += 1;
    }
    if ((btnDownClickEvent == true || btnDownDuringHoldEvent == true) && humidityThreshold > 40)
    {
      humidityThreshold -= 1;
    }
    if (btnSelectClickEvent == true)
    {
      // save to eeprom
      EEPROM.writeInt(memBase, humidityThreshold);
      // go to new state, next menu item
      state = SET_HYSTERESIS;
    }

    // reset all button events
    // * You NEED to have this button reset code in every state else the
    // * button event(s) will transfer over to the new state with unwanted resuls
    btnSelectClickEvent = false;
    btnSelectHoldEvent = false;
    btnUpClickEvent = false;
    btnUpHoldEvent = false;
    btnDownClickEvent = false;
    btnDownHoldEvent = false;
    btnUpDuringHoldEvent = false;
    btnDownDuringHoldEvent = false;
    break;

    //                    888         888                        888
    //                    888         888                        888
    //                    888         888                        888
    //  .d8888b   .d88b.  888888      88888b.  888  888 .d8888b  888888 .d88b.  888d888
    //  88K      d8P  Y8b 888         888 "88b 888  888 88K      888   d8P  Y8b 888P"
    //  "Y8888b. 88888888 888         888  888 888  888 "Y8888b. 888   88888888 888
    //       X88 Y8b.     Y88b.       888  888 Y88b 888      X88 Y88b. Y8b.     888  d8b
    //   88888P'  "Y8888   "Y888      888  888  "Y88888  88888P'  "Y888 "Y8888  888  Y8P
    //                                              888
    //                                         Y8b d88P
    //                                          "Y88P"
  /***************************************************************************/
  case SET_HYSTERESIS:
    displayUpdate();

    if (btnUpClickEvent == true && humidityHysteresis <= 8)
    {
      humidityHysteresis += 1;
    }
    if (btnDownClickEvent == true && humidityHysteresis >= 4)
    {
      humidityHysteresis -= 1;
    }
    if (btnSelectClickEvent == true)
    {
      // save to eeprom
      EEPROM.writeInt(memBase + 2, humidityHysteresis);
      // go to new state, next menu item
      state = SET_SWITCH_OFF_DELAY;
    }

    // reset all button events
    // * You NEED to have this button reset code in every state else the
    // * button event(s) will transfer over to the new state with unwanted resuls
    btnSelectClickEvent = false;
    btnSelectHoldEvent = false;
    btnUpClickEvent = false;
    btnUpHoldEvent = false;
    btnDownClickEvent = false;
    btnDownHoldEvent = false;
    btnUpDuringHoldEvent = false;
    btnDownDuringHoldEvent = false;
    break;

    //                    888                   .d888  .d888           888          888
    //                    888                  d88P"  d88P"            888          888
    //                    888                  888    888              888          888
    //  .d8888b   .d88b.  888888       .d88b.  888888 888888       .d88888  .d88b.  888  8888b.  888  888
    //  88K      d8P  Y8b 888         d88""88b 888    888         d88" 888 d8P  Y8b 888     "88b 888  888
    //  "Y8888b. 88888888 888         888  888 888    888         888  888 88888888 888 .d888888 888  888
    //       X88 Y8b.     Y88b.       Y88..88P 888    888         Y88b 888 Y8b.     888 888  888 Y88b 888
    //   88888P'  "Y8888   "Y888       "Y88P"  888    888          "Y88888  "Y8888  888 "Y888888  "Y88888
    //                                                                                                888
    //                                                                                           Y8b d88P
    //                                                                                            "Y88P"
  /***************************************************************************/
  case SET_SWITCH_OFF_DELAY:
    displayUpdate();

    if ((btnUpClickEvent == true || btnUpDuringHoldEvent) && fanSwitchOffDelayTime < 60)
    {
      fanSwitchOffDelayTime += 1;
    }
    if ((btnDownClickEvent == true || btnDownDuringHoldEvent) && fanSwitchOffDelayTime > 0)
    {
      fanSwitchOffDelayTime -= 1;
    }
    if (btnSelectClickEvent == true)
    {
      // save to eeprom
      EEPROM.writeInt(memBase + 4, fanSwitchOffDelayTime);
      // exit menu and return
      state = IDLE_FAN_OFF;
    }

    // reset all button events
    // * You NEED to have this button reset code in every state else the
    // * button event(s) will transfer over to the new state with unwanted resuls
    btnSelectClickEvent = false;
    btnSelectHoldEvent = false;
    btnUpClickEvent = false;
    btnUpHoldEvent = false;
    btnDownClickEvent = false;
    btnDownHoldEvent = false;
    btnUpDuringHoldEvent = false;
    btnDownDuringHoldEvent = false;
    break;
  }
}
//
//
//
//  888888 888888 888888 888888 888888 888888 888888 888888 888888 888888 888888 888888 888888 888888 888888
//
//  888888 888888 888888 888888 888888 888888 888888 888888 888888 888888 888888 888888 888888 888888 888888
//
//                                888
//                                888
//                                888
//  888d888 .d88b.   8888b.   .d88888      .d8888b   .d88b.  88888b.  .d8888b   .d88b.  888d888
//  888P"  d8P  Y8b     "88b d88" 888      88K      d8P  Y8b 888 "88b 88K      d88""88b 888P"
//  888    88888888 .d888888 888  888      "Y8888b. 88888888 888  888 "Y8888b. 888  888 888
//  888    Y8b.     888  888 Y88b 888           X88 Y8b.     888  888      X88 Y88..88P 888
//  888     "Y8888  "Y888888  "Y88888       88888P'  "Y8888  888  888  88888P'  "Y88P"  888
//
//
/***************************************************************************/
// read sensor value each second
void readSensor()
{
  if ((millis() - previousSensorReadTime) > 1000)
  {
    // blink % symbol to indicate sensor IDLE_FAN_OFFment
    // each second, the variable boolean value is inverted.
    sensorIsRead = !sensorIsRead;

    sensorTemp = bme.readTemperature();
    /* 
    read integer part of humidity value. Because the sensorHumidity variable is
    defined as integer, the value after the decinal point is always ignored!
    */
    sensorHumidity = bme.readHumidity();
    /*
    now we want to get the fraction of humidity value to round off to a whole number.
    This is done by multiplying the float value by 10 and the result is forced to
    become an integer by using the (int)code before the calculation. Then we use
    the modulo operator to see what was the value after the decimal point and
    use that to round the sensorHumidity value to nearest whole number
    */
    sensorHumidityFraction = (int)(bme.readHumidity() * 10) % 10;
    if (sensorHumidityFraction >= 5)
    {
      sensorHumidity += 1;
    }
    //reset counter
    previousSensorReadTime = millis();
  }
}

//  8888888b. 8888888 8888888b.                                                            
//  888   Y88b  888   888   Y88b                                                           
//  888    888  888   888    888                                                           
//  888   d88P  888   888   d88P      .d8888b   .d88b.  88888b.  .d8888b   .d88b.  888d888 
//  8888888P"   888   8888888P"       88K      d8P  Y8b 888 "88b 88K      d88""88b 888P"   
//  888         888   888 T88b        "Y8888b. 88888888 888  888 "Y8888b. 888  888 888     
//  888         888   888  T88b            X88 Y8b.     888  888      X88 Y88..88P 888     
//  888       8888888 888   T88b       88888P'  "Y8888  888  888  88888P'  "Y88P"  888     
//                                                                                         
void checkPIRsensor()
{
  // only check after delay has passed. Only turn OFF display after set delay
  // else the display would be ON for only 1 second after no movement has been
  // detected.
  if (digitalRead(PIR_SENSOR) == HIGH)
  {
    turnOledDisplayOFF = false;
  }
  else if ((millis() - previousPIRsensorReadTime) > 60000L)
  {
    turnOledDisplayOFF = true;
    //reset counter
    previousPIRsensorReadTime = millis();
  }
}

//           888                        888           888                             d8b      888
//           888                        888           888                             Y8P      888
//           888                        888           888                                      888
//   .d8888b 88888b.   .d88b.   .d8888b 888  888      88888b.  888  888 88888b.d88b.  888  .d88888
//  d88P"    888 "88b d8P  Y8b d88P"    888 .88P      888 "88b 888  888 888 "888 "88b 888 d88" 888
//  888      888  888 88888888 888      888888K       888  888 888  888 888  888  888 888 888  888
//  Y88b.    888  888 Y8b.     Y88b.    888 "88b      888  888 Y88b 888 888  888  888 888 Y88b 888 d8b
//   "Y8888P 888  888  "Y8888   "Y8888P 888  888      888  888  "Y88888 888  888  888 888  "Y88888 Y8P
//
/***************************************************************************/
void checkHumidityLevel()
{
  if (sensorHumidity >= humidityThreshold)
  {
    humidityLevelTooHigh = true;
  }
  else if (sensorHumidity <= (humidityThreshold - humidityHysteresis))
  {
    humidityLevelTooHigh = false;
  }
}

//       888 d8b                   888
//       888 Y8P                   888
//       888                       888
//   .d88888 888 .d8888b  88888b.  888  8888b.  888  888
//  d88" 888 888 88K      888 "88b 888     "88b 888  888
//  888  888 888 "Y8888b. 888  888 888 .d888888 888  888
//  Y88b 888 888      X88 888 d88P 888 888  888 Y88b 888
//   "Y88888 888  88888P' 88888P"  888 "Y888888  "Y88888
//                        888                        888
//                        888                   Y8b d88P
//                        888                    "Y88P"

void displayUpdate()
{
  /***************************************************************************/
  // clear screen buffer
  u8g2.clearBuffer(); // clear the internal memory

  /***************************************************************************/
  // display lines
  if (state == IDLE_FAN_OFF ||
      state == FAN_OFF_DELAY ||
      state == FAN_ON ||
      state == MANUAL_ON)
  {
    // draw lines
    u8g2.drawHLine(80, 46, 48);
    u8g2.drawVLine(79, 0, 64);
  }
  /***************************************************************************/
  //display SET humidity value
  if (state == IDLE_FAN_OFF ||
      state == FAN_OFF_DELAY ||
      state == FAN_ON)
  {
    u8g2.setFont(u8g2_font_helvR24_tn);
    u8g2.setCursor(82, 24);
    u8g2.print(humidityThreshold);
    // percent symbol for set humidity value
    u8g2.drawXBMP(118, 0, percent_width, percent_height, percent_bits);
  }
/***************************************************************************/
#ifdef DISPLAY_HYSTERESIS
  //display humidity hysteresis value
  if (state == IDLE_FAN_OFF ||
      state == FAN_OFF_DELAY ||
      state == FAN_ON)
  {
    u8g2.setFont(u8g2_font_helvB12_tn);
    u8g2.setCursor(106, 41);
    u8g2.print(humidityHysteresis);
    // hysteresis symbol
    u8g2.drawXBMP(117, 29, hysteresis_width, hysteresis_height, hysteresis_bits);
  }
#endif
  /***************************************************************************/
  // display ACTUAL humidity
  if (state == IDLE_FAN_OFF ||
      state == FAN_OFF_DELAY ||
      state == FAN_ON ||
      state == MANUAL_ON)
  {
    u8g2.setFont(u8g2_font_fub42_tn);
    u8g2.setCursor(0, 43);
    u8g2.print(sensorHumidity);
    // percent symbol for current humidity value, blinking
    u8g2.drawXBMP(65, 0, percent_width, percent_height, (sensorIsRead == 0 ? black_bits : percent_bits));
  }
  /***************************************************************************/
  // display temperature
  if (state == IDLE_FAN_OFF ||
      state == FAN_OFF_DELAY ||
      state == FAN_ON ||
      state == MANUAL_ON)
  {
    u8g2.setFont(u8g2_font_helvB12_tn);
    u8g2.setCursor(83, 64);
    u8g2.print((sensorTemp), 1);
    // degrees celcius symbol for temperature value
    u8g2.drawXBMP(115, 52, celcius_width, celcius_height, celcius_bits);
  }
  /***************************************************************************/
  // fan symbol
  if (state == FAN_ON ||
      state == FAN_OFF_DELAY ||
      state == MANUAL_ON)
  {
    u8g2.drawXBMP(0, 48, fan_width, fan_height, fan_bits);
  }
  /***************************************************************************/
  // MANUAL_ON mode Fan ON delay time
  if (state == MANUAL_ON)
  {
    fanCountdown /*seconds*/ = (fanManualOnRunTime * 60 /*minutes->seconds*/) -
                               fanRunTime /*seconds*/;

    timeHours = fanCountdown /*=seconds*/ / 3600;
    timeMinutes = (fanCountdown /*=seconds*/ / 60) % 60;
    //timeSeconds = fanCountdown % 60;

    // display FAN ON duration
    u8g2.setFont(u8g2_font_helvB12_tn);
    // make string to display with leading zero's for hours and minutes
    sprintf(bufferTime, "%02d:%02d", timeHours, timeMinutes);
    // display sprintf string
    u8g2.drawStr(24, 64, bufferTime);
  }
  /***************************************************************************/
  // MANUAL_OFF mode, display delay time
  if (state == MANUAL_OFF)
  {

    fanCountdown /*seconds*/ = (fanDisabledRunTime * 60 /*minutes->seconds*/) -
                               fanDisabledTime /*seconds*/;

    timeHours = fanCountdown /*=seconds*/ / 3600;
    timeMinutes = (fanCountdown /*=seconds*/ / 60) % 60;
    u8g2.setFont(u8g2_font_helvR10_tr);
    u8g2.drawStr(3, 15, "Temp. Switched Off");
    u8g2.drawRFrame(0, 23, 128, 40, 3);
    // display FAN OFF duration
    u8g2.setFont(u8g2_font_helvR24_tn);
    // make string to display with leading zero's for hours and minutes
    sprintf(bufferTime, "%02d:%02d", timeHours, timeMinutes);
    // display sprintf string
    u8g2.drawStr(9, 55, bufferTime);
    // draw stopwatch symbol next to the countdown time
    u8g2.drawXBMP(96, 31, stopwatch_width, stopwatch_height, stopwatch_bits);
  }
  /***************************************************************************/
  // AUTO mode Fan off-delay time
  if (state == FAN_OFF_DELAY)
  {
    // display 'DELAY' to indicate Fan will run for set time before turning off
    u8g2.setFont(u8g2_font_helvR12_tr);
    // display sprintf string
    u8g2.drawStr(21, 63, "DELAY");
  }
  /***************************************************************************/
  // DISABLE mode, system shut down
  if (state == DISABLE)
  {
    // display 'DELAY' to indicate Fan will run for set time before turning off
    u8g2.setFont(u8g2_font_helvR24_tr);
    u8g2.drawStr(44, 44, "Off");
  }
  /***************************************************************************/
  // display MANUAL_ON mode symbol
  if (state == MANUAL_ON)
  {
    u8g2.drawXBMP(88, 0, hand_width, hand_height, hand_bits);
  }
  /***************************************************************************/
  // display FAN_DELAY set menu
  if (state == SET_THRESHOLD)
  {
    u8g2.setFont(u8g2_font_helvR12_tr);

    u8g2.drawStr(0, 13, "Set threshold");
    //u8g2.drawHLine(0, 15, 127);
    u8g2.drawRFrame(4, 23, 77, 40, 3);
    u8g2.drawXBMP(90, 25, upArrow_width, upArrow_height, upArrow_bits);
    u8g2.drawXBMP(90, 46, downArrow_width, downArrow_height, downArrow_bits);
    u8g2.setFont(u8g2_font_helvR10_tr);
    u8g2.drawStr(105, 49, "S");

    u8g2.setFont(u8g2_font_helvR24_tn);
    u8g2.setCursor(9, 55);
    u8g2.print(humidityThreshold);
    u8g2.drawXBMP(50, 31, percent_width, percent_height, percent_bits);
  }
  /***************************************************************************/
  // display FAN_DELAY set menu
  if (state == SET_SWITCH_OFF_DELAY)
  {
    u8g2.setFont(u8g2_font_helvR12_tr);

    u8g2.drawStr(0, 13, "Set fan delay");
    //u8g2.drawHLine(0, 15, 127);
    u8g2.drawRFrame(4, 23, 77, 40, 3);
    u8g2.drawXBMP(90, 25, upArrow_width, upArrow_height, upArrow_bits);
    u8g2.drawXBMP(90, 46, downArrow_width, downArrow_height, downArrow_bits);
    u8g2.setFont(u8g2_font_helvR10_tr);
    u8g2.drawStr(105, 49, "S");

    u8g2.setFont(u8g2_font_helvR24_tn);
    u8g2.setCursor(9, 55);
    u8g2.print(fanSwitchOffDelayTime);
    u8g2.setFont(u8g2_font_helvR10_tr);
    u8g2.drawStr(50, 55, "Min.");
  }
  /***************************************************************************/
  // display FAN_DELAY set menu
  if (state == SET_HYSTERESIS)
  {
    u8g2.setFont(u8g2_font_helvR12_tr);

    u8g2.drawStr(0, 13, "Set Hysteresis");
    //u8g2.drawHLine(0, 15, 127);
    u8g2.drawRFrame(4, 23, 77, 40, 3);
    u8g2.drawXBMP(90, 25, upArrow_width, upArrow_height, upArrow_bits);
    u8g2.drawXBMP(90, 46, downArrow_width, downArrow_height, downArrow_bits);
    u8g2.setFont(u8g2_font_helvR10_tr);
    u8g2.drawStr(105, 49, "S");

    u8g2.setFont(u8g2_font_helvR24_tn);
    u8g2.setCursor(29, 55);
    u8g2.print(humidityHysteresis);
    u8g2.drawXBMP(50, 31, percent_width, percent_height, percent_bits);
  }
  /***************************************************************************/
  // transfer internal memory to the display
  if (turnOledDisplayOFF == true)
  {
    u8g2.clearBuffer(); // clear the internal memory
  }
  u8g2.sendBuffer();
}

//  888               888    888
//  888               888    888
//  888               888    888
//  88888b.  888  888 888888 888888 .d88b.  88888b.  .d8888b
//  888 "88b 888  888 888    888   d88""88b 888 "88b 88K
//  888  888 888  888 888    888   888  888 888  888 "Y8888b.
//  888 d88P Y88b 888 Y88b.  Y88b. Y88..88P 888  888      X88
//  88888P"   "Y88888  "Y888  "Y888 "Y88P"  888  888  88888P'
//
/*****************************************************************************/
// this function will be called when the button was pressed 1 time
// and them some time has passed.
void buttonSelectClick() { btnSelectClickEvent = true; }
void buttonUpClick() { btnUpClickEvent = true; }
void buttonDownClick() { btnDownClickEvent = true; }

// this function will be called when the button was hold down for >1 second.
void buttonSelectLongPress() { btnSelectHoldEvent = true; }
void buttonUpLongPress() { btnUpHoldEvent = true; }
void buttonDownLongPress() { btnDownHoldEvent = true; }

// this function will be called when the button was hold down for >1 second.
void buttonUpDuringLongPress() { btnUpDuringHoldEvent = true; }
void buttonDownDuringLongPress() { btnDownDuringHoldEvent = true; }
//                  888          d8b
//                  888          Y8P
//                  888
//  888d888 .d88b.  888  8888b.  888 .d8888b
//  888P"  d8P  Y8b 888     "88b 888 88K
//  888    88888888 888 .d888888 888 "Y8888b.
//  888    Y8b.     888 888  888 888      X88
//  888     "Y8888  888 "Y888888 888  88888P'
//
//
//
/*****************************************************************************/
void turnFanOn()
{
  // switch ON the relais
  digitalWrite(PIN_RELAIS, HIGH);
}

void turnFanOff()
{
  // switch OFF the relais
  digitalWrite(PIN_RELAIS, LOW);
}

//  888                     888           d8b           .d888
//  888                     888           Y8P          d88P"
//  888                     888                        888
//  888888 .d88b.   .d8888b 88888b.       888 88888b.  888888 .d88b.
//  888   d8P  Y8b d88P"    888 "88b      888 888 "88b 888   d88""88b
//  888   88888888 888      888  888      888 888  888 888   888  888
//  Y88b. Y8b.     Y88b.    888  888      888 888  888 888   Y88..88P
//   "Y888 "Y8888   "Y8888P 888  888      888 888  888 888    "Y88P"
//
/***************************************************************************

In contrast to some other GFX systems, the co-ordinates of the bounding boxes
point to the right and upwards. 
For the most characters, x-offset is the horizontal distance between the very left
pixel of the glyph and the left border, so in the picture above, the x-offset is
quite exaggerated. y-offset is the vertical distance between baseline and the
lowest pixel.For most glyphs without descender, it is equal to zero, 
for glyphs with descender, it is negative.

Generally, the font bounding box is the outline of all glyph bounding boxes,
with all offsets taken into account. Due to some large glyphs 
(e.g. for '@' and '|'), the font bounding box can be quite large - 
larger than typical character pitch or line height.

There are some fonts, where the font bounding box is a bit larger than the outline
of all contained glyphs. This happens, when glyphs are removed. However, 
this makes no difference in font decoding or display.
42
Fonts can be generated in different bounding box modes:

    [t=1]: Transparent mode. All glyph bounding boxes are tight fit. This generates minimal flash memory footprint.
    [h=2]: Height mode. All glyph bounding boxes are horizontally tight fit, but have the same height. This allows overwriting text lines without clearing.
    [m=3]: Monospaced mode. All glyph bounding boxes have the same size, which is equal to font bounding box size.

Bounding box mode is the second last character of the font name (see font names).

*/

// Please UNCOMMENT one of the contructor lines below
// U8g2 Contructor List (Frame Buffer)

// The complete list is available here: https://github.com/olikraus/u8g2/wiki/u8g2setupcpp

// Please update the pin numbers according to your setup. Use U8X8_PIN_NONE if the reset pin is not connected

//U8G2_NULL u8g2(U8G2_R0);	// null device, a 8x8 pixel display which does nothing
//U8G2_SSD1306_128X64_NONAME_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1306_128X64_NONAME_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 12, /* dc=*/ 4, /* reset=*/ 6);	// Arduboy (Production, Kickstarter Edition)
//U8G2_SSD1306_128X64_NONAME_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1306_128X64_NONAME_F_3W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* reset=*/ 8);
//U8G2_SSD1306_128X64_NONAME_F_3W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* reset=*/ 8);
//U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
//U8G2_SSD1306_128X64_ALT0_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);   // same as the NONAME variant, but may solve the "every 2nd line skipped" problem
//U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* reset=*/ 8);
//U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);   // All Boards without Reset of the Display
//U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ 16, /* data=*/ 17, /* reset=*/ U8X8_PIN_NONE);   // ESP32 Thing, pure SW emulated I2C
//U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ 16, /* data=*/ 17);   // ESP32 Thing, HW I2C with pin remapping
//U8G2_SSD1306_128X64_NONAME_F_6800 u8g2(U8G2_R0, 13, 11, 2, 3, 4, 5, 6, A4, /*enable=*/ 7, /*cs=*/ 10, /*dc=*/ 9, /*reset=*/ 8);
//U8G2_SSD1306_128X64_NONAME_F_8080 u8g2(U8G2_R0, 13, 11, 2, 3, 4, 5, 6, A4, /*enable=*/ 7, /*cs=*/ 10, /*dc=*/ 9, /*reset=*/ 8);
//U8G2_SSD1306_128X64_VCOMH0_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);	// same as the NONAME variant, but maximizes setContrast() range
//U8G2_SSD1306_128X64_ALT0_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);	// same as the NONAME variant, but may solve the "every 2nd line skipped" problem
//U8G2_SH1106_128X64_NONAME_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
//U8G2_SH1106_128X64_VCOMH0_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);		// same as the NONAME variant, but maximizes setContrast() range
//U8G2_SH1106_128X64_WINSTAR_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);		// same as the NONAME variant, but uses updated SH1106 init sequence
//U8G2_SH1106_72X40_WISE_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SH1107_64X128_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SH1107_128X128_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SH1107_128X128_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ 8);
//U8G2_SH1107_PIMORONI_128X128_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ 8);
//U8G2_SH1107_SEEED_128X128_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);
//U8G2_SH1107_SEEED_128X128_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
//U8G2_SH1107_SEEED_96X96_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SH1108_160X160_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SH1122_256X64_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);				// Enable U8G2_16BIT in u8g2.h
//U8G2_SSD1306_128X32_UNIVISION_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ 21, /* data=*/ 20, /* reset=*/ U8X8_PIN_NONE);   // Adafruit Feather M0 Basic Proto + FeatherWing OLED
//U8G2_SSD1306_128X32_UNIVISION_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);   // Adafruit Feather ESP8266/32u4 Boards + FeatherWing OLED
//U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);  // Adafruit ESP8266/32u4/ARM Boards + FeatherWing OLED
//U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ SCL, /* data=*/ SDA);   // pin remapping with ESP8266 HW I2C
//U8G2_SSD1306_128X32_WINSTAR_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ SCL, /* data=*/ SDA);   // pin remapping with ESP8266 HW I2C
//U8G2_SSD1306_64X48_ER_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);   // EastRising 0.66" OLED breakout board, Uno: A4=SDA, A5=SCL, 5V powered
//U8G2_SSD1306_48X64_WINSTAR_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
//U8G2_SSD1306_64X32_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
//U8G2_SSD1306_64X32_1F_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
//U8G2_SSD1306_96X16_ER_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);   // EastRising 0.69" OLED
//U8G2_SSD1306_72X40_ER_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);   // EastRising 0.42" OLED
//U8G2_SSD1322_NHD_256X64_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);	// Enable U8G2_16BIT in u8g2.h
//U8G2_SSD1322_NHD_256X64_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);	// Enable U8G2_16BIT in u8g2.h
//U8G2_SSD1322_NHD_128X64_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1322_NHD_128X64_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1325_NHD_128X64_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1325_NHD_128X64_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD0323_OS128064_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD0323_OS128064_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1326_ER_256X32_1_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);         // experimental driver for ER-OLED018-1
//U8G2_SSD1327_SEEED_96X96_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);	// Seeedstudio Grove OLED 96x96
//U8G2_SSD1327_SEEED_96X96_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);	// Seeedstudio Grove OLED 96x96
//U8G2_SSD1327_EA_W128128_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1327_EA_W128128_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1327_EA_W128128_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ 5, /* data=*/ 4, /* reset=*/ U8X8_PIN_NONE);
//U8G2_SSD1327_EA_W128128_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);  /* Uno: A4=SDA, A5=SCL, add "u8g2.setBusClock(400000);" into setup() for speedup if possible */
//U8G2_SSD1327_MIDAS_128X128_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1327_MIDAS_128X128_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1327_MIDAS_128X128_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); /* Uno: A4=SDA, A5=SCL, add "u8g2.setBusClock(400000);" into setup() for speedup if possible */
//U8G2_SSD1327_WS_128X128_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1327_WS_128X128_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1327_VISIONOX_128X96_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1327_VISIONOX_128X96_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1329_128X96_NONAME_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1329_128X96_NONAME_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1305_128X32_NONAME_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1305_128X32_NONAME_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1305_128X32_ADAFRUIT_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1305_128X32_ADAFRUIT_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1305_128X64_ADAFRUIT_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1305_128X64_ADAFRUIT_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1309_128X64_NONAME0_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1309_128X64_NONAME0_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1309_128X64_NONAME2_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1309_128X64_NONAME2_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1316_128X32_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1316_128X32_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1317_96X96_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);  // not tested, not confirmed
//U8G2_SSD1317_96X96_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8); 	// not tested, not confirmed
//U8G2_SSD1318_128X96_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SSD1318_128X96_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_LD7032_60X32_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 11, /* data=*/ 12, /* cs=*/ 9, /* dc=*/ 10, /* reset=*/ 8);	// SW SPI Nano Board
//U8G2_LD7032_60X32_F_4W_SW_I2C u8g2(U8G2_R0, /* clock=*/ 11, /* data=*/ 12, /* reset=*/ U8X8_PIN_NONE);	// NOT TESTED!
//U8G2_UC1701_EA_DOGS102_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_UC1701_EA_DOGS102_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_PCD8544_84X48_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);  // Nokia 5110 Display
//U8G2_PCD8544_84X48_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8); 		// Nokia 5110 Display
//U8G2_PCF8812_96X65_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);	// Could be also PCF8814
//U8G2_PCF8812_96X65_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);						// Could be also PCF8814
//U8G2_HX1230_96X68_F_3W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* reset=*/ 8);
//U8G2_HX1230_96X68_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_KS0108_128X64_F u8g2(U8G2_R0, 8, 9, 10, 11, 4, 5, 6, 7, /*enable=*/ 18, /*dc=*/ 17, /*cs0=*/ 14, /*cs1=*/ 15, /*cs2=*/ U8X8_PIN_NONE, /* reset=*/  U8X8_PIN_NONE); 	// Set R/W to low!
//U8G2_KS0108_ERM19264_F u8g2(U8G2_R0, 8, 9, 10, 11, 4, 5, 6, 7, /*enable=*/ 18, /*dc=*/ 17, /*cs0=*/ 14, /*cs1=*/ 15, /*cs2=*/ 16, /* reset=*/  U8X8_PIN_NONE); 	// Set R/W to low!
//U8G2_ST7920_192X32_F_8080 u8g2(U8G2_R0, 8, 9, 10, 11, 4, 5, 6, 7, /*enable=*/ 18, /*cs=*/ U8X8_PIN_NONE, /*dc=*/ 17, /*reset=*/ U8X8_PIN_NONE);
//U8G2_ST7920_192X32_F_SW_SPI u8g2(U8G2_R0, /* clock=*/ 18 /* A4 */ , /* data=*/ 16 /* A2 */, /* CS=*/ 17 /* A3 */, /* reset=*/ U8X8_PIN_NONE);
//U8G2_ST7920_128X64_F_8080 u8g2(U8G2_R0, 8, 9, 10, 11, 4, 5, 6, 7, /*enable=*/ 18 /* A4 */, /*cs=*/ U8X8_PIN_NONE, /*dc/rs=*/ 17 /* A3 */, /*reset=*/ 15 /* A1 */);	// Remember to set R/W to 0
//U8G2_ST7920_128X64_F_SW_SPI u8g2(U8G2_R0, /* clock=*/ 18 /* A4 */ , /* data=*/ 16 /* A2 */, /* CS=*/ 17 /* A3 */, /* reset=*/ U8X8_PIN_NONE);
//U8G2_ST7920_128X64_F_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* CS=*/ 10, /* reset=*/ 8);
//U8G2_ST7920_128X64_F_SW_SPI u8g2(U8G2_R0, /* clock=*/ 14, /* data=*/ 13, /* CS=*/ 15, /* reset=*/ 16); // Feather HUZZAH ESP8266, E=clock=14, RW=data=13, RS=CS
//U8G2_ST7920_128X64_F_HW_SPI u8g2(U8G2_R0, /* CS=*/ 10, /* reset=*/ 8);
//U8G2_ST7920_128X64_F_HW_SPI u8g2(U8G2_R0, /* CS=*/ 15, /* reset=*/ 16); // Feather HUZZAH ESP8266, E=clock=14, RW=data=13, RS=CS
//U8G2_ST7565_EA_DOGM128_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7565_EA_DOGM128_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7565_64128N_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7565_64128N_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7565_EA_DOGM132_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ U8X8_PIN_NONE);	// DOGM132 Shield
//U8G2_ST7565_EA_DOGM132_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ U8X8_PIN_NONE);	// DOGM132 Shield
//U8G2_ST7565_ZOLEN_128X64_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7565_ZOLEN_128X64_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7565_LM6059_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);		// Adafruit ST7565 GLCD
//U8G2_ST7565_LM6059_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);		// Adafruit ST7565 GLCD
//U8G2_ST7565_KS0713_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);		// KS0713 controller
//U8G2_ST7565_KS0713_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);			// KS0713 controller
//U8G2_ST7565_LX12864_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7565_LX12864_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7565_ERC12864_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7565_ERC12864_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7565_ERC12864_ALT_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8); // contrast improved version for ERC12864
//U8G2_ST7565_ERC12864_ALT_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);  // contrast improved version for ERC12864
//U8G2_ST7565_NHD_C12832_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7565_NHD_C12832_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7565_NHD_C12864_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7565_NHD_C12864_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7565_JLX12864_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7565_JLX12864_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7567_PI_132X64_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 7, /* dc=*/ 9, /* reset=*/ 8);  // Pax Instruments Shield, LCD_BL=6
//U8G2_ST7567_PI_132X64_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 7, /* dc=*/ 9, /* reset=*/ 8);  // Pax Instruments Shield, LCD_BL=6
//U8G2_ST7567_JLX12864_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 7, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7567_JLX12864_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 7, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7567_OS12864_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 7, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7567_OS12864_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 7, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7567_ENH_DG128064_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7567_ENH_DG128064_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7567_ENH_DG128064I_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7567_ENH_DG128064I_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7567_64X32_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
//U8G2_ST7567_HEM6432_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
//U8G2_ST75256_JLX172104_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST75256_JLX172104_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST75256_JLX19296_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST75256_JLX19296_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST75256_JLX256128_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);	// Enable U8g2 16 bit mode for this display
//U8G2_ST75256_JLX256128_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);		// Enable U8g2 16 bit mode for this display
//U8G2_ST75256_WO256X128_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);	// Enable U8g2 16 bit mode for this display
//U8G2_ST75256_WO256X128_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);		// Enable U8g2 16 bit mode for this display
//U8G2_ST75256_JLX256128_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 9, /* data=*/ 8, /* cs=*/ 7, /* dc=*/ 6, /* reset=*/ 5);  // MKR Zero, Enable U8g2 16 bit mode for this display
//U8G2_ST75256_JLX256128_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 7, /* dc=*/ 6, /* reset=*/ 5);  // MKR Zero, Enable U8g2 16 bit mode for this display
//U8G2_ST75256_JLX256160_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);	// Enable U8g2 16 bit mode for this display
//U8G2_ST75256_JLX256160_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);		// Enable U8g2 16 bit mode for this display
//U8G2_ST75256_JLX256160M_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);	// Enable U8g2 16 bit mode for this display
//U8G2_ST75256_JLX256160M_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);		// Enable U8g2 16 bit mode for this display
//U8G2_ST75256_JLX256160_ALT_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);	// Enable U8g2 16 bit mode for this display
//U8G2_ST75256_JLX256160_ALT_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);		// Enable U8g2 16 bit mode for this display
//U8G2_ST75256_JLX240160_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST75256_JLX240160_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST75256_JLX25664_F_2ND_HW_I2C u8g2(U8G2_R0, /* reset=*/ 8);	// Due, 2nd I2C, enable U8g2 16 bit mode for this display
//U8G2_ST75320_JLX320240_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);	// Enable U8g2 16 bit mode for this display
//U8G2_ST75320_JLX320240_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);		// Enable U8g2 16 bit mode for this display
//U8G2_NT7534_TG12864R_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_NT7534_TG12864R_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_ST7588_JLX12864_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ 5);
//U8G2_ST7588_JLX12864_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ 5);
//U8G2_IST3020_ERC19264_F_6800 u8g2(U8G2_R0, 44, 43, 42, 41, 40, 39, 38, 37,  /*enable=*/ 28, /*cs=*/ 32, /*dc=*/ 30, /*reset=*/ 31); // Connect WR pin with GND
//U8G2_IST3020_ERC19264_F_8080 u8g2(U8G2_R0, 44, 43, 42, 41, 40, 39, 38, 37,  /*enable=*/ 29, /*cs=*/ 32, /*dc=*/ 30, /*reset=*/ 31); // Connect RD pin with 3.3V
//U8G2_IST3020_ERC19264_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_IST7920_128X128_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);  // Round display
//U8G2_IST7920_128X128_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);  // Round display
//U8G2_LC7981_160X80_F_6800 u8g2(U8G2_R0, 8, 9, 10, 11, 4, 5, 6, 7, /*enable=*/ 18, /*cs=*/ 14, /*dc=*/ 15, /*reset=*/ 16); // Connect RW with GND
//U8G2_LC7981_160X160_F_6800 u8g2(U8G2_R0, 8, 9, 10, 11, 4, 5, 6, 7, /*enable=*/ 18, /*cs=*/ 14, /*dc=*/ 15, /*reset=*/ 16); // Connect RW with GND
//U8G2_LC7981_240X128_F_6800 u8g2(U8G2_R0, 8, 9, 10, 11, 4, 5, 6, 7, /*enable=*/ 18, /*cs=*/ 14, /*dc=*/ 15, /*reset=*/ 16); // Connect RW with GND
//U8G2_LC7981_240X64_F_6800 u8g2(U8G2_R0, 8, 9, 10, 11, 4, 5, 6, 7, /*enable=*/ 18, /*cs=*/ 14, /*dc=*/ 15, /*reset=*/ 16); // Connect RW with GND
//U8G2_SED1520_122X32_F u8g2(U8G2_R0, 8, 9, 10, 11, 4, 5, 6, 7, /*dc=*/ A0, /*e1=*/ A3, /*e2=*/ A2, /* reset=*/  A4); 	// Set R/W to low!
//U8G2_T6963_240X128_F_8080 u8g2(U8G2_R0, 8, 9, 10, 11, 4, 5, 6, 7, /*enable/wr=*/ 17, /*cs/ce=*/ 14, /*dc=*/ 15, /*reset=*/ 16); // Connect RD with +5V, FS0 and FS1 with GND
//U8G2_T6963_256X64_F_8080 u8g2(U8G2_R0, 8, 9, 10, 11, 4, 5, 6, 7, /*enable/wr=*/ 17, /*cs/ce=*/ 14, /*dc=*/ 15, /*reset=*/ 16); // Connect RD with +5V, FS0 and FS1 with GND
//U8G2_T6963_160X80_F_8080 u8g2(U8G2_R0, 8, 9, 10, 11, 4, 5, 6, 7, /*enable/wr=*/ 17, /*cs/ce=*/ 14, /*dc=*/ 15, /*reset=*/ 16); // Connect RD with +5V, FS0 and FS1 with GND
//U8G2_T6963_128X64_F_8080 u8g2(U8G2_R0, 8, 9, 10, 11, 4, 5, 6, 7, /*enable/wr=*/ 17, /*cs/ce=*/ 14, /*dc=*/ 15, /*reset=*/ 16); // Connect RD with +5V, FS0 and FS1 with GND
//U8G2_T6963_128X64_ALT_F_8080 u8g2(U8G2_R0, 8, 9, 10, 11, 4, 5, 6, 7, /*enable/wr=*/ 17, /*cs/ce=*/ 14, /*dc=*/ 15, /*reset=*/ 16); // Connect RD with +5V, FS0 and FS1 with GND
//U8G2_SED1330_240X128_F_8080 u8g2(U8G2_R0, 8, 9, 10, 11, 4, 5, 6, 7, /*enable=*/ 17, /*cs=*/ 14, /*dc=*/ 15, /*reset=*/ 16); // Connect RD with +5V, FG with GND
//U8G2_SED1330_240X128_F_6800 u8g2(U8G2_R0, 13, 11, 2, 3, 4, 5, 6, A4, /*enable=*/ 7, /*cs=*/ 10, /*dc=*/ 9, /*reset=*/ 8); // A0 is dc pin!
//U8G2_RA8835_NHD_240X128_F_8080 u8g2(U8G2_R0, 8, 9, 10, 11, 4, 5, 6, 7, /*enable=*/ 17, /*cs=*/ 14, /*dc=*/ 15, /*reset=*/ 16); // Connect /RD = E with +5V, enable is /WR = RW, FG with GND, 14=Uno Pin A0
//U8G2_RA8835_NHD_240X128_F_6800 u8g2(U8G2_R0, 8, 9, 10, 11, 4, 5, 6, 7,  /*enable=*/ 17, /*cs=*/ 14, /*dc=*/ 15, /*reset=*/ 16); // A0 is dc pin, /WR = RW = GND, enable is /RD = E
//U8G2_UC1604_JLX19264_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_UC1604_JLX19264_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_UC1608_ERC24064_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);  // SW SPI, Due ERC24064-1 Test Setup
//U8G2_UC1608_DEM240064_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);  // SW SPI, Due ERC24064-1 Test Setup
//U8G2_UC1608_ERC240120_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_UC1608_240X128_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);  // SW SPI, Due ERC24064-1 Test Setup
//U8G2_UC1610_EA_DOGXL160_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/  U8X8_PIN_NONE);
//U8G2_UC1610_EA_DOGXL160_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/  U8X8_PIN_NONE);
//U8G2_UC1611_EA_DOGM240_F_2ND_HW_I2C u8g2(U8G2_R0, /* reset=*/ 8);	// Due, 2nd I2C, DOGM240 Test Board
//U8G2_UC1611_EA_DOGM240_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);   // Due, SW SPI, DOGXL240 Test Board
//U8G2_UC1611_EA_DOGXL240_F_2ND_HW_I2C u8g2(U8G2_R0, /* reset=*/ 8);	// Due, 2nd I2C, DOGXL240 Test Board
//U8G2_UC1611_EA_DOGXL240_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);   // Due, SW SPI, DOGXL240 Test Board
//U8G2_UC1611_EW50850_F_8080 u8g2(U8G2_R0, 8, 9, 10, 11, 4, 5, 6, 7,  /*enable=*/ 18, /*cs=*/ 3, /*dc=*/ 16, /*reset=*/ 17); // 240x160, Connect RD/WR1 pin with 3.3V, CS is aktive high
//U8G2_UC1611_CG160160_F_8080 u8g2(U8G2_R0, 8, 9, 10, 11, 4, 5, 6, 7,  /*enable=*/ 18, /*cs=*/ 3, /*dc=*/ 16, /*reset=*/ 17); // Connect WR1 and CD1 pin with 3.3V, connect CS0 with cs, WR0 with enable, CD with dc
//U8G2_UC1617_JLX128128_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_UC1617_JLX128128_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_UC1638_160X128_F_4W_HW_SPI u8g2(U8G2_R2, /* cs=*/ 2, /* dc=*/ 3, /* reset=*/ 4); 	 // Not tested
//U8G2_SSD1606_172X72_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);		// eInk/ePaper Display
//U8G2_SSD1607_200X200_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);	// eInk/ePaper Display, original LUT from embedded artists
//U8G2_SSD1607_GD_200X200_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);	// Good Display
//U8G2_SSD1607_WS_200X200_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);	// Waveshare
//U8G2_IL3820_296X128_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);	// WaveShare 2.9 inch eInk/ePaper Display, enable 16 bit mode for this display!
//U8G2_IL3820_V2_296X128_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);	// ePaper Display, lesser flickering and faster speed, enable 16 bit mode for this display!
//U8G2_MAX7219_64X8_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 11, /* data=*/ 12, /* cs=*/ 10, /* dc=*/ U8X8_PIN_NONE, /* reset=*/ U8X8_PIN_NONE);
//U8G2_MAX7219_32X8_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 11, /* data=*/ 12, /* cs=*/ 10, /* dc=*/ U8X8_PIN_NONE, /* reset=*/ U8X8_PIN_NONE);
//U8G2_MAX7219_8X8_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 11, /* data=*/ 12, /* cs=*/ 10, /* dc=*/ U8X8_PIN_NONE, /* reset=*/ U8X8_PIN_NONE);
//U8G2_LS013B7DH03_128X128_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ U8X8_PIN_NONE, /* reset=*/ 8);	// there is no DC line for this display
//U8G2_LS027B7DH01_400X240_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ U8X8_PIN_NONE, /* reset=*/ 8);	// there is no DC line for this display
//U8G2_LS013B7DH05_144X168_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ U8X8_PIN_NONE, /* reset=*/ 8);	// there is no DC line for this display
//U8G2_ST7511_AVD_320X240_F_8080 u8g2(U8G2_R0, 13, 11, 2, 3, 4, 5, 6, A4, /*enable/WR=*/ 7, /*cs=*/ 10, /*dc=*/ 9, /*reset=*/ 8); // Enable U8g2 16Bit Mode and connect RD pin with 3.3V/5V

// End of constructor list

Bathroom Fan Controller v 1.11

Arduino
see decription
//  888888b.            888    888
//  888  "88b           888    888
//  888  .88P           888    888
//  8888888K.   8888b.  888888 88888b.  888d888 .d88b.   .d88b.  88888b.d88b.
//  888  "Y88b     "88b 888    888 "88b 888P"  d88""88b d88""88b 888 "888 "88b
//  888    888 .d888888 888    888  888 888    888  888 888  888 888  888  888
//  888   d88P 888  888 Y88b.  888  888 888    Y88..88P Y88..88P 888  888  888
//  8888888P"  "Y888888  "Y888 888  888 888     "Y88P"   "Y88P"  888  888  888
//
//
//
//  8888888888                      .d8888b.                    888                    888 888
//  888                            d88P  Y88b                   888                    888 888
//  888                            888    888                   888                    888 888
//  8888888  8888b.  88888b.       888         .d88b.  88888b.  888888 888d888 .d88b.  888 888  .d88b.  888d888
//  888         "88b 888 "88b      888        d88""88b 888 "88b 888    888P"  d88""88b 888 888 d8P  Y8b 888P"
//  888     .d888888 888  888      888    888 888  888 888  888 888    888    888  888 888 888 88888888 888
//  888     888  888 888  888      Y88b  d88P Y88..88P 888  888 Y88b.  888    Y88..88P 888 888 Y8b.     888
//  888     "Y888888 888  888       "Y8888P"   "Y88P"  888  888  "Y888 888     "Y88P"  888 888  "Y8888  888
//
/*

* Version 1.11 - Last change: 2021-03-25

WHY (why oh why...?)
Well I looked for a good solution to keep the humidity level down in our bathroom.  We already have (already >20 years) a very good and silent fan but it is operated manually and sometimes we forgot to turn it on and/or off.
So looking around, I found there are only a few options. Yes you can buy a fan with build in controller but they are expensive and the manual settings are very limited. 
A stand alone humidity controller/switch was much harder to find! I only found a mechanical switch for just under 100 euros.  

The solution and description

As I am very fond of the Arduino, I (again) decided to make myself the things I need, in this case a "Bathroom Fan Controller" (for lack of a better name)
The controller has the following function and options:

    Measure Relative Humidity and temperature. (duh.)
    Turn a Fan on (via a relay) and switching it off when the humidity has dropped.
    OPTIONAL: The Fan will stay on for a selectable time after humidity has dropped. (decrease the humidity a bit more)
    Manually turn the Fan ON for 15m, 30m, 1, 2, 3, 4, 5, 6 or 12 hours.
    (useful for smelly events...)
    Manually turn the Fan Controller system OFF for 30m, 1, 2, 4, 8 or 12 hours.
    (want to go to bed but the noisy fan is on?  turn it off!)
    Turn the Fan Controller system OFF completely until turning ON manually.
    (vacation time!)
    User settings are stored in EEPROM and preserved after reset/power fail.

USER SETTINGS MENU:

    - Threshold: from 40%RH to 95%RH
    - Hysteresis: from 3%RH to 9%RH
    - Fan off delay: from 0 (NO delay) to 60 minutes.

BUTTONS:

    There are 3 buttons, from top to bottom these are:
    - ON / UP
    - OFF / DOWN
    - SELECT
    - on the side of the case: system RESET button


Explanation of the display of the controller...

    At the top-left on the display you see the CURRENT HUMIDITY value, updated every second. The percent (%) sign will blink to indicate this.
    on the top-right we have the humidity THRESHOLD value.
    below the threshold value you'll see the set HYSTERESIS value (optional)
    at the bottom right, the current TEMPERATURE is displayed.
    at the bottom left a Fan icon will indicate when the Fan is turned on. right of that icon a text 'DELAY' is displayed if the fan-off delay is activated.


Explanation of the system

No event / system IDLE:
The humidity and temperature is measured and updated every second, indicated by a blinking '%' character next to the measured humidity value.

The sensor is *very* sensitive and also *very* accurate!  So it will react fast and reliable on changing conditions. 

    NOTE: If you decide to use a sensor from China then this will be a different matter. Cheap AND reliable/precise is simply not possible. 

 
Event: humidity has risen equal or above the threshold:
When the current humidity reaches the threshold value, the Fan (relais) will switch on, indicated by a FAN SYMBOL at the lower left of the display.
The Fan will stay on until the humidity level has dropped below the threshold *minus the Hysteresis value*.  So if the threshold is 70% and the Hysteresis is 5, then the fan will shut off at 65% Relative humidity. 
NOTE: Obviously the hysteresis is very important! If not used you would have a fan switching off and on around the threshold value.

Event: humidity has dropped below the threshold value *minus Hysteresis*:
When the humidity level drop below the threshold plus hysteresis value, the fan will turn OFF.  Example: threshold=70 and hysteresis=5, then the fan will stop at a threshold level of 65. 
EXCEPT: if you have set a FAN OFF DELAY time then the fan will remain on for a user determined time (menu setting)

Manual interventions:
I purposely build in several useful features not found in commercial controllers (AFAIK). For example: 
- you have made the WC happy but the smell is not to be desired... Then you can turn on the fan manually for a set time.
- You want to go to bed but the fan is on because the humidity level is too high but the noise of the fan is disturbing. Then you can turn the system off for a set time, after which it will continue to measure and switch on when needed. Ventilation is important to keep mould away so this way you can't forget to turn the system on again.
- You are going on holiday: turn the system off completely. This seems obvious but with build in sensors in a fan this is not always possible

Explanation of the BUTTONS

    ON / UP: 
    - SYSTEM IDLE (fan OFF): when pressed the fan will turn ON for a set time, starting at 15 minutes. press UP again to increase fan ON time in pre-determined steps. (Maximum 12 hours)
    - SYSTEM OFF: turn system ON again
    - SYSTEM MANUALY SWITCHED OFF: system returns to SYSTEM IDLE state
    - MENU ACTIVE: when pressed the value is increased, hold to fast increase value.
    OFF / DOWN:
    - SYSTEM IDLE (fan OFF): when pressed, the system will SHUT DOWN for the set time, starting at 30 minutes. Press DOWN again to increase the shut down time in pre-determined steps. (Maximum 12 hours)
    - FAN IS ON or  FAN OFF DELAY active: stop the fan, then same as SYSTEM IDLE
    - ANY STATE (except MENU): when button is pressed for >1 second, the system is turned off completely until being turned ON again by pressing ON button.
    - MENU ACTIVE: when pressed the value is decreased, hold to fast decrease value.
    SELECT:
    when the button is pressed for >1 second, the user MENU is displayed
    - Set threshold: from 40%RH to 95%RH
    - Hysteresis: from 3%RH to 9%RH
    - Fan off delay: from 0 (no delay) to 60 minutes.


I had 2 cheap I2C 128x64pixel OLED screens in a drawer. maybe a bit tiny but way better than a 
20x2 LCD screen... Very bright an crisp displays, these OLED things... 

To get descent fonts, I use the amazing 8U2G font library from Oli Kraus
https://github.com/olikraus/U8g2_Arduino

This font library consumes a *lot* of memory but the result is great... I managed to get all
code in the Arduino Uno (atmega328). 

I maybe over-commented this sketch but I'm a NOT a programma myself so I want to: 1. make changes
in the future easier for myself and 2. help others to understand what the heck the code means.
Experienced programmers may make this sketch way better but it does it's job, that's the beauty
of the Arduino plaform: even beginners can enjoy coding and grow and be more efficient later on.



============ BSD License for Bathroom Fan Controller ============

Copyright (c) 2021, Erik de Ruiter, The Netherlands
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list 
  of conditions and the following disclaimer.
  
* Redistributions in binary form must reproduce the above copyright notice, this 
  list of conditions and the following disclaimer in the documentation and/or other 
  materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  
*/

#include <Arduino.h>
#include <Wire.h>
#include <SPI.h>
#include <EEPROMex.h>
#include <U8g2lib.h>
#include <Adafruit_BME280.h>
#include <OneButton.h>

// *comment-out the #define line below if you don't want to see the
// *Hysteresis value and symbol on the OLED display
#define DISPLAY_HYSTERESIS

// home made icons for the display defined here. I used GIMP: made a new file,
// say 20x20 pixels, used the 'pen' to paint the image. When finished: menu
// [IMAGE]>[crop to selection]. Then menu [FILE]>[export as] and renamed the
// file, CHANGING THE EXTENSION TO .XBM (!)
// Then open this saved file with a text editor and paste all in the sketch.
// NOTE!!: I added in the line starting with 'static' this:
// 'const' and 'U8X8_PROGMEM', see below.

// percent icon
#define percent_width 10
#define percent_height 9
static const unsigned char percent_bits[] U8X8_PROGMEM = {
    0x0c, 0x02, 0x12, 0x01, 0x92, 0x00, 0x4c, 0x00, 0x20, 0x00, 0x90, 0x01,
    0x48, 0x02, 0x44, 0x02, 0x82, 0x01};
// percent icon BLACK (to 'erase' the icon for blinking it)
#define percent_width 10
#define percent_height 9
static const unsigned char black_bits[] U8X8_PROGMEM = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
// degree + celcius icon
#define celcius_width 12
#define celcius_height 13
static const unsigned char celcius_bits[] U8X8_PROGMEM = {
    0x0e, 0x00, 0x91, 0x07, 0x51, 0x08, 0x51, 0x00, 0x4e, 0x00, 0x40, 0x00,
    0x40, 0x00, 0x40, 0x08, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00};
// fan icon
#define fan_width 16
#define fan_height 16
static const unsigned char fan_bits[] U8X8_PROGMEM = {
    0xf0, 0x00, 0xf8, 0x01, 0xf8, 0x03, 0xf0, 0x63, 0xe0, 0xf3, 0xc0, 0xf9,
    0xdc, 0xff, 0x7e, 0xfe, 0x7f, 0x7e, 0xff, 0x3b, 0x9f, 0x03, 0xcf, 0x07,
    0xc6, 0x0f, 0xc0, 0x1f, 0x80, 0x1f, 0x00, 0x0f};
// hand icon for MANUAL_ON mode indicator
#define hand_width 33
#define hand_height 41
static const unsigned char hand_bits[] U8X8_PROGMEM = {
    0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0xc0, 0x07, 0x00, 0x00, 0x00, 0xc0,
    0xc7, 0x01, 0x00, 0x00, 0xc7, 0xe7, 0x03, 0x00, 0x80, 0xcf, 0xe7, 0x03,
    0x00, 0x80, 0xcf, 0xe7, 0x03, 0x00, 0x80, 0xcf, 0xe7, 0xe3, 0x00, 0x80,
    0xcf, 0xe7, 0xf3, 0x01, 0x80, 0xcf, 0xe7, 0xf3, 0x01, 0x80, 0xcf, 0xe7,
    0xf3, 0x01, 0x80, 0xcf, 0xe7, 0xf3, 0x01, 0x80, 0xcf, 0xe7, 0xf3, 0x01,
    0x80, 0xcf, 0xe7, 0xf3, 0x01, 0x80, 0xcf, 0xe7, 0xf3, 0x01, 0x80, 0xcf,
    0xe7, 0xf3, 0x01, 0x8e, 0xcf, 0xe7, 0xf3, 0x01, 0x9f, 0xcf, 0xe7, 0xf3,
    0x01, 0x9f, 0xff, 0xff, 0xf3, 0x01, 0x9f, 0xff, 0xff, 0xff, 0x01, 0xbf,
    0xff, 0xff, 0xff, 0x01, 0xbf, 0xff, 0xff, 0xff, 0x01, 0xff, 0xff, 0xff,
    0xff, 0x01, 0xfe, 0xc3, 0x7f, 0xf8, 0x01, 0xfe, 0x83, 0x3f, 0xf8, 0x01,
    0xfe, 0x03, 0x1f, 0xf8, 0x01, 0xfc, 0x03, 0x0e, 0xf8, 0x01, 0xfc, 0x23,
    0x84, 0xf8, 0x01, 0xfc, 0x63, 0xc0, 0xf8, 0x01, 0xf8, 0xe3, 0xe0, 0xf8,
    0x01, 0xf8, 0xe3, 0xf1, 0xf8, 0x01, 0xf0, 0xe3, 0xfb, 0xf8, 0x01, 0xf0,
    0xe3, 0xff, 0xf8, 0x01, 0xe0, 0xe3, 0xff, 0xf8, 0x00, 0xe0, 0xe3, 0xff,
    0xf8, 0x00, 0xc0, 0xe3, 0xff, 0xf8, 0x00, 0xc0, 0xff, 0xff, 0x7f, 0x00,
    0x80, 0xff, 0xff, 0x7f, 0x00, 0x80, 0xff, 0xff, 0x3f, 0x00, 0x00, 0xff,
    0xff, 0x1f, 0x00, 0x00, 0xfe, 0xff, 0x0f, 0x00, 0x00, 0xfc, 0xff, 0x07,
    0x00};
// up arrow for menu
#define upArrow_width 12
#define upArrow_height 15
static const unsigned char upArrow_bits[] U8X8_PROGMEM = {
    0x60, 0x00, 0xf0, 0x00, 0xf8, 0x01, 0xfc, 0x03, 0xfe, 0x07, 0xff, 0x0f,
    0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01,
    0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01};
// down arrow for menu
#define downArrow_width 12
#define downArrow_height 15
static const unsigned char downArrow_bits[] U8X8_PROGMEM = {
    0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01,
    0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xff, 0x0f, 0xfe, 0x07, 0xfc, 0x03,
    0xf8, 0x01, 0xf0, 0x00, 0x60, 0x00};
// hysteresis icon
#ifdef DISPLAY_HYSTERESIS
#define hysteresis_width 11
#define hysteresis_height 11
static const unsigned char hysteresis_bits[] U8X8_PROGMEM = {
    0xf8, 0x07, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00,
    0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0xff, 0x00};
#endif
// stopwatch icon
#define stopwatch_width 24
#define stopwatch_height 24
static const unsigned char stopwatch_bits[] U8X8_PROGMEM = {
    0x00, 0x7e, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x3c, 0x00, 0x18, 0x18, 0x18,
    0x0c, 0x7e, 0x30, 0x9e, 0x81, 0x79, 0x7a, 0x18, 0x5e, 0x10, 0x00, 0x08,
    0x10, 0x18, 0x08, 0x08, 0x18, 0x10, 0x08, 0x18, 0x10, 0x04, 0x18, 0x20,
    0x04, 0x18, 0x20, 0x14, 0xf8, 0x2b, 0x14, 0xf8, 0x2b, 0x04, 0x00, 0x20,
    0x04, 0x00, 0x20, 0x08, 0x00, 0x10, 0x08, 0x00, 0x10, 0x10, 0x00, 0x08,
    0x10, 0x00, 0x08, 0x60, 0x18, 0x06, 0x80, 0x81, 0x01, 0x00, 0x7e, 0x00};

//  8888888b. 8888888 888b    888                                 .d888 d8b
//  888   Y88b  888   8888b   888                                d88P"  Y8P
//  888    888  888   88888b  888                                888
//  888   d88P  888   888Y88b 888       .d8888b .d88b.  88888b.  888888 888  .d88b.
//  8888888P"   888   888 Y88b888      d88P"   d88""88b 888 "88b 888    888 d88P"88b
//  888         888   888  Y88888      888     888  888 888  888 888    888 888  888
//  888         888   888   Y8888      Y88b.   Y88..88P 888  888 888    888 Y88b 888
//  888       8888888 888    Y888       "Y8888P "Y88P"  888  888 888    888  "Y88888
//                                                                               888
//                                                                          Y8b d88P
//                                                                           "Y88P"

// These are all the Arduino PIN connections... Of course definition of the I2C pins
// A4 and A5 are not needed but added here for convenience.
#define PIN_RELAIS 13
#define PIN_DISPLAY_CLOCK 12
#define PIN_DISPLAY_DATA 11
#define PIN_DISPLAY_CS 10
#define PIN_DISPLAY_DC 9
#define PIN_DISPLAY_RESET 8
#define PIN_BUTTON_DOWN 5
#define PIN_BUTTON_UP 7
#define PIN_BUTTON_SELECT 6
#define PIN_I2C_CLOCK A5
#define PIN_I2C_DATA A4

//                            d8b          888      888
//                            Y8P          888      888
//                                         888      888
//  888  888  8888b.  888d888 888  8888b.  88888b.  888  .d88b.  .d8888b
//  888  888     "88b 888P"   888     "88b 888 "88b 888 d8P  Y8b 88K
//  Y88  88P .d888888 888     888 .d888888 888  888 888 88888888 "Y8888b.
//   Y8bd8P  888  888 888     888 888  888 888 d88P 888 Y8b.          X88
//    Y88P   "Y888888 888     888 "Y888888 88888P"  888  "Y8888   88888P'
//

float sensorTemp = 0;
int sensorHumidity = 0;
int sensorHumidityFraction = 0;
byte humidityHysteresis = 5 /*Rel.Humidity*/;
byte humidityThreshold = 0;

bool btnSelectClickEvent = false;
bool btnUpClickEvent = false;
bool btnDownClickEvent = false;
bool btnSelectHoldEvent = false;
bool btnUpHoldEvent = false;
bool btnDownHoldEvent = false;
bool btnUpDuringHoldEvent = false;
bool btnDownDuringHoldEvent = false;

unsigned long previousSensorReadTime = 0;
bool sensorIsRead = false;
bool humidityLevelTooHigh = false;

unsigned int fanCountdown = 0;
unsigned int fanRunTime = 0;
unsigned int fanRunStartTime = 0;
unsigned int fanManualOnRunTime = 15 /*minutes*/;

unsigned int fanDisabledTime = 0;
unsigned int fanDisabledStartTime = 0;
unsigned int fanDisabledRunTime = 30 /*minutes*/;

byte fanSwitchOffDelayTime = 30 /*minutes*/;

int timeHours = 0;
int timeMinutes = 0;
int timeSeconds = 0;

char bufferTime[6];

// set Arduino Uno EEPROM membase to store the user data
const int memBase = 350;

//           888       d8b                   888
//           888       Y8P                   888
//           888                             888
//   .d88b.  88888b.  8888  .d88b.   .d8888b 888888 .d8888b
//  d88""88b 888 "88b "888 d8P  Y8b d88P"    888    88K
//  888  888 888  888  888 88888888 888      888    "Y8888b.
//  Y88..88P 888 d88P  888 Y8b.     Y88b.    Y88b.       X88
//   "Y88P"  88888P"   888  "Y8888   "Y8888P  "Y888  88888P'
//                     888
//                    d88P
//                  888P"

// Setup new OneButton Objects
OneButton buttonSelect(/*PIN*/ PIN_BUTTON_SELECT, /*INPUT_PULLUP*/ true);
OneButton buttonUP(/*PIN*/ PIN_BUTTON_UP, /*INPUT_PULLUP*/ true);
OneButton buttonDown(/*PIN*/ PIN_BUTTON_DOWN, /*INPUT_PULLUP*/ true);

// Object OLED screen 128x64 pixels with SPI interface
// ! NOTE:  In my case connecting the display using I2C resulted in erratic behaviour
// ! due to static electricity(??). SPI proved to be much more stable in my case.
//
// NOTE2: Some displays support FLIP MODE so you can rotate the display output.
// change the 'U8G2_R0' in the constructor below: 
//
// U8G2_R0 = no rotation, 
// U8G2_R1 = 90 degree clockwise rotation,
// U8G2_R2 = 180 degree clockwise rotation,
// U8G2_R3 = 270 degree clockwise rotation.
//
U8G2_SSD1306_128X64_NONAME_F_4W_SW_SPI u8g2(U8G2_R0,
                                            /* clock=*/PIN_DISPLAY_CLOCK,
                                            /* data=*/PIN_DISPLAY_DATA,
                                            /* cs=*/PIN_DISPLAY_CS,
                                            /* dc=*/PIN_DISPLAY_DC,
                                            /* reset=*/PIN_DISPLAY_RESET);

// Object BME280 sensor
Adafruit_BME280 bme; // I2C

// State Machine States
typedef enum FSM
{
  IDLE_FAN_OFF,
  FAN_ON,
  MANUAL_ON,
  MANUAL_OFF,
  DISABLE,
  FAN_OFF_DELAY,
  SET_THRESHOLD,
  SET_HYSTERESIS,
  SET_SWITCH_OFF_DELAY,
} FSM;

FSM state = IDLE_FAN_OFF; // no action when starting

//   .d8888b.           888                        .d88 88b.
//  d88P  Y88b          888                       d88P" "Y88b
//  Y88b.               888                      d88P     Y88b
//   "Y888b.    .d88b.  888888 888  888 88888b.  888       888
//      "Y88b. d8P  Y8b 888    888  888 888 "88b 888       888
//        "888 88888888 888    888  888 888  888 Y88b     d88P
//  Y88b  d88P Y8b.     Y88b.  Y88b 888 888 d88P  Y88b. .d88P
//   "Y8888P"   "Y8888   "Y888  "Y88888 88888P"    "Y88 88P"
//                                      888
//                                      888
//                                      888
/******************************************************************************/
void setup()
{
  bme.begin();
  u8g2.begin();
  Wire.begin();
  /*clockFrequency: the value (in Hertz) of desired communication clock. 
  Accepted values are 100000 (standard mode) and 400000 (fast mode). 
  Some processors also support 10000 (low speed mode), 
  1000000 (fast mode plus) and 3400000 (high speed mode). 
  Please refer to the specific processor documentation to make sure 
  the desired mode is supported. */
  // if you set at 400000, display will mess up occasionally
  Wire.setClock(100000);

  pinMode(PIN_DISPLAY_RESET, OUTPUT);
  pinMode(PIN_RELAIS, OUTPUT);

  // Buttons...
  // link the myClickFunction function to be called on a button click event.
  buttonSelect.attachClick(buttonSelectClick);
  buttonUP.attachClick(buttonUpClick);
  buttonDown.attachClick(buttonDownClick);

  // link the myClickFunction function to be called on a button hold event.
  buttonUP.attachDuringLongPress(buttonUpDuringLongPress);
  buttonDown.attachDuringLongPress(buttonDownDuringLongPress);

  // link the myClickFunction function to be called on a button START hold event.
  buttonSelect.attachLongPressStart(buttonSelectLongPress);
  buttonUP.attachLongPressStart(buttonUpLongPress);
  buttonDown.attachLongPressStart(buttonDownLongPress);

  // set 50 msec. debouncing time. Default is 50 msec.
  buttonSelect.setDebounceTicks(50);
  buttonUP.setDebounceTicks(50);
  buttonDown.setDebounceTicks(50);

  // read EEPROM values. new memory often has 255 as memory content so we perform a rudimentary
  // check to see if the memory locations has never been used before. if so, set default values
  // memBase is the start EEPROM address (see variables)
  // An Interger value take 2 Bytes to store it in EEPROM so we need to take that in account.
  EEPROM.readInt(memBase) > 100 ? humidityThreshold = 65 : humidityThreshold = EEPROM.readInt(memBase);
  EEPROM.readInt(memBase + 2) > 10 ? humidityHysteresis = 4 : humidityHysteresis = EEPROM.readInt(memBase + 2);
  EEPROM.readInt(memBase + 4) > 60 ? fanSwitchOffDelayTime = 30 : fanSwitchOffDelayTime = EEPROM.readInt(memBase + 4);

}

//  888                                  .d88 88b.
//  888                                 d88P" "Y88b
//  888                                d88P     Y88b
//  888      .d88b.   .d88b.  88888b.  888       888
//  888     d88""88b d88""88b 888 "88b 888       888
//  888     888  888 888  888 888  888 Y88b     d88P
//  888     Y88..88P Y88..88P 888 d88P  Y88b. .d88P
//  88888888 "Y88P"   "Y88P"  88888P"    "Y88 88P"
//                            888
//                            888
//                            888

/******************************************************************************/
void loop()
{
  // keep watching the push button:
  buttonSelect.tick();
  buttonUP.tick();
  buttonDown.tick();
  // update sensor IDLE_FAN_OFFment
  readSensor();
  // run Finite State Machine
  runFSM();
}

//  8888888888   .d8888b.      888b     d888
//  888         d88P  Y88b     8888b   d8888
//  888         Y88b.          88888b.d88888
//  8888888      "Y888b.       888Y88888P888
//  888             "Y88b.     888 Y888P 888
//  888               "888     888  Y8P  888
//  888     d8b Y88b  d88P d8b 888   "   888 d8b
//  888     Y8P  "Y8888P"  Y8P 888       888 Y8P
//
/******************************************************************************/
void runFSM()
{
  switch (state)
  {
    //  d8b      888 888
    //  Y8P      888 888
    //           888 888
    //  888  .d88888 888  .d88b.
    //  888 d88" 888 888 d8P  Y8b
    //  888 888  888 888 88888888
    //  888 Y88b 888 888 Y8b.
    //  888  "Y88888 888  "Y8888
    //
    /***************************************************************************/
  case IDLE_FAN_OFF:
    // default state, show main display
    displayUpdate();

    //check for need to turn fan on
    checkHumidityLevel();
    if (humidityLevelTooHigh == true)
    {
      state = FAN_ON;
    }

    // check if MANUAL_ON mode is required ('ON' button click event)
    if (btnUpClickEvent == true)
    {
      // set Fan start timer before switching state
      fanRunStartTime /*=seconds*/ = (millis() / 1000);
      state = MANUAL_ON;
    }

    // check if MANUAL_OFF mode is required, so turning OFF the humidity
    // controller for a selected time
    if (btnDownClickEvent == true)
    {
      // set Fan start timer before switching state
      fanDisabledStartTime /*=seconds*/ = (millis() / 1000);
      state = MANUAL_OFF;
    }

    // check if DOWN button is being hold, turning OFF the system
    if (btnDownHoldEvent == true)
    {
      state = DISABLE;
    }

    if (btnSelectHoldEvent == true)
    {
      state = SET_THRESHOLD;
    }

    // reset all button events
    // * You NEED to have this button reset code in every state else the
    // * button event(s) will transfer over to the new state with unwanted resuls
    btnSelectClickEvent = false;
    btnSelectHoldEvent = false;
    btnUpClickEvent = false;
    btnUpHoldEvent = false;
    btnDownClickEvent = false;
    btnDownHoldEvent = false;
    btnUpDuringHoldEvent = false;
    btnDownDuringHoldEvent = false;

    break;

    //   .d888
    //  d88P"
    //  888
    //  888888 8888b.  88888b.        .d88b.  88888b.
    //  888       "88b 888 "88b      d88""88b 888 "88b
    //  888   .d888888 888  888      888  888 888  888
    //  888   888  888 888  888      Y88..88P 888  888
    //  888   "Y888888 888  888       "Y88P"  888  888
    //
  /***************************************************************************/
  case FAN_ON:
    displayUpdate();
    turnFanOn();
    checkHumidityLevel();

    //check for need to turn fan off
    if (humidityLevelTooHigh == false)
    {
      // set Fan off-delay timer before switching state
      fanRunStartTime /*=seconds*/ = (millis() / 1000);
      state = FAN_OFF_DELAY;
    }

    if (btnSelectHoldEvent == true)
    {
      turnFanOff();
      state = SET_THRESHOLD;
    }

    // check if MANUAL_OFF mode is required, so turning OFF the humidity
    // controller for a selected time
    if (btnDownClickEvent == true)
    {
      // set Fan start timer before switching state
      fanDisabledStartTime /*=seconds*/ = (millis() / 1000);
      state = MANUAL_OFF;
    }
    // check if DOWN button is being hold, turning OFF the system
    if (btnDownHoldEvent == true)
    {
      state = DISABLE;
    }

    // reset all button events
    // * You NEED to have this button reset code in every state else the
    // * button event(s) will transfer over to the new state with unwanted resuls
    btnSelectClickEvent = false;
    btnSelectHoldEvent = false;
    btnUpClickEvent = false;
    btnUpHoldEvent = false;
    btnDownClickEvent = false;
    btnDownHoldEvent = false;
    btnUpDuringHoldEvent = false;
    btnDownDuringHoldEvent = false;

    break;

    //                                                    888
    //                                                    888
    //                                                    888
    //  88888b.d88b.   8888b.  88888b.  888  888  8888b.  888       .d88b.  88888b.
    //  888 "888 "88b     "88b 888 "88b 888  888     "88b 888      d88""88b 888 "88b
    //  888  888  888 .d888888 888  888 888  888 .d888888 888      888  888 888  888
    //  888  888  888 888  888 888  888 Y88b 888 888  888 888      Y88..88P 888  888
    //  888  888  888 "Y888888 888  888  "Y88888 "Y888888 888       "Y88P"  888  888
    //
  /***************************************************************************/
  case MANUAL_ON:
    displayUpdate();
    turnFanOn();
    // fanRunStartTime was set to the current millis() value in the previous State
    // so now we can compare this 'start' time with the time passed using the current
    // millis() value. the max. time possible is 12 hours which is 12hx3600s=43200s
    // so it will fit in the unsigned INT variables.  If you want longer run times,
    // be sure to use LONG variables.
    fanRunTime /*=seconds*/ = (millis() / 1000) - (fanRunStartTime /*=seconds*/);

    // check the FAN 'ON' duration timer
    if ((fanRunTime /*=seconds*/ / 60) /*=converted to minutes*/ >= fanManualOnRunTime /*=minutes*/)
    {
      turnFanOff();
      // reset to default value before exit
      fanManualOnRunTime = 15;
      // go to new state
      state = IDLE_FAN_OFF;
    }

    // check if we want to exit the MANUAL ON mode
    if (btnDownClickEvent == true)
    {
      turnFanOff();
      // reset to default value before exit
      fanManualOnRunTime = 15;
      // go to new state
      state = IDLE_FAN_OFF;
    }

    // check if DOWN button is being hold, turning OFF the system
    if (btnDownHoldEvent == true)
    {
      // reset to default value before exit
      fanManualOnRunTime = 15;
      // go to new state
      state = DISABLE;
    }

    // if UP button is pressed while in MANUAL_ON mode, cycle through different off delay times
    if (btnUpClickEvent == true)
    {
      switch (fanManualOnRunTime)
      {
      case 15:
        fanManualOnRunTime = 30;
        break;
      case 30:
        fanManualOnRunTime = 60;
        break;
      case 60:
        fanManualOnRunTime = 90;
        break;
      case 90:
        fanManualOnRunTime = 120;
        break;
      case 120:
        fanManualOnRunTime = 180;
        break;
      case 180:
        fanManualOnRunTime = 240;
        break;
      case 240:
        fanManualOnRunTime = 300;
        break;
      case 300:
        fanManualOnRunTime = 360;
        break;
      case 360:
        fanManualOnRunTime = 720;
        break;
      case 720:
        fanManualOnRunTime = 15;
        break;
      }
    }

    // reset all button events
    // * You NEED to have this button reset code in every state else the
    // * button event(s) will transfer over to the new state with unwanted resuls
    btnSelectClickEvent = false;
    btnSelectHoldEvent = false;
    btnUpClickEvent = false;
    btnUpHoldEvent = false;
    btnDownClickEvent = false;
    btnDownHoldEvent = false;
    btnUpDuringHoldEvent = false;
    btnDownDuringHoldEvent = false;

    break; //case MANUAL_ON

    //                                                    888                .d888  .d888
    //                                                    888               d88P"  d88P"
    //                                                    888               888    888
    //  88888b.d88b.   8888b.  88888b.  888  888  8888b.  888       .d88b.  888888 888888
    //  888 "888 "88b     "88b 888 "88b 888  888     "88b 888      d88""88b 888    888
    //  888  888  888 .d888888 888  888 888  888 .d888888 888      888  888 888    888
    //  888  888  888 888  888 888  888 Y88b 888 888  888 888      Y88..88P 888    888
    //  888  888  888 "Y888888 888  888  "Y88888 "Y888888 888       "Y88P"  888    888
    //
    //
  /***************************************************************************/
  case MANUAL_OFF:
    displayUpdate();
    turnFanOff();
    // fanRunStartTime was set to the current millis() value in the previous State
    // so now we can compare this 'start' time with the time passed using the current
    // millis() value. the max. time possible is 12 hours which is 12hx3600s=43200s
    // so it will fit in the unsigned INT variables.  If you want longer run times,
    // be sure to use LONG variables.
    fanDisabledTime /*=seconds*/ = (millis() / 1000) - (fanDisabledStartTime /*=seconds*/);

    // check the FAN 'OFF' duration timer
    if ((fanDisabledTime /*=seconds*/ / 60) /*=converted to minutes*/ >= fanDisabledRunTime /*=minutes*/)
    {
      // reset to default value before exit
      fanDisabledRunTime = 30;
      // go to new state
      state = IDLE_FAN_OFF;
    }

    // check if return to normal operational mode is required ('ON' button click event)
    if (btnUpClickEvent == true)
    {
      // reset to default value before exit
      fanDisabledRunTime = 30;
      // go to new state
      state = IDLE_FAN_OFF;
    }

    // check if DOWN button is being hold, turning OFF the system
    if (btnDownHoldEvent == true)
    {
      // reset to default value before exit
      fanDisabledRunTime = 30;
      // go to new state
      state = DISABLE;
    }

    // if DOWN button is pressed while in MANUAL_OFF mode, cycle through different off delay times
    if (btnDownClickEvent == true)
    {
      switch (fanDisabledRunTime)
      {
      case 30:
        fanDisabledRunTime = 60;
        break;
      case 60:
        fanDisabledRunTime = 120;
        break;
      case 120:
        fanDisabledRunTime = 240;
        break;
      case 240:
        fanDisabledRunTime = 480;
        break;
      case 480:
        fanDisabledRunTime = 720;
        break;
      case 720:
        fanDisabledRunTime = 30;
        break;
      }
    }

    // reset all button events
    // * You NEED to have this button reset code in every state else the
    // * button event(s) will transfer over to the new state with unwanted resuls
    btnSelectClickEvent = false;
    btnSelectHoldEvent = false;
    btnUpClickEvent = false;
    btnUpHoldEvent = false;
    btnDownClickEvent = false;
    btnDownHoldEvent = false;
    btnUpDuringHoldEvent = false;
    btnDownDuringHoldEvent = false;
    break;

    //       888 d8b                   888      888
    //       888 Y8P                   888      888
    //       888                       888      888
    //   .d88888 888 .d8888b   8888b.  88888b.  888  .d88b.
    //  d88" 888 888 88K          "88b 888 "88b 888 d8P  Y8b
    //  888  888 888 "Y8888b. .d888888 888  888 888 88888888
    //  Y88b 888 888      X88 888  888 888 d88P 888 Y8b.
    //   "Y88888 888  88888P' "Y888888 88888P"  888  "Y8888
    //
  /***************************************************************************/
  case DISABLE:
    displayUpdate();
    turnFanOff();
    // check if UP button is clicked to turn the system on again
    if (btnUpClickEvent == true)
    {
      // go to new state
      state = IDLE_FAN_OFF;
    }
    // reset all button events
    // * You NEED to have this button reset code in every state else the
    // * button event(s) will transfer over to the new state with unwanted resuls
    btnSelectClickEvent = false;
    btnSelectHoldEvent = false;
    btnUpClickEvent = false;
    btnUpHoldEvent = false;
    btnDownClickEvent = false;
    btnDownHoldEvent = false;
    btnUpDuringHoldEvent = false;
    btnDownDuringHoldEvent = false;

    break;

    //   .d888                                 .d888  .d888           888          888
    //  d88P"                                 d88P"  d88P"            888          888
    //  888                                   888    888              888          888
    //  888888 8888b.  88888b.        .d88b.  888888 888888       .d88888  .d88b.  888  8888b.  888  888
    //  888       "88b 888 "88b      d88""88b 888    888         d88" 888 d8P  Y8b 888     "88b 888  888
    //  888   .d888888 888  888      888  888 888    888         888  888 88888888 888 .d888888 888  888
    //  888   888  888 888  888      Y88..88P 888    888         Y88b 888 Y8b.     888 888  888 Y88b 888
    //  888   "Y888888 888  888       "Y88P"  888    888          "Y88888  "Y8888  888 "Y888888  "Y88888
    //                                                                                               888
    //                                                                                          Y8b d88P
  /***************************************************************************/
  case FAN_OFF_DELAY:
    displayUpdate();

    fanRunTime /*=seconds*/ = (millis() / 1000) - (fanRunStartTime /*=seconds*/);

    // check if humidity level did rise above the threshold level *during* delay.
    // if so, cancel FAN_OFF_DELAY and go to FAN_ON state again.
    checkHumidityLevel();
    if (humidityLevelTooHigh == true)
    {
      state = FAN_ON;
    }

    // check the FAN off-delay timer
    if ((fanRunTime /*=seconds*/ / 60) >= fanSwitchOffDelayTime /*=minutes*/)
    {
      turnFanOff();
      // go to new state
      state = IDLE_FAN_OFF;
    }

    // check if MANUAL_ON fan off is equired ('OFF' button click event)
    if (btnDownClickEvent == true)
    {
      turnFanOff();
      // go to new state
      state = IDLE_FAN_OFF;
    }

    // check if DOWN button is being hold, turning OFF the system
    if (btnDownHoldEvent == true)
    {
      state = DISABLE;
    }

    // reset all button events
    // * You NEED to have this button reset code in every state else the
    // * button event(s) will transfer over to the new state with unwanted resuls
    btnSelectClickEvent = false;
    btnSelectHoldEvent = false;
    btnUpClickEvent = false;
    btnUpHoldEvent = false;
    btnDownClickEvent = false;
    btnDownHoldEvent = false;
    btnUpDuringHoldEvent = false;
    btnDownDuringHoldEvent = false;
    break;

    //                    888         888    888                               888               888      888
    //                    888         888    888                               888               888      888
    //                    888         888    888                               888               888      888
    //  .d8888b   .d88b.  888888      888888 88888b.  888d888 .d88b.  .d8888b  88888b.   .d88b.  888  .d88888
    //  88K      d8P  Y8b 888         888    888 "88b 888P"  d8P  Y8b 88K      888 "88b d88""88b 888 d88" 888
    //  "Y8888b. 88888888 888         888    888  888 888    88888888 "Y8888b. 888  888 888  888 888 888  888
    //       X88 Y8b.     Y88b.       Y88b.  888  888 888    Y8b.          X88 888  888 Y88..88P 888 Y88b 888
    //   88888P'  "Y8888   "Y888       "Y888 888  888 888     "Y8888   88888P' 888  888  "Y88P"  888  "Y88888
    //
    //
  /***************************************************************************/
  case SET_THRESHOLD:
    displayUpdate();

    if ((btnUpClickEvent == true || btnUpDuringHoldEvent == true) && humidityThreshold < 95)
    {
      humidityThreshold += 1;
    }
    if ((btnDownClickEvent == true || btnDownDuringHoldEvent == true) && humidityThreshold > 40)
    {
      humidityThreshold -= 1;
    }
    if (btnSelectClickEvent == true)
    {
      // save to eeprom
      EEPROM.writeInt(memBase, humidityThreshold);
      // go to new state, next menu item
      state = SET_HYSTERESIS;
    }

    // reset all button events
    // * You NEED to have this button reset code in every state else the
    // * button event(s) will transfer over to the new state with unwanted resuls
    btnSelectClickEvent = false;
    btnSelectHoldEvent = false;
    btnUpClickEvent = false;
    btnUpHoldEvent = false;
    btnDownClickEvent = false;
    btnDownHoldEvent = false;
    btnUpDuringHoldEvent = false;
    btnDownDuringHoldEvent = false;
    break;

    //                    888         888                        888
    //                    888         888                        888
    //                    888         888                        888
    //  .d8888b   .d88b.  888888      88888b.  888  888 .d8888b  888888 .d88b.  888d888
    //  88K      d8P  Y8b 888         888 "88b 888  888 88K      888   d8P  Y8b 888P"
    //  "Y8888b. 88888888 888         888  888 888  888 "Y8888b. 888   88888888 888
    //       X88 Y8b.     Y88b.       888  888 Y88b 888      X88 Y88b. Y8b.     888  d8b
    //   88888P'  "Y8888   "Y888      888  888  "Y88888  88888P'  "Y888 "Y8888  888  Y8P
    //                                              888
    //                                         Y8b d88P
    //                                          "Y88P"
  /***************************************************************************/
  case SET_HYSTERESIS:
    displayUpdate();

    if (btnUpClickEvent == true && humidityHysteresis <= 8)
    {
      humidityHysteresis += 1;
    }
    if (btnDownClickEvent == true && humidityHysteresis >= 4)
    {
      humidityHysteresis -= 1;
    }
    if (btnSelectClickEvent == true)
    {
      // save to eeprom
      EEPROM.writeInt(memBase + 2, humidityHysteresis);
      // go to new state, next menu item
      state = SET_SWITCH_OFF_DELAY;
    }

    // reset all button events
    // * You NEED to have this button reset code in every state else the
    // * button event(s) will transfer over to the new state with unwanted resuls
    btnSelectClickEvent = false;
    btnSelectHoldEvent = false;
    btnUpClickEvent = false;
    btnUpHoldEvent = false;
    btnDownClickEvent = false;
    btnDownHoldEvent = false;
    btnUpDuringHoldEvent = false;
    btnDownDuringHoldEvent = false;
    break;

    //                    888                   .d888  .d888           888          888
    //                    888                  d88P"  d88P"            888          888
    //                    888                  888    888              888          888
    //  .d8888b   .d88b.  888888       .d88b.  888888 888888       .d88888  .d88b.  888  8888b.  888  888
    //  88K      d8P  Y8b 888         d88""88b 888    888         d88" 888 d8P  Y8b 888     "88b 888  888
    //  "Y8888b. 88888888 888         888  888 888    888         888  888 88888888 888 .d888888 888  888
    //       X88 Y8b.     Y88b.       Y88..88P 888    888         Y88b 888 Y8b.     888 888  888 Y88b 888
    //   88888P'  "Y8888   "Y888       "Y88P"  888    888          "Y88888  "Y8888  888 "Y888888  "Y88888
    //                                                                                                888
    //                                                                                           Y8b d88P
    //                                                                                            "Y88P"
  /***************************************************************************/
  case SET_SWITCH_OFF_DELAY:
    displayUpdate();

    if ((btnUpClickEvent == true || btnUpDuringHoldEvent) && fanSwitchOffDelayTime < 60)
    {
      fanSwitchOffDelayTime += 1;
    }
    if ((btnDownClickEvent == true || btnDownDuringHoldEvent) && fanSwitchOffDelayTime > 0)
    {
      fanSwitchOffDelayTime -= 1;
    }
    if (btnSelectClickEvent == true)
    {
...

This file has been truncated, please download it to see its full contents.

Credits

Erik de Ruiter
8 projects • 131 followers
No electronics education but enjoying my new found hobby. Never thought it would be possible to make things myself but here we are...

Comments