// 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
//
/*
https://www.hackster.io/edr1924/bathroom-ventilation-fan-controller-v1-0-0590ab
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 <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
//#include <Arduino.h>
#include <EEPROMex.h>
//#include <U8g2lib.h>
//#include <Adafruit_BME280.h>
#include <OneButton.h>
#include <DHT.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
//------------------------------------------------------------------------------------------------
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3c ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
//------------------------------------------------------------------------------------------------
/*
// Declaration for SSD1306 display connected using software SPI (default case):
#define OLED_MOSI 9
#define OLED_CLK 10
#define OLED_DC 11
#define OLED_CS 12
#define OLED_RESET 13
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT,
OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);
*/
/* Comment out above, uncomment this block to use hardware SPI
#define OLED_DC 6
#define OLED_CS 7
#define OLED_RESET 8
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT,
&SPI, OLED_DC, OLED_RESET, OLED_CS);
*/
//------------------------------------------------------------------------------------------------
// *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 '', see below.
// percent icon
#define percent_width 10
#define percent_height 9
static const unsigned char percent_bits[] = {
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[] = {
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[] = {
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[] = {
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[] = {
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[] = {
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[] = {
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[] = {
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[] = {
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 DHTPIN 2 // Digital pin connected to the DHT sensor
#define PIN_RELAIS 8
//#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'
//
int 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.
//
// Object BME280 sensor
//Adafruit_BME280 bme; // I2C
#define DHTTYPE DHT22 // DHT 22 (AM2302), AM2321
DHT dht(DHTPIN, DHTTYPE);
// 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() {
Wire.begin();
Wire.setClock(100000);
Serial.begin(9600);
Serial.println(F("Bathroom Fan Controller"));
//bme.begin();
//------------- I2C -------------
//display.begin();
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3c)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;);
}
//------------- SPI -------------
/*
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if (!display.begin(SSD1306_SWITCHCAPVCC)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;); // Don't proceed, loop forever
}
*/
dht.begin();
//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);
display.clearDisplay();
display.setTextSize(1); // Normal 1:1 pixel scale
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(10, 10);
display.println(F("Hello, world!"));
display.display();
delay(2000);
testscrolltext(); // Draw scrolling text
delay(2000);
}
// 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;
// Reading temperature or humidity takes about 250 milliseconds!
// Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
//float h = dht.readHumidity();
// Read temperature as Celsius (the default)
//float t = dht.readTemperature();
// Read temperature as Fahrenheit (isFahrenheit = true)
//float f = dht.readTemperature(true);
//sensorTemp = bme.readTemperature();
sensorTemp = dht.readTemperature(true);
/*
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();
sensorHumidity = dht.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)(sensorHumidity * 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
display.clearDisplay();//display.clearBuffer(); // clear the internal memory
/***************************************************************************/
// display lines
if (state == IDLE_FAN_OFF ||
state == FAN_OFF_DELAY ||
state == FAN_ON ||
state == MANUAL_ON)
{
// draw lines
//display.drawHLine(80, 46, 48);
//display.drawVLine(79, 0, 64);
}
/***************************************************************************/
//display SET humidity value
if (state == IDLE_FAN_OFF ||
state == FAN_OFF_DELAY ||
state == FAN_ON)
{
//display.setFont(u8g2_font_helvR24_tn);
display.setCursor(82, 24);
display.print(humidityThreshold);
// percent symbol for set humidity value
display.drawBitmap(118, 0, percent_bits, percent_width, percent_height, SSD1306_WHITE);
}
/***************************************************************************/
#ifdef DISPLAY_HYSTERESIS
//display humidity hysteresis value
if (state == IDLE_FAN_OFF ||
state == FAN_OFF_DELAY ||
state == FAN_ON)
{
//display.setFont(u8g2_font_helvR10_tr);////display.setFont(u8g2_font_helvB12_tn);
display.setCursor(106, 41);
display.print(humidityHysteresis);
// hysteresis symbol
display.drawBitmap(117, 29, hysteresis_bits, hysteresis_width, hysteresis_height, SSD1306_WHITE);
}
#endif
/***************************************************************************/
// display ACTUAL humidity
if (state == IDLE_FAN_OFF ||
state == FAN_OFF_DELAY ||
state == FAN_ON ||
state == MANUAL_ON)
{
//display.setFont(u8g2_font_fub42_tn);
display.setCursor(0, 43);
display.print(sensorHumidity);
// percent symbol for current humidity value, blinking
display.drawBitmap(65, 0, (sensorIsRead == 0 ? black_bits : percent_bits), percent_width, percent_height, SSD1306_WHITE);
}
/***************************************************************************/
// display temperature
if (state == IDLE_FAN_OFF ||
state == FAN_OFF_DELAY ||
state == FAN_ON ||
state == MANUAL_ON)
{
//display.setFont(u8g2_font_helvR10_tr);////display.setFont(u8g2_font_helvB12_tn);
display.setCursor(83, 64);
display.print((sensorTemp), 1);
// degrees celcius symbol for temperature value
display.drawBitmap(115, 52, celcius_bits, celcius_width, celcius_height, SSD1306_WHITE);
}
/***************************************************************************/
// fan symbol
if (state == FAN_ON ||
state == FAN_OFF_DELAY ||
state == MANUAL_ON)
{
display.drawBitmap(0, 48, fan_bits, fan_width, fan_height, SSD1306_WHITE);
}
/***************************************************************************/
// 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
//display.setFont(u8g2_font_helvR10_tr);////display.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
//display.drawStr(24, 64, bufferTime);
display.setCursor(24, 64);
display.print(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;
//display.setFont(u8g2_font_helvR10_tr);
//display.drawStr(3, 15, "Temp. Switched Off");
display.setCursor(3, 15);
display.print("Temp. Switched Off");
//display.drawRFrame(0, 23, 128, 40, 3);
// display FAN OFF duration
//display.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
//display.drawStr(9, 55, bufferTime);
display.setCursor(9, 55);
display.print(bufferTime);
// draw stopwatch symbol next to the countdown time
display.drawBitmap(96, 31, stopwatch_bits, stopwatch_width, stopwatch_height, SSD1306_WHITE);
}
/***************************************************************************/
// AUTO mode Fan off-delay time
if (state == FAN_OFF_DELAY) {
// display 'DELAY' to indicate Fan will run for set time before turning off
//display.setFont(u8g2_font_helvR12_tr);
// display sprintf string
//display.drawStr(21, 63, "DELAY");
display.setCursor(21, 63);
display.print("DELAY");
}
/***************************************************************************/
// DISABLE mode, system shut down
if (state == DISABLE) {
// display 'DELAY' to indicate Fan will run for set time before turning off
//display.setFont(u8g2_font_helvR24_tr);
//display.drawStr(44, 44, "Off");
display.setCursor(44, 44);
display.print("Off");
}
/***************************************************************************/
// display MANUAL_ON mode symbol
if (state == MANUAL_ON)
{
display.drawBitmap(88, 0, hand_bits, hand_width, hand_height, SSD1306_WHITE);
}
/***************************************************************************/
// display FAN_DELAY set menu
if (state == SET_THRESHOLD) {
//display.setFont(u8g2_font_helvR12_tr);
//display.drawStr(0, 13, "Set threshold");
display.setCursor(0, 13);
display.print("Set threshold");
////display.drawHLine(0, 15, 127);
//display.drawRFrame(4, 23, 77, 40, 3);
//display.drawBitmap(90, 25, upArrow_width, upArrow_height, upArrow_bits);
//display.drawBitmap(90, 46, downArrow_width, downArrow_height, downArrow_bits);
//display.setFont(u8g2_font_helvR10_tr);
//display.drawStr(105, 49, "S");
display.setCursor(105, 49);
display.print("S");
//display.setFont(u8g2_font_helvR24_tn);
display.setCursor(9, 55);
display.print(humidityThreshold);
display.drawBitmap(50, 31, percent_bits, percent_width, percent_height, percent_bits, SSD1306_WHITE);
}
/***************************************************************************/
// display FAN_DELAY set menu
if (state == SET_SWITCH_OFF_DELAY) {
//display.setFont(u8g2_font_helvR12_tr);
//display.drawStr(0, 13, "Set fan delay");
display.setCursor(0, 13);
display.print("Set fan delay");
////display.drawHLine(0, 15, 127);
//display.drawRFrame(4, 23, 77, 40, 3);
//display.drawBitmap(90, 25, upArrow_width, upArrow_height, upArrow_bits);
//display.drawBitmap(90, 46, downArrow_width, downArrow_height, downArrow_bits);
//display.setFont(u8g2_font_helvR10_tr);
display.setCursor(105, 49);
//display.drawStr(105, 49, "S");
display.print("S");
//display.setFont(u8g2_font_helvR24_tn);
display.setCursor(9, 55);
display.print(fanSwitchOffDelayTime);
//display.setFont(u8g2_font_helvR10_tr);
display.setCursor(50, 55);
//display.drawStr(50, 55, "Min.");
display.print("Min.");
}
/***************************************************************************/
// display FAN_DELAY set menu
if (state == SET_HYSTERESIS) {
//display.setFont(u8g2_font_helvR12_tr);
display.setCursor(0, 13);
//display.drawStr(0, 13, "Set Hysteresis");
display.print("Set Hysteresis");
////display.drawHLine(0, 15, 127);
//display.drawRFrame(4, 23, 77, 40, 3);
//display.drawBitmap(90, 25, upArrow_width, upArrow_height, upArrow_bits);
//display.drawBitmap(90, 46, downArrow_width, downArrow_height, downArrow_bits);
//display.setFont(u8g2_font_helvR10_tr);
display.setCursor(105, 49);
//display.drawStr(105, 49, "S");
display.print("S");
//display.setFont(u8g2_font_helvR24_tn);
display.setCursor(29, 55);
display.print(humidityHysteresis);
display.drawBitmap(50, 31, percent_bits, percent_width, percent_height, percent_bits, SSD1306_WHITE);
}
/***************************************************************************/
// transfer internal memory to the display
if (turnOledDisplayOFF == true) {
display.clearDisplay();//display.clearBuffer(); // clear the internal memory
}
//display.sendBuffer();
display.display();
}
// 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);
}
void testscrolltext(void) {
display.clearDisplay();
display.setTextSize(2); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.setCursor(10, 0);
display.println(F("scroll"));
display.display(); // Show initial text
delay(100);
// Scroll in various directions, pausing in-between:
display.startscrollright(0x00, 0x0F);
delay(2000);
display.stopscroll();
delay(1000);
display.startscrollleft(0x00, 0x0F);
delay(2000);
display.stopscroll();
delay(1000);
display.startscrolldiagright(0x00, 0x07);
delay(2000);
display.startscrolldiagleft(0x00, 0x07);
delay(2000);
display.stopscroll();
delay(1000);
}