亲,“电路城”已合并升级到更全、更大、更强的「新与非网」。点击查看「新与非网」

本网页已闲置超过3分钟,按键盘任意键或点击空白处,即可回到网页

教你做一个浴室通风风扇控制器

发布时间:2022-10-30
分享到:

教你做一个浴室通风风扇控制器

发布时间:2022-10-30
分享到:

距离这个项目完成到现在为止,我已经使用了9个月,它的表现很好! 控制器也可以非常有效地将湿度保持在设定的水平。

背景
项目的主题是我想了一个很好的办法来降低我们浴室的湿度。在此之前,我有一个非常好和安静的标准普尔风扇,但它是需要我们手动操作的,有时我们会忘记打开或关闭它。

所以在环顾四周之后,我发现只有少数几个商业选择。是的,你可以买一个内置控制器的风扇,但它们很贵,手动设置非常有限。

一个独立的湿度控制器或开关很难找到,下降机械类开关又会非常贵。

控制器描述
因为我非常喜欢Arduino,所以我决定给自己做一些我需要的东西,在这种情况下,正是一个“浴室通风风扇控制器”的诞生。

控制器具有以下功能和选项:

  • 测量相对湿度和温度。
  • 打开风扇(通过继电器),当湿度下降时关闭风扇。
  • 可选:在湿度下降后,风扇将保持一段可选择的时间。(把湿度再调低一点)
  • 手动打开风扇15m, 30m, 1,2,3,4,5,6或12小时。(对于出现异味的情况很有用)
  • 手动关闭风扇控制器系统30、1、2、4、8或12小时。(想睡觉但风扇开着?把它关掉!)
  • 将风扇控制器系统完全关闭,直到手动打开。(休假时间)
  • 用户设置存储在EEPROM中,并在复位/断电后保存。

用户设置菜单:

—阈值范围:40%RH ~ 95%RH。—迟滞量范围:3%RH ~ 9%RH。—风扇关闭延时范围:0 ~ 60min。

按钮:

有3个按钮,从上到下分别是:

—ON / UP—OFF / DOWN—表壳侧面的SELECT—系统复位按钮

控制器的显示说明

  • 在显示器的左上角,您可以看到CURRENT湿度值,每秒钟更新一次。百分比(%)符号将闪烁表示这一点。
  • 在右上角我们有湿度阈值。
  • 低于阈值,您将看到设置的HYSTERESIS值(可选)
  • 右下方显示当前的TEMPERATURE。
  • 在左下方的风扇图标将指示何时风扇被打开。如果风扇关闭延迟被激活,该图标的右边会显示一个文本'DELAY'。

系统说明
无事件/系统空闲:

湿度和温度每秒钟都会被测量并更新一次,在测量的湿度值旁边以闪烁的“%”字符表示。传感器是非常敏感且准确。因此,它将对变化的条件作出快速和可靠的反应。

事件:湿度上升等于或超过阈值:

当当前湿度达到阈值时,风扇(继电器)将接通,由显示屏左下方的Fan符号指示。

风扇将一直开着,直到湿度降到阈值* -滞回值*以下。所以如果阈值是70%,滞回量是5,那么风扇将在65%的相对湿度关闭。

注意:显然,迟滞是非常重要的!如果不使用,你会有一个风扇开关左右的阈值。

事件:湿度下降到阈值以下*减去迟滞量*:

当湿度低于阈值和滞回值时,风扇关闭。

例外:如果你设置了风扇关闭延迟时间,那么风扇将保持在用户确定的时间(菜单设置)

手动干预措施:

我故意在控制器(AFAIK)中添加了一些有用的特性。例如:

  • 厕所气味不理想,你可以手动打开风扇设定的时间。
  • 你想睡觉,但风扇开着,因为湿度太高,但风扇的噪音令人不安。你可以将系统关闭一段时间,之后它将继续测量,并在需要时打开。通风对驱除霉菌很重要,这样你就不会忘记再次打开系统。
  • 你要去度假了,把系统完全关掉。

按键说明

ON / UP:

-系统空闲(风扇关闭):按下风扇将打开设定的时间,从15分钟开始。再次按上键,以预先确定的步骤增加风扇开机时间。(最大12小时)

—SYSTEM OFF:重新打开系统

—SYSTEM MANUALLY OFF:系统恢复到SYSTEM IDLE状态

—MENU ACTIVE:当按下数值增加时,长按快速增加数值。

OFF / DOWN:

—SYSTEM IDLE(风扇OFF):按下该键,系统将在设定的时间内关机,从30分钟开始关机。再次按下以增加预定步骤的关闭时间。(最大12小时)

—FAN IS ON或FAN OFF DELAY active:关闭风扇,然后与SYSTEM IDLE相同。—ANY STATE (MENU除外):当按下按钮>1秒时,系统完全关闭,直到按下ON按钮再次打开。

- MENU ACTIVE:当按下数值下降时,长按快速降低数值。

SELECT:

—当按下1秒时,进入“user MENU”界面。

(只能在IDLE/风扇关闭或风扇打开状态下使用)
—设置阈值:40%RH ~ 95%RH—迟滞量:3%RH ~ 9%RH—风扇关闭延时:0(无延时)~ 60分钟。

风扇继电器
继电器方面我所使用的是一个好牌子的5V继电器,详情可以见示意图。

我重新利用一个旧pcb与安全的情况下安装我需要的部分,并连接高电压没有风险。在风扇和230V输入之间使用5A熔断器

更多图片:

Bathroom Fan Controller v 1.11:

//  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.

如果您对此项目有任何想法、意见或问题,请在下方留言。

以上内容翻译自网络,原作者:Erik de Ruiter,如涉及侵权,可联系删除。

加入微信技术交流群

技术交流,职业进阶

关注与非网服务号

获取电子工程师福利

加入电路城 QQ 交流群

与技术大牛交朋友

讨论