// https://www.hackster.io/edr1924/bathroom-ventilation-fan-controller-v1-0-0590ab
//
// https://www.hackster.io/edr1924/bathroom-ventilation-fan-controller-v2-1-ba99bc
//
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h> // https://github.com/adafruit/Adafruit_SSD1306/blob/master/examples/ssd1306_128x64_i2c/ssd1306_128x64_i2c.ino
#include "RBDdimmer.h" // https://github.com/RobotDynOfficial/RBDDimmer/blob/master/examples/SimplePotentiometer/SimplePotentiometer.ino
//#include "icons.h"
#include <Arduino.h>
//#include <EEPROMex.h> // https://github.com/thijse/Arduino-EEPROMEx/blob/master/examples/EEPROMEx/EEPROMEx.ino
//#include <OneButton.h> // https://github.com/mathertel/OneButton/blob/master/examples/FunctionalButton/FunctionalButton.ino
#include <DHT.h> // https://github.com/adafruit/DHT-sensor-library/blob/master/examples/DHTtester/DHTtester.ino
#include "SparkFun_External_EEPROM.h" // Click here to get the library: http://librarymanager/All#SparkFun_External_EEPROM
ExternalEEPROM myMem;
#define ZCpin 2
#define ACpulsePin 3
#define DHTPIN 7 // Digital pin connected to the DHT sensor
#define PIN_RELAY 9
#define PIN_BUTTON_DOWN 4
#define PIN_BUTTON_UP 6
#define PIN_BUTTON_SELECT 5
#define PIR_SENSOR 8
#define rLed_pin 10
#define gLed_pin 11
#define bLed_pin 12
#define PIN_I2C_CLOCK A5
#define PIN_I2C_DATA A4
dimmerLamp dimmer(ACpulsePin); //initialase port for dimmer for MEGA, Leonardo, UNO, Arduino M0, Arduino Zero
int outVal = 0;
#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);
// 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
// State Machine States
struct SETTINGS { // https://www.tutorialspoint.com/structs-in-arduino-program
bool enable = false;
byte humidityHysteresis = 5 /*Rel.Humidity*/;
byte humidityThreshold = 0;
byte fanSwitchOffDelayTime = 30 /*minutes*/;
};
SETTINGS setting;
struct SENSOR { // https://www.tutorialspoint.com/structs-in-arduino-program
int humidityFraction;
float humidity;
float temperature;
float dewPoint;
float heatindex;
int fanSwitchOffDelayTime = 30 /*minutes*/;
};
SENSOR sensor;
struct BUTTONS { // https://www.tutorialspoint.com/structs-in-arduino-program
bool selectClick = false;
bool selectHold = false;
bool upClick = false;
bool upHold = false;
bool downClick = false;
bool downHold = false;
bool upDuringHold = false;
bool downDuringHold = false;
};
BUTTONS button;
bool buttonUpState;
bool buttonSelectState;
bool buttonDownState;
bool motion = false;
bool inputChange = false;
long currentMilli = 0;
long prevMilli = 0;
long readInputTime = 0;
long readDHTtime = 0;
//float temperature = 0.0;
//float humidity = 0.0;
//float heatindex = 0.0;
//float dewPoint = 0.0;
//--------------------------------------------------------------------------------
// Nextion DatatimeHoursfanManualOnRunTime
unsigned int hmiMinSpeed = 100;
unsigned int hmiMaxSpeed = 255;
bool hmiPIRtrigger = false;
bool hmiTimerTrigger = false;
bool hmiCalcHumidity = true;
bool hmiCalcRelHumidity = false;
bool hmiCalcDewPoint = false;
unsigned int hmiThreshold = 50;
unsigned int hmiHysteresis = 5;
unsigned int hmiScreenTimeout = 30; // minutes
unsigned int hmiManRunTime = 10; // minutes
unsigned int hmiAutoRunTime = 10; // minutes
unsigned int hmiSensorUpdateTime = 1000; // seconds
//--------------------------------------------------------------------------------
// 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;
//######################################################################################################################
void setup() {
Wire.begin();
Serial.begin(9600);
Serial.println(F("Fan Controller Startup"));
dht.begin();
Serial.println(F("DHT Startup Complete"));
delay(100);
//dimmer.begin(NORMAL_MODE, ON); //dimmer initialisation: name.begin(MODE, STATE)
Serial.println(F("Dimmer Startup Complete"));
delay(100);
//pinMode(PIN_DISPLAY_RESET, OUTPUT);
pinMode(rLed_pin, OUTPUT);
pinMode(gLed_pin, OUTPUT);
pinMode(bLed_pin, OUTPUT);
pinMode(PIN_RELAY, OUTPUT);
pinMode(PIR_SENSOR, INPUT);
pinMode(PIN_BUTTON_DOWN, INPUT_PULLUP);
pinMode(PIN_BUTTON_UP, INPUT_PULLUP);
pinMode(PIN_BUTTON_SELECT, INPUT_PULLUP);
Serial.println(F("Pinmodes Startup Complete"));
digitalWrite(rLed_pin, LOW);
digitalWrite(gLed_pin, HIGH);
digitalWrite(bLed_pin, HIGH);
delay(100);
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;); // Don't proceed, loop forever
} else {
Serial.println(F("OLED Startup Complete"));
}
display.clearDisplay();
display.setTextSize(2); // Normal 1:1 pixel scale
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(0, 0); // Start at top-left corner
display.println(F(" Bathroom"));
display.setTextSize(3); // Normal 1:1 pixel scale
display.println(F(" Fan"));
display.setTextSize(2); // Normal 1:1 pixel scale
display.println(F("Controller"));
//display.drawBitmap(0, 48, fan_bits, fan_width, fan_height, SSD1306_WHITE);
//display.drawBitmap( (display.width() - fan_width ) / 2, (display.height() - fan_height) / 2, fan_bits, fan_width, fan_height, 1);
display.display();
delay(2000);
// Clear the buffer
//display.clearDisplay();
//display.drawRect(0, 0, 128, 64, SSD1306_WHITE);
//display.display(); // Update screen with each newly-drawn rectangle
displayUpdate();
// 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 ? setting.humidityThreshold = 65 : setting.humidityThreshold = EEPROM.readInt(memBase);
//EEPROM.readInt(memBase + 2) > 10 ? setting.humidityHysteresis = 4 : setting.humidityHysteresis = EEPROM.readInt(memBase + 2);
//EEPROM.readInt(memBase + 4) > 60 ? setting.fanSwitchOffDelayTime = 30 : setting.fanSwitchOffDelayTime = EEPROM.readInt(memBase + 4);
// Default to the Qwiic 24xx512 EEPROM: https://www.sparkfun.com/products/18355
myMem.setMemoryType(512); // Valid types: 0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1025, 2048
myMem.begin();
Serial.println(F("Setup Complete"));
}
//######################################################################################################################
void loop() {
readInputs();
currentMilli = millis();
if (currentMilli > readDHTtime + 1000) {
// read_DHT();
readDHTtime = currentMilli;
}
//dimmer.setPower(outVal); // setPower(0-100%);
//runFSM();
}
//######################################################################################################################
void displayUpdate() {
display.clearDisplay();
display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SSD1306_WHITE);
display.drawRect(0, 53, SCREEN_WIDTH, 16, SSD1306_WHITE);
display.drawRect(42, 53, 44, 16, SSD1306_WHITE);
display.setTextSize(1); // Normal 1:1 pixel scale
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(7, 55); // Start at top-left corner
display.print(F("DOWN"));
display.setCursor(47, 55); // Start at top-left corner
display.print(F("SELECT"));
display.setCursor(102, 55); // Start at top-left corner
display.print(F("UP"));
display.display(); // Update screen with each newly-drawn rectangle
}
//######################################################################################################################
void debug_display() {
display.clearDisplay();
display.setTextSize(1); // Normal 1:1 pixel scale
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(0, 0); // Start at top-left corner
//display.print(F("motion = ")); display.println(motion);
display.print(F("buttonDn = ")); display.println(buttonDownState);
display.print(F("buttonUp = ")); display.println(buttonUpState);
display.print(F("buttonSelect = ")); display.println(buttonSelectState);
display.print(F("Temp = ")); display.println(sensor.temperature, 1);
display.print(F("Humidity = ")); display.println(sensor.humidity, 1);
display.print(F("heat index = ")); display.println(sensor.heatindex, 1);
display.print(F("DewPoint = ")); display.println(sensor.dewPoint, 1);
display.display();
}
//######################################################################################################################
void readInputs() {
buttonUpState = !digitalRead(PIN_BUTTON_UP);
buttonSelectState = !digitalRead(PIN_BUTTON_SELECT);
buttonDownState = !digitalRead(PIN_BUTTON_DOWN);
if (buttonUpState) button.upClick = true;
if (buttonSelectState) button.selectClick = true;
if (buttonDownState) button.downClick = true;
if (buttonUpState || buttonDownState || buttonSelectState) {
inputChange = true;
}
if (inputChange) {
//debug_display();
displayUpdate();
inputChange = false;
}
read_DHT();
}
//######################################################################################################################
void read_DHT() {
if ((millis() - previousSensorReadTime) > 1000) {
// Reading temperature or humidity takes about 250 milliseconds!
// Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
sensor.humidity = dht.readHumidity();
//float t = dht.readTemperature(); // Read temperature as Celsius (the default)
sensor.temperature = dht.readTemperature(true); // Read temperature as Fahrenheit (isFahrenheit = true)
/*
// Check if any reads failed and exit early (to try again).
if (isnan(sensor.humidity) || isnan(sensor.temperature)) {
Serial.println(F("Failed to read from DHT sensor!"));
//display.clearDisplay();
//display.setTextSize(2); // Normal 1:1 pixel scale
//display.setTextColor(SSD1306_WHITE); // Draw white text
//display.setCursor(0, 0); // Start at top-left corner
//display.println(F("DHT")); display.println(F("FAIL"));
return;
}
*/
//sensor.heatindex = dht.computeHeatIndex(sensor.temperature, sensor.humidity); // Compute heat index in Fahrenheit (the default)
//float hic = dht.computeHeatIndex(t, h, false); // Compute heat index in Celsius (isFahreheit = false)
sensor.dewPoint = (((sensor.temperature - 32) * (5 / 9)) - (100 - sensor.humidity) / 5); // dewPoint calculation using Celsius value
//sensor.dewPoint = (DP * 9 / 5) + 32; // converts dewPoint calculation to fahrenheit
runFSM();
//debug_display();
sensorIsRead = !sensorIsRead;
previousSensorReadTime = millis();
}
}
//######################################################################################################################
//######################################################################################################################
void runFSM() {
switch (state) {
//-------------------------------------------------------------------------------------------
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 (button.upClick == true) {
// set Fan start timer before switching state
fanRunStartTime = (millis() / 1000);
state = MANUAL_ON;
}
// check if MANUAL_OFF mode is required, so turning OFF the humidity
// controller for a selected time
if (button.downClick == true) {
// set Fan start timer before switching state
fanDisabledStartTime = (millis() / 1000);
state = MANUAL_OFF;
}
// check if DOWN button is being hold, turning OFF the system
if (button.downHold == true) {
state = DISABLE;
}
if (button.selectHold == 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
button.selectClick = false;
button.selectHold = false;
button.upClick = false;
button.upHold = false;
button.downClick = false;
button.downHold = false;
button.upDuringHold = false;
button.downDuringHold = false;
break;
//-------------------------------------------------------------------------------------------
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 = (millis() / 1000);
state = FAN_OFF_DELAY;
}
if (button.selectHold == true) {
turnFanOff();
state = SET_THRESHOLD;
}
// check if MANUAL_OFF mode is required, so turning OFF the humidity
// controller for a selected time
if (button.downClick == true) {
// set Fan start timer before switching state
fanDisabledStartTime = (millis() / 1000);
state = MANUAL_OFF;
}
// check if DOWN button is being hold, turning OFF the system
if (button.downHold == 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
button.selectClick = false;
button.selectHold = false;
button.upClick = false;
button.upHold = false;
button.downClick = false;
button.downHold = false;
button.upDuringHold = false;
button.downDuringHold = false;
break;
//-------------------------------------------------------------------------------------------
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 = (millis() / 1000) - (fanRunStartTime);
// check the FAN 'ON' duration timer
if ((fanRunTime/ 60) >= fanManualOnRunTime) {
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 (button.downClick == 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 (button.downHold == 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 (button.upClick == 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
button.selectClick = false;
button.selectHold = false;
button.upClick = false;
button.upHold = false;
button.downClick = false;
button.downHold = false;
button.upDuringHold = false;
button.downDuringHold = false;
break; //case MANUAL_ON
/*
//-------------------------------------------------------------------------------------------
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 = (millis() / 1000) - (fanDisabledStartTime );
// check the FAN 'OFF' duration timer
if ((fanDisabledTime / 60) >= fanDisabledRunTime ) {
// 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 (button.upClick == 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 (button.downHold == 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 (button.downClick == 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
button.selectClick = false;
button.selectHold = false;
button.upClick = false;
button.upHold = false;
button.downClick = false;
button.downHold = false;
button.upDuringHold = false;
button.downDuringHold = false;
break;
//-------------------------------------------------------------------------------------------
case DISABLE:
displayUpdate();
turnFanOff();
// check if UP button is clicked to turn the system on again
if (button.upClick == 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
button.selectClick = false;
button.selectHold = false;
button.upClick = false;
button.upHold = false;
button.downClick = false;
button.downHold = false;
button.upDuringHold = false;
button.downDuringHold = false;
break;
//-------------------------------------------------------------------------------------------
case FAN_OFF_DELAY:
displayUpdate();
fanRunTime = (millis() / 1000) - (fanRunStartTime );
// 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 / 60) >= fanSwitchOffDelayTime ) {
turnFanOff();
// go to new state
state = IDLE_FAN_OFF;
}
// check if MANUAL_ON fan off is equired ('OFF' button click event)
if (button.downClick == true) {
turnFanOff();
// go to new state
state = IDLE_FAN_OFF;
}
// check if DOWN button is being hold, turning OFF the system
if (button.downHold == 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
button.selectClick = false;
button.selectHold = false;
button.upClick = false;
button.upHold = false;
button.downClick = false;
button.downHold = false;
button.upDuringHold = false;
button.downDuringHold = false;
break;
//-------------------------------------------------------------------------------------------
case SET_THRESHOLD:
displayUpdate();
if ((button.upClick == true || button.upDuringHold == true) && setting.humidityThreshold < 95) {
setting.humidityThreshold += 1;
}
if ((button.downClick == true || button.downDuringHold == true) && setting.humidityThreshold > 40) {
setting.humidityThreshold -= 1;
}
if (button.selectClick == true) {
// save to eeprom
EEPROM.writeInt(memBase, setting.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
button.selectClick = false;
button.selectHold = false;
button.upClick = false;
button.upHold = false;
button.downClick = false;
button.downHold = false;
button.upDuringHold = false;
button.downDuringHold = false;
break;
//-------------------------------------------------------------------------------------------
case SET_HYSTERESIS:
displayUpdate();
if (button.upClick == true && setting.humidityHysteresis <= 8) {
setting.humidityHysteresis += 1;
}
if (button.downClick == true && setting.humidityHysteresis >= 4) {
setting.humidityHysteresis -= 1;
}
if (button.selectClick == true) {
// save to eeprom
EEPROM.writeInt(memBase + 2, setting.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
button.selectClick = false;
button.selectHold = false;
button.upClick = false;
button.upHold = false;
button.downClick = false;
button.downHold = false;
button.upDuringHold = false;
button.downDuringHold = false;
break;
//-------------------------------------------------------------------------------------------
// Set Off Delay
case SET_SWITCH_OFF_DELAY:
displayUpdate();
if ((button.upClick == true || button.upDuringHold) && fanSwitchOffDelayTime < 60) {
fanSwitchOffDelayTime += 1;
}
if ((button.downClick == true || button.downDuringHold) && fanSwitchOffDelayTime > 0) {
fanSwitchOffDelayTime -= 1;
}
if (button.selectClick == 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
button.selectClick = false;
button.selectHold = false;
button.upClick = false;
button.upHold = false;
button.downClick = false;
button.downHold = false;
button.upDuringHold = false;
button.downDuringHold = false;
break;
*/
}
} // END of runFSM
//######################################################################################################################
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();
}
}
//######################################################################################################################
void checkHumidityLevel() {
if (sensor.humidity >= setting.humidityThreshold) {
humidityLevelTooHigh = true;
} else if (sensor.humidity <= (setting.humidityThreshold - setting.humidityHysteresis)) {
humidityLevelTooHigh = false;
}
}
//######################################################################################################################
// RELAY
//######################################################################################################################
void turnFanOn() {
// switch ON the relais
digitalWrite(PIN_RELAY, HIGH);
}
void turnFanOff() {
// switch OFF the relais
digitalWrite(PIN_RELAY, LOW);
}