Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 3 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 |
A new version is in the making with color TFT display, clock and other upgrades I didn't think of yet. Also I will design a 'real' PCB this time. It's so easy to make and cheap these days.
UPDATE v1.12:v1.12 sketch: Although, when asking, others 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.
PLEASE NOTE:I got notice that the provided sketch WILL NOT compile using the Arduino IDE. I compiled it successfully with VScode/PlatformIO but forgot to check if the Arduino IDE would work...
Alternatively, upload the HEX file of use VScode with the PlatformIO extension. See the Attachtments on this page.
So... I'm planning to rebuild the controller completely in the coming months using a controller with more memory and a TFT colour display.
----------
Note 2:After a few months of use, the chinese Arduino behaved erratically. I had already a defective chinese Nano dying on me in another project.
So... I will invest in Genuine Arduino boards from this time forward!
WHY I made this: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 S&P ventilation 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 commercial 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 descent mechanical switch for just under 100 euros.
PhotosAs I am very fond of the Arduino, so I (again) decided to make myself the things I need, in this case a "Bathroom Ventilation Fan Controller"
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.
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 (relay) 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.
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
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 MANUALLY 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.
(only possible in state IDLE/fan off or fan ON)
-- 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.
Some remarks...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 (only just) in the Arduino Nano (or Uno)
I maybe over-commented this sketch but I'm a NOT an experienced programmer 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 platform: even beginners can enjoy coding and grow and be more efficient later on.
The Fan RelayThe relay: I used a 5V relay of a good brand (!) I don't want to risk my house and family by using a cheap chinese one. They do make good stuff BTW but then you'll pay the same price as a locally sourced product.
***********************************************
YOU WILL NEED A TRANSISTOR to drive the relay!!***********************************************
Else you will blow up you Arduino port...
See the schematic.
I repurposed an old pcb with safe case to mount the parts I needed and to connect the high voltage without risk. Used a 5A fuse between the fan and the 230V input
I used a free program 'VeeCAD' (<- click) to convert a schematic to stripboard. Featuring 'ratsnest' wires it's easy to wire and this way you won't forget any connections!
See the ATTACHMENTS section for download of these files.
you don't need to use VeeCAD to make this project. If you print the 'scale 1 to 1 documents' you can easily make the breaks (cuts) on the strip board and wire the board.
More pictures:UPDATE v1.12: added PIR sensor input to prevent OLED burg-in
C/C++// 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
// 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.
Comments