BLYNK
HOME       📲 GETTING STARTED       📗 DOCS       ❓HELP CENTER       👉 SKETCH BUILDER

(AKA - Another Terrarium powered by Blynk) Peltier Heater/Cooler control via Sensirion SHT10, MCP23017 I/O expander, Wemos D1 Min & BLYNK

Hello fellow Blynkers!

This is my first post, but I’ve been lurking around this Forum for awhile now just reading about all the really cool projects others have made. I’m not new to the Arduino world, but I’m more of a hardware guy, The coding side of projects like these used to make my head hurt, but less so now that I’ve learned to use the BLYNK app.

Long story short… I’ve got a wife and two kids. The kids love reptiles. The wife not so much. Because of this, my wife banished the reptiles from the house to the garage. The garage is fine, but Humidity & Temperature levels vary a lot. It’s not a good place for reptiles. The reptiles ( Crested Geckos) needed stable conditions. That’s were I came to the rescue… armed with an Arduino Uno, a DHT22 sensor, some relays, and a couple 12V automotive Heater/Cooler assemblies ( Peltier devices sandwiched between two large aluminum heat sinks). That was 3 years ago, and the Geckos are fine, but since I discovered BLYNK, a standard Arduino controlled life support system just won’t do.

Enter BLYNK… Time to re-vamp the whole thing!

Equipment used-

(1) WEMOS D1 Mini
(1) MCP23017 I/O port expander IC
(1) 4 channel Relay board (active LOW)
(1) 60 watt automotive Heater/Cooler assembly
(1) 12V 8 amp desktop power supply
(2) 90mm PC cooling fans ( one mounted to each side of the heat sink for air movement)
(1) Sensirion SHT10 Humidity/Temperature sensor ( bought the Adafruit version. Pricey but it’s robust construction is worth it)

The Code…(with only 3 lines in the ‘loop’() )

 /* TERRARIUM HVAC CONTROL - Using the Sensirion SHT10 Humidity/Temp sensor.

  Humidity & Temperature levels are checked, and logic is used to determine
  if measured Humidity and/or Temperature parameters require adjustment.
  All control commands are sent via I2C to an MCP23017 I/O port expander to save precious I/O pins on the ESP8266.
  All relay control signals are held HIGH before being driven LOW to activate 4 "active LOW' relays.
  One Relay controls the Humidifier's A/C line directly.
  Two relays are used to toggle the direction of 12V current through a 60watt peltier device.
  The relays are wired in such a way that both the Peltiers positive & negative wires are on the ground side of the circuit when the relays are at rest.
  a Heating or Cooling command causes one of the two relays to switch ON, passing +12V through the Peltier device in one direction only.




  WORK IN PROGRESS
  need to ADD------
  auto save settings to EEPROM - DONE
  WiFiManager autoconnect AP and BLYNK TOKEN (needed for login to Blynk server)- DONE
  BLYNK WIDGETS...i.e
  RTC- Time(V4) Date(V2)- DONE
  Read Teperature/Humidity data using a Sensirion SHT10 sensor. - DONE
  Humidity/Temp Gauges & Settings Sliders- DONE
  Light (Spare relay) Overide switch. (Using Vitual button with BLYNK app)NOT DONE
  Add ESP8266 OTA code to sketch - DONE
  Temperature and Humidity Relay control functions via MCP23017 port expander.- DONE
  Spare channel relay control for external A/C light control- NOT DONE
  4 channel PWM LED lighting control logic (PCA9685 Based)- NOT DONE
*/
#include <FS.h>                   //this needs to be first, or it all crashes and burns...
#define BLYNK_PRINT Serial        // comment this out to save space
#include <SPI.h>
#include <BlynkSimpleEsp8266.h>
#include <SimpleTimer.h>
#include <Wire.h>
#include <Centipede.h>            // Library used to control the MCP23017 port expander chip.
#include <ArduinoOTA.h>

//included libraries for WiFiManager - AutoConnectWithFSParameters
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>          //https://github.com/tzapu/WiFiManager
#include <ArduinoJson.h>          //https://github.com/bblanchon/ArduinoJson
#include <EEPROM.h>

//added for RTC Widget (BLYNK app)
#include <TimeLib.h>
#include <WidgetRTC.h>
//added from Sensirion library for use with the SHT10 Humidity/Temp sensor
#include <Sensirion.h>


//LED status indicator widgets
WidgetLED led1(V8); //register to virtual pin V8 used for HUMIDIFIER ON/OFF indication
WidgetLED led2(V9); //register to virtual pin V9 used for HEATER ON/OFF indication
WidgetLED led3(V10); //register to virtual pin V10 used for COOLER ON/OFF indication
WidgetLED led4(V11); //register to virtual pin V11 used for SHT1X sensor malfunction indication
WidgetLED led5(V12); //register to virtual pin V12 used for TEMP IN RANGE indication
WidgetLED led6(V13); //register to virtual pin V13 used for HUMIDITY IN RANGE indication

// Sensirion sensor constants and variables.
const uint8_t dataPin =  D0;              // SHT serial data
const uint8_t sclkPin =  D3;              // SHT serial clock
const uint32_t TRHSTEP   = 5000UL;        // Sensor query period
uint16_t rawData;
byte measActive = false;
byte measType = TEMP;
unsigned long trhMillis = 0;              // Time interval tracking


Sensirion sht = Sensirion(dataPin, sclkPin);




// Humidity and Temperature variables
unsigned int desiredHum = 0; // variable used to store the setpoint value for the desired humidity, once the measured humidity is lower than this value - the humDiff, start humidification.
int desiredTemp = 0;  //variable used to store the setpoint value for the desired temperture, once the measured temperture is more/less than this value (+/- the tempDiff), heating or cooling will be activated.
int PreviousdesiredTemp = 0;// variable used to compare curent desired Temperature setting with the stored EEProm value.
int tempDiff = 3;          // the +/- temperature differential.
int humDiff = 5; //  a negative differential that's used to prevent constant humidifier relay cycling near the desired setpoint.
float temp = 0;// the temperature reading from the SHT10 sensor
float humidity = 0;// the humidity reading from the SHT10 sensor
int PreviousdesiredHum = 0;// variable used to compare curent desired Humidity setting with the stored EEProm value.

char blynk_token[34] = "YOUR_BLYNK_TOKEN";//added from WiFiManager - AutoConnectWithFSParameters

//flag for saving data
bool shouldSaveConfig = false;

//callback notifying the need to save config
void saveConfigCallback () {
Serial.println("Should save config");
shouldSaveConfig = true;
}
Centipede CS; // From the Centipede library, it's functions are used to interact with the MCP23017 I2C I/O expander chip.
SimpleTimer timer;// From the SimpleTimer Library. it creates a timer object.

// All Relay Outputs correspond to PORT A pins on a MCP23017 chip at I2C address = 0x20;.
// All 4 Relay controlled loads are switched ON when MCP23017 outputs go LOW.
// Humidifier_Relay = GPA7 // the pin number of the Humidifier relay.
// Heater Relay =     GPA6 // the pin number of the Heater relay.
// Cooler Relay =     GPA5 // the pin number of the Cooler relay.
// Spare Relay =      GPA4 // the pin number of the Spare relay.
//


// This function runs the SHT10 sensor and sends Humidity and Temperature values to gauge widgets in the BLYNK app.
// Humidity readings on (V5)
// Temperature readings on (V6)

void sendSensor()

{ // Read values from the SHT10 sensor
unsigned long curMillis = millis();          // Get current time in millis

// non-blocking calls to SHT10 sensor
if (curMillis - trhMillis >= TRHSTEP) {      // Time for new measurements?
  measActive = true;
  measType = TEMP;
  sht.meas(TEMP, &rawData, NONBLOCK);        // Start temp measurement
  trhMillis = curMillis;
}
if (measActive && sht.measRdy()) {           // Note: no error checking
  if (measType == TEMP) {                    // Process temp or humi?
    measType = HUMI;
    temp = sht.calcTemp(rawData);     // Convert raw sensor data
    sht.meas(HUMI, &rawData, NONBLOCK);      // Start humidity measurement
  } else {
    measActive = false;
    humidity = sht.calcHumi(rawData, temp); // Convert raw sensor data
    logData();
  }
}

// Don't send more that 10 values per second.
Blynk.virtualWrite(V5, humidity); // guage in BLYNK app showing "Actual Humidity" as reported by the DHT22 sensor.
Blynk.virtualWrite(V6, temp); // guage in BLYNK app showing "Actual Temp" as reported by the DHT22 sensor.
}
void logData() {
Serial.print("Temperature = ");
Serial.print(temp);
Serial.println(" F ");
Serial.print("Humidity = ");
Serial.print(humidity);
Serial.println(" % ");

}
///// Humidity & Temperature Slider & Gauge Widget functions. Used to set and provide eye appealing desired Humidity & Temperature gauge readout.

BLYNK_WRITE(V3) {// slider widget to set the desired Temp in BLYNK app.
desiredTemp = param.asInt();
Blynk.virtualWrite(V1, desiredTemp);// Gauge widget showing desired Temp In BLYNK app.

}
BLYNK_WRITE(V7) { // slider widget to set the desired Humidity in BLYNK app.
desiredHum = param.asInt();
Blynk.virtualWrite(V0, desiredHum);// Gauge widget showing desired Humidity In BLYNK app.


}

//   Humidifier control functions-
void determineHumidifierOnOff()
{
/// logic used to determine whether or not relative humidity should be raised.
if (humidity < desiredHum - humDiff )// This function measures the humidity level and subtracts the differential (humDiff) and switches the humidifier on if the prpoer conditions are met.  humDiff = 5 points under desired Humidity level.
{ // HUMIDIFIER ON
  CS.digitalWrite(7, LOW);//Humidifier Relay ON. Pin# assigment corresponds to MCP23017 I/O PORT A register #'s
  Serial.println("HUMIDIFIER ON");
  led1.on();//HUMIDIFIER INDICATOR ON
  led4.off();//MALFUNCTION INDICATOR OFF
  led6.off();//DESIRED RANGE INDICATION OFF
}

if (humidity <= 0)// this function stops the humidifier if a non valid reading is received from the SHT1X sensor.
{ // HUMIDIFIER OFF
  CS.digitalWrite(7, HIGH);//Humidifier Relay OFF. Pin# assigment corresponds to MCP23017 I/O PORT A register #'s
  Serial.println("SHT10 Sensor Malfunction- HUMIDIFIER operation terminated");
  led1.off();////HUMIDIFIER INDICATOR OFF
  led4.on();//MALFUNCTION INDICATOR ON
  led6.off();//DESIRED RANGE INDICATION OFF

}

if (humidity >= desiredHum)// this function stops the humidifier if the measured humidity is in the desired range.
{ // HUMIDIFIER OFF
  CS.digitalWrite(7, HIGH);//Hunidifier Relay OFF. Pin# assigment corresponds to MCP23017 I/O PORT A register #'s
  Serial.println("HUMIDIFIER OFF");
  led1.off();//HUMIDIFIER INDICATOR OFF
  led4.off();//MALFUNCTION INDICATOR OFF
  led6.on();//DESIRED RANGE INDICATION ON
}
if (humidity >= desiredHum - humDiff)
{
  led6.on();//DESIRED RANGE INDICATION ON
}
}
/*
 Temperature Control function used to switch Heating/Cooling on/off by toggling the direction of current
 through a 60 watt Peltier device. The fans used to provide heating or cooling are activated through a separate circuit, anytime current is flowing through the Peltier device.
*/
void determineHeatorCool()
{

/// logic used to determine whether or not heating or cooling is needed.

if (temp <= 0)
{ // HEATER/COOLER OFF This function stops Heating/Cooling if a non valid value is received from the SHT10 sensor.
  CS.digitalWrite(6, HIGH);//Heater Relay OFF. Pin# assigment corresponds to MCP23017 I/O PORT A register #'s
  CS.digitalWrite(5, HIGH);//Cooler Relay OFF
  CS.digitalWrite(4, HIGH);//Spare Relay OFF
  Serial.println("SHT10 Sensor Malfunction- HEATING/COOLING operation terminated");
  led2.off();//HEATING INDICATOR OFF
  led3.off();//COOLING INDICATOR OFF
  led4.on();//MALFUNCTION INDICATOR ON
  led5.off();//TEMP IN RANGE OFF
}

if (temp < desiredTemp - tempDiff)
{ // HEATER ON / COOLER OFF
  CS.digitalWrite(6, LOW);//Heater Relay ON. Pin# assigment corresponds to MCP23017 I/O PORT A register #'s
  CS.digitalWrite(5, HIGH);//Cooler Relay OFF
  CS.digitalWrite(4, LOW);//Spare Relay ON
  Serial.println("HEATING");
  led2.on();//HEATING INDICATOR ON
  led3.off();//COOLING INDICATOR OFF
  led4.off();//MALFUNCTION INDICATOR OFF
  led5.off();//TEMP IN RANGE OFF
}

if (temp > desiredTemp + tempDiff )
{ // HEATER OFF/ COOLER ON
  CS.digitalWrite(6, HIGH);//Heater Relay OFF. Pin# assigment corresponds to MCP23017 I/O PORT A register #'s
  CS.digitalWrite(5, LOW);//Cooler Relay  ON
  CS.digitalWrite(4, LOW);//Spare Relay ON
  Serial.println("COOLING");
  led2.off();//HEATING INDICATOR OFF
  led3.on();//COOLING INDICATOR ON
  led4.off();//MALFUNCTION INDICATOR OFF
  led5.off();//TEMP IN RANGE OFF
}
if (temp > desiredTemp - tempDiff  && temp < desiredTemp + tempDiff)
{ // HEATER OFF/ COOLER OFF.... TEMP IN DESIRED RANGE.
  CS.digitalWrite(6, HIGH);//Heater Relay OFF. Pin# assigment corresponds to MCP23017 I/O PORT A register #'s
  CS.digitalWrite(5, HIGH);//Cooler Relay OFF
  CS.digitalWrite(4, HIGH);//Spare Relay OFF
  Serial.println("TEMP = DESIRED RANGE");
  led2.off();//HEATING INDICATOR OFF
  led3.off();//COOLING INDICATOR OFF
  led4.off();//MALFUNCTION INDICATOR OFF
  led5.on();//TEMP IN RANGE ON
}
}
void spare()// Spare relay (GPAI/O4) saved for future use.
{
// CS.digitalWrite(4, HIGH);//Spare Relay OFF
}

WidgetRTC rtc;// REAL TIME CLOCK added to BLYNK app dashboard

// Digital clock display of the time
void clockDisplay()
{
// You can call hour(), minute(), ... at any time
// Please see Time library examples for details

String currentTime = String(hour()) + ":" + minute() + ":" + second();
String currentDate = String(month()) + " " + day() + " " + year();
Serial.print("Current time: ");
Serial.print(currentTime);
Serial.print(" ");
Serial.print(currentDate);
Serial.println();

// Send time to the App
Blynk.virtualWrite(V4, currentTime);
// Send date to the App
Blynk.virtualWrite(V2, currentDate);


}
void GetPresets() {// Pre-set Humidity/Temperature values are stored in ESP8266 memory.

if (desiredTemp != PreviousdesiredTemp) {  //update the EEPROM if desired temperature has changed.
  EEPROM.write(1, desiredTemp);
  EEPROM.commit();
  Serial.print(F("New desired temperature saved: "));
  Serial.println(desiredTemp);
  PreviousdesiredTemp = desiredTemp;
}
desiredTemp = EEPROM.read(1);
if ((desiredTemp < 50) || (desiredTemp > 100)) {
  desiredTemp = 75;
  Serial.println(F("No saved temperature setting."));
}
if (desiredHum != PreviousdesiredHum) {  //update the EEPROM if desired temperature has changed.
  EEPROM.write(2, desiredHum);
  EEPROM.commit();
  Serial.print(F("New desired humidity level saved: "));
  Serial.println(desiredHum);
  PreviousdesiredHum = desiredHum;
}
desiredHum = EEPROM.read(2);
if ((desiredHum < 0) || (desiredHum > 100)) {
  desiredHum = 50;
  Serial.println(F("No saved humidity setting."));
}
}

void setup()
{
// put your setup code here, to run once:
Serial.begin(115200);// Start serial communication....
Serial.println();
Wire.begin(); // start the I2C communication protocol

CS.initialize(); // initalize the MCP23017 I/O expander chip.
CS.portMode(0, 0b0000000000000000); // set all 16 pins on the MCP23017 port expander chip to OUTPUT (0 to 15)
CS.pinMode(7, OUTPUT); //SET PIN TO OUTPUT. Pin# assigment corresponds to MCP23017 I/O PORT A register #'s
CS.pinMode(6, OUTPUT); //SET PIN TO OUTPUT
CS.pinMode(5, OUTPUT); //SET PIN TO OUTPUT
CS.pinMode(4, OUTPUT); //SET PIN TO OUTPUT

CS.digitalWrite(7, HIGH); //(#1) HUMIDIFIER RELAY set to OFF when HIGH. Pin# assigment corresponds to MCP23017 I/O PORT A register #'s
CS.digitalWrite(6, HIGH); //(#2) HEATER RELAY     set to OFF when HIGH
CS.digitalWrite(5, HIGH); //(#3) COOLER RELAY     set to OFF when HIGH
CS.digitalWrite(4, HIGH); //(#4) SPARE RELAY      set to OFF when HIGH


//Load any saved settings from the EEPROM
EEPROM.begin(20);
Serial.println(F("STARTUP : LOADING SETTINGS FROM MEMORY"));
Serial.println(F(""));
delay(1000);


// Simple Timer functions-
timer.setInterval(5000L, GetPresets);
timer.setInterval(1000L, sendSensor);
timer.setInterval(5000L, determineHeatorCool);
timer.setInterval(5000L, determineHumidifierOnOff );
timer.setInterval(5000L, clockDisplay);
timer.setInterval(10000L, spare);
ArduinoOTA.begin();


//The following code is borrowed from WiFiManager
//clean FS, for testing
//SPIFFS.format();

//read configuration from FS json
Serial.println("mounting FS...");

if (SPIFFS.begin()) {
  Serial.println("mounted file system");
  if (SPIFFS.exists("/config.json")) {
    //file exists, reading and loading
    Serial.println("reading config file");
    File configFile = SPIFFS.open("/config.json", "r");
    if (configFile) {
      Serial.println("opened config file");
      size_t size = configFile.size();
      // Allocate a buffer to store contents of the file.
      std::unique_ptr<char[]> buf(new char[size]);

      configFile.readBytes(buf.get(), size);
      DynamicJsonBuffer jsonBuffer;
      JsonObject& json = jsonBuffer.parseObject(buf.get());
      json.printTo(Serial);
      if (json.success()) {
        Serial.println("\nparsed json");
        strcpy(blynk_token, json["blynk_token"]);

      } else {
        Serial.println("failed to load json config");
      }
    }
  }
} else {
  Serial.println("failed to mount FS");
}
//end read
// The extra parameters to be configured (can be either global or just in the setup)
// After connecting, parameter.getValue() will get you the configured value
// id/name placeholder/prompt default length
WiFiManagerParameter custom_blynk_token("blynk", "blynk token", blynk_token, 34);
Serial.println(blynk_token);
//WiFiManager
//Local intialization. Once its business is done, there is no need to keep it around
WiFiManager wifiManager;

//set config save notify callback
wifiManager.setSaveConfigCallback(saveConfigCallback);

//add all your parameters here
wifiManager.addParameter(&custom_blynk_token);

//reset settings - for testing
//wifiManager.resetSettings();

//set minimu quality of signal so it ignores AP's under that quality
//defaults to 8%
wifiManager.setMinimumSignalQuality();

//sets timeout until configuration portal gets turned off
//useful to make it all retry or go to sleep
//in seconds
wifiManager.setTimeout(180);

//fetches ssid and pass and tries to connect
//if it does not connect it starts an access point with the specified name
//here  "TERRA-HVAC-AP"
//and goes into a blocking loop awaiting configuration
if (!wifiManager.autoConnect("TERRA-HVAC AP")) {
  Serial.println("failed to connect and hit timeout");
  delay(3000);
  //reset and try again, or maybe put it to deep sleep
  ESP.reset();
  delay(5000);
}

//if you get here you have connected to the WiFi
Serial.println("TERRA-HVAC connected :)");

//read updated parameters
strcpy(blynk_token, custom_blynk_token.getValue());

//save the custom parameters to FS
if (shouldSaveConfig) {
  Serial.println("saving config");
  DynamicJsonBuffer jsonBuffer;
  JsonObject& json = jsonBuffer.createObject();
  json["blynk_token"] = blynk_token;

  File configFile = SPIFFS.open("/config.json", "w");
  if (!configFile) {
    Serial.println("failed to open config file for writing");
  }

  json.printTo(Serial);
  json.printTo(configFile);
  configFile.close();
  //end save
}

Serial.println("local ip");
Serial.println(WiFi.localIP());
Blynk.connect();
Blynk.config(blynk_token);
rtc.begin();
Blynk.syncAll();
delay(1000);

if (!blynk_token)
{
  Serial.println("Failed to connect to Blynk server");
  wifiManager.resetSettings(); //ESP.reset();
  delay(1000);
}
}

void loop()
{
Blynk.run();
timer.run(); // Initiates SimpleTimer
ArduinoOTA.handle();
}

Here’s a shot of the tabs of my BLYNK project dash board. The second tab contains an Lcd display and four buttons for an led lighting PWM controller. I’m still working on the code for that, and will roll it into the main code when it’s completed. I’m hoping that someone else will find this project useful?

7 Likes

Good project. Thanks for share :clap:
Why do you use MCP23017 instead of WeMos D1 mini pins. It can handle 4 Output pins

The reason for the MCP23017? This was my first project that used an ESP8266 and BLYNK. From my limited understanding of how the ESP8266 functions, it seems that many of the available I/O pins are needed for dedicated purposes during the boot up, and if connected to outside circuits during that time… problems occur. Rather than be forced to learn about the inner workings of the ESP8266, I chose a proven, albeit a more complicated route using I2C communication with dedicated port expander chip. The MCP23017 gave me an additional 16 GPI/O pins to play with. Communication between the ESP and any other I2C enabled IC only requires 2 pins on the ESP8266 (SDA,SCL) . The I2C protocol is uses a bus system with different “device addresses” for communication between different IC chips. Meaning… with enough “addresses”, one ESP can control and process data from dozens of different IC’s using only the same 2pins!
In the future I’ll add a PCA9685 PWM controller IC to my project, and design/build a custom pcb to condense the entire project/ eliminate much of the wiring. Stay tuned…

2 Likes

According to me the more hardware the more problem source. If i can do the same job with less hardware, why do i use more. If you are developing an expandable system than you may use such expansion ic’s. Anyway it is a qood work.
:clap: for your work.

Hi @William,
Nice project! Please, add some photos!! :slight_smile:

Let me suggest several changes at your set up in order to avoid “extra” widgets and “clean” a little bit:

1:Use setProperty feature to change the label at the Widget

Blynk.setProperty(V5, "label", desiredTemp);

2: Change the color at the wigdet,for example:

-the Temp is OK, the Gauge could be “Green”
-the Temp is less than your setpoint, the Gauge could be “Blue”
-the Temp is more than your setup, the Gauge could be “Red”

To be able to do these changes, you can use


#define BLYNK_GREEN     "#23C48E"
#define BLYNK_BLUE      "#04C0F8"
#define BLYNK_RED       "#D3435C"


Blynk.setProperty(V5, "color", "#23C48E"); // Green
Blynk.setProperty(V5, "color", "#04C0F8"); // Blue
Blynk.setProperty(V5, "color", "#D3435C"); // Red

More details here:
http://docs.blynk.cc/#blynk-main-operations-change-widget-properties

This way, your setup could be like this, all the info in “one page”:

Kind regards!!

1 Like

Thanks for the compliments and input guys! I’ll give those changes a try. I spent some time recently to create a custom pcb for this project. I added on board support for DHT & DS18B20 sensors, in addition to the Sensirion SHT10 that I’ll be using. The board also contains 2 constant current led drivers of my own design. I’ll order the pcb’s soon, and will post pictures of the project when it’s completed.

4 Likes

@William nice work :thumbsup:

Thanks for the compliments! I received the PcB’s today! Only had to wait 6 days for production and delivery! You gotta love the internet! I immediately got to work and soldered up a couple for testing. Here’s a photo of the completed board. I’ll post more pictures as this project progresses…

3 Likes

Hello William
I am very much in love in your project. I was looking for exactly this to use it both on my boy’s terrarium where his tortus can get better quality of life, and on my humidor where my cigars get preset stable temperature and humidity levels.
Unlike you I’m neither a programmer nor a hardware guy , I am a tinkerer I have some knowledge of electronics and can build and solder wires and make my primitive boards .
I tried to use ure code to work with adafruit feather huzzah with no luck
I heading out to get a wemos d1 maybe I will have some luck.
if I copy paste the code to Arduino IDE should it work or I am dreaming and there is more to it
I need help.
is your board available for purchase ?
thank you
Harry

Harry-

I used the Arduino IDE to write and upload the code to my Wemos D1 Mini. Did you locate and download all of the non BLYNK provided libraries? There’s a few different versions of the Sensirion library floating around GitHub, so that may be part of the problem you’re having. I’ve added comments to my code with links to the libraries that I used. This version of the code is nearly identical to the original, but has a few pin assignments changed to make it comply with the Pcb that I created. I’ve now completed functional testing of the Pcb… it works perfectly. The only thing that still needs to be done is to add the led lighting control portion of the code to the main program. I’m still working on that. I’ll make the PcB design files/ parts BOM available to whoever wants them, just chime in if interested. I might also make a few fully assembled boards available too. I’m not a business, just a hardcore DiY’er, so whatever the boards cost me that’s what I’d pass them on for.

/* CUSTOM PcB version-
*

  • TERRARIUM HVAC CONTROL - Using the Sensirion SHT10 Humidity/Temp sensor.

Humidity & Temperature levels are checked, and logic is used to determine
if measured Humidity and/or Temperature parameters require adjustment.
All control commands are sent via I2C to an MCP23017 I/O port expander to save precious I/O pins on the ESP8266.
All relay control signals are held HIGH before being driven LOW to activate 4 "active LOW’ relays.
One Relay controls the Humidifier’s A/C line directly.
Two relays are used to toggle the direction of 12V current through a 60watt peltier device.
The relays are wired in such a way that both the Peltiers positive & negative wires are on the ground side of the circuit when the relays are at rest.
a Heating or Cooling command causes one of the two relays to switch ON, passing +12V through the Peltier device in one direction only.

WORK IN PROGRESS
need to ADD------
auto save settings to EEPROM - DONE
WiFiManager autoconnect AP and BLYNK TOKEN (needed for login to Blynk server)- DONE
BLYNK WIDGETS…i.e
RTC- Time(V4) Date(V2)- DONE
Read Teperature/Humidity data using a Sensirion SHT10 sensor. - DONE
Humidity/Temp Gauges & Settings Sliders- DONE
Light (Spare relay) Overide switch. (Using Vitual button with BLYNK app)NOT DONE
Add ESP8266 OTA code to sketch - DONE
Temperature and Humidity Relay control functions via MCP23017 port expander.- DONE
Spare channel relay control for external A/C light control- NOT DONE
4 channel PWM LED lighting control logic (PCA9685 Based)- NOT DONE
*/
#include <FS.h> //this needs to be first, or it all crashes and burns…
#define BLYNK_PRINT Serial // comment this out to save space
#include <SPI.h>
#include <BlynkSimpleEsp8266.h>
#include <SimpleTimer.h>
#include <Wire.h>
#include <Centipede.h> // Library used to control the MCP23017 port expander chip. download available here - http://macetech.com/Centipede.zip
#include <ArduinoOTA.h>

//included libraries for WiFiManager - AutoConnectWithFSParameters
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h> //https://github.com/tzapu/WiFiManager
#include <ArduinoJson.h> //https://github.com/bblanchon/ArduinoJson
#include <EEPROM.h>

//added for RTC Widget (BLYNK app)
#include <TimeLib.h>
#include <WidgetRTC.h>
//added from Sensirion library for use with the SHT10 Humidity/Temp sensor- https://github.com/spease/Sensirion
#include <Sensirion.h>

//LED status indicator widgets
WidgetLED led1(V8); //register to virtual pin V8 used for HUMIDIFIER ON/OFF indication
WidgetLED led2(V9); //register to virtual pin V9 used for HEATER ON/OFF indication
WidgetLED led3(V10); //register to virtual pin V10 used for COOLER ON/OFF indication
WidgetLED led4(V11); //register to virtual pin V11 used for SHT1X sensor malfunction indication
WidgetLED led5(V12); //register to virtual pin V12 used for TEMP IN RANGE indication
WidgetLED led6(V13); //register to virtual pin V13 used for HUMIDITY IN RANGE indication

// Sensirion sensor constants and variables.
const uint8_t dataPin = D0; // SHT serial data on pin D0 of the WEMOS D1 Mini.
const uint8_t sclkPin = D3; // SHT serial clock on pin D3 of the WEMOS D1 Mini.
const uint32_t TRHSTEP = 5000UL; // Sensor query period
uint16_t rawData;
byte measActive = false;
byte measType = TEMP;
unsigned long trhMillis = 0; // Time interval tracking

Sensirion sht = Sensirion(dataPin, sclkPin);

// Humidity and Temperature variables
unsigned int desiredHum = 0; // variable used to store the setpoint value for the desired humidity, once the measured humidity is lower than this value - the humDiff, start humidification.
int desiredTemp = 0; //variable used to store the setpoint value for the desired temperture, once the measured temperture is more/less than this value (+/- the tempDiff), heating or cooling will be activated.
int PreviousdesiredTemp = 0;// variable used to compare curent desired Temperature setting with the stored EEProm value.
int tempDiff = 3; // the +/- temperature differential.
int humDiff = 5; // a negative differential that’s used to prevent constant humidifier relay cycling near the desired setpoint.
float temp = 0;// the temperature reading from the SHT10 sensor
float humidity = 0;// the humidity reading from the SHT10 sensor
int PreviousdesiredHum = 0;// variable used to compare curent desired Humidity setting with the stored EEProm value.

char blynk_token[34] = “YOUR_BLYNK_TOKEN”;//added from WiFiManager - AutoConnectWithFSParameters

//flag for saving data
bool shouldSaveConfig = false;

//callback notifying the need to save config
void saveConfigCallback () {
Serial.println(“Should save config”);
shouldSaveConfig = true;
}
Centipede CS; // From the Centipede library, it’s functions are used to interact with the MCP23017 I2C I/O expander chip.
SimpleTimer timer;// From the SimpleTimer Library. it creates a timer object.

// All Relay Outputs correspond to PORT A pins on a MCP23017 chip at I2C address = 0x20;.
// All 4 Relay controlled loads are switched ON when MCP23017 outputs go LOW.
// Humidifier_Relay = GPA3 // the pin number of the Humidifier relay. Centipede control pin #3
// Heater Relay = GPA2 // the pin number of the Heater relay. Centipede control pin #2
// Cooler Relay = GPA1 // the pin number of the Cooler relay. Centipede control pin #1
// Spare Relay = GPA0 // the pin number of the Spare relay. Centipede control pin #0

// Additional control using PORT B pins on the MCP23017 chip.
// FAN CONTROL Signal = GPB0 // Centipede control pin #8

// This function runs the SHT10 sensor and sends Humidity and Temperature values to gauge widgets in the BLYNK app.
// Humidity readings on (V5)
// Temperature readings on (V6)

void sendSensor()

{ // Read values from the SHT10 sensor
unsigned long curMillis = millis(); // Get current time in millis

// non-blocking calls to SHT10 sensor
if (curMillis - trhMillis >= TRHSTEP) { // Time for new measurements?
measActive = true;
measType = TEMP;
sht.meas(TEMP, &rawData, NONBLOCK); // Start temp measurement
trhMillis = curMillis;
}
if (measActive && sht.measRdy()) { // Note: no error checking
if (measType == TEMP) { // Process temp or humi?
measType = HUMI;
temp = sht.calcTemp(rawData); // Convert raw sensor data
sht.meas(HUMI, &rawData, NONBLOCK); // Start humidity measurement
} else {
measActive = false;
humidity = sht.calcHumi(rawData, temp); // Convert raw sensor data
logData();
}
}

// Don’t send more that 10 values per second.
Blynk.virtualWrite(V5, humidity); // guage in BLYNK app showing “Actual Humidity” as reported by the SH10 sensor.
Blynk.virtualWrite(V6, temp); // guage in BLYNK app showing “Actual Temp” as reported by the SHR10 sensor.

}
void logData() {
Serial.print("Temperature = “);
Serial.print(temp);
Serial.println(” F ");
Serial.print("Humidity = “);
Serial.print(humidity);
Serial.println(” % ");

}
///// Humidity & Temperature Slider & Gauge Widget functions. Used to set and provide eye appealing desired Humidity & Temperature gauge readout.

BLYNK_WRITE(V3) {// slider widget to set the desired Temp in BLYNK app.
desiredTemp = param.asInt();
Blynk.virtualWrite(V1, desiredTemp);// Gauge widget showing desired Temp In BLYNK app.

}
BLYNK_WRITE(V7) { // slider widget to set the desired Humidity in BLYNK app.
desiredHum = param.asInt();
Blynk.virtualWrite(V0, desiredHum);// Gauge widget showing desired Humidity In BLYNK app.

}

//Humidifier control functions- Switches Relay#1 ON/OFF to control A/C powered Humidifier.
void determineHumidifierOnOff()
{
/// logic used to determine whether or not relative humidity should be raised.
if (humidity < desiredHum - humDiff )// This function measures the humidity level and subtracts the differential (humDiff) and switches the humidifier on if the prpoer conditions are met. humDiff = 5 points under desired Humidity level.
{ // HUMIDIFIER ON
CS.digitalWrite(3, LOW);//Humidifier Relay ON. Pin# assigment corresponds to MCP23017 I/O PORT A register #'s
Serial.println(“HUMIDIFIER ON”);
led1.on();//HUMIDIFIER INDICATOR ON
led4.off();//MALFUNCTION INDICATOR OFF
led6.off();//DESIRED RANGE INDICATION OFF

}

if (humidity <= 0)// this function stops the humidifier if a non valid reading is received from the SHT1X sensor.
{ // HUMIDIFIER OFF
CS.digitalWrite(3, HIGH);//Humidifier Relay OFF. Pin# assigment corresponds to MCP23017 I/O PORT A register #'s
Serial.println(“SHT10 Sensor Malfunction- HUMIDIFIER operation terminated”);
led1.off();////HUMIDIFIER INDICATOR OFF
led4.on();//MALFUNCTION INDICATOR ON
led6.off();//DESIRED RANGE INDICATION OFF

}

if (humidity >= desiredHum)// this function stops the humidifier if the measured humidity is in the desired range.
{ // HUMIDIFIER OFF
CS.digitalWrite(3, HIGH);//Hunidifier Relay OFF. Pin# assigment corresponds to MCP23017 I/O PORT A register #'s

Serial.println(“HUMIDIFIER OFF”);
led1.off();//HUMIDIFIER INDICATOR OFF
led4.off();//MALFUNCTION INDICATOR OFF
led6.on();//DESIRED RANGE INDICATION ON

}
if (humidity >= desiredHum - humDiff)
{
led6.on();//DESIRED RANGE INDICATION ON

}
}

// Temperature Control function used to switch Heating/Cooling on/off by toggling the direction of current
// through a 60 watt Peltier device. The fans used to provide heating or cooling airflow are activated through a separate
// circuit, anytime current is flowing through the Peltier device.

void determineHeatorCool()
{

/// logic used to determine whether or not heating or cooling is needed.

if (temp <= 0)
{ // HEATER/COOLER OFF This function stops Heating/Cooling if a non valid value is received from the SHT10 sensor.
CS.digitalWrite(2, HIGH);//Heater Relay OFF. Pin# assigment corresponds to MCP23017 I/O PORT A&B register #'s
CS.digitalWrite(1, HIGH);//Cooler Relay OFF
CS.digitalWrite(8, HIGH);//FAN CONTROLLER OFF
Serial.println(“SHT10 Sensor Malfunction- HEATING/COOLING operation terminated”);
led2.off();//HEATING INDICATOR OFF
led3.off();//COOLING INDICATOR OFF
led4.on();//MALFUNCTION INDICATOR ON
led5.off();//TEMP IN RANGE OFF

}

if (temp < desiredTemp - tempDiff)
{ // HEATER ON / COOLER OFF
CS.digitalWrite(2, LOW);//Heater Relay ON. Pin# assigment corresponds to MCP23017 I/O PORT A&B register #'s
CS.digitalWrite(1, HIGH);//Cooler Relay OFF
CS.digitalWrite(8, LOW);//FAN CONTROLLLER ON
Serial.println(“HEATING”);
led2.on();//HEATING INDICATOR ON
led3.off();//COOLING INDICATOR OFF
led4.off();//MALFUNCTION INDICATOR OFF
led5.off();//TEMP IN RANGE OFF

}

if (temp > desiredTemp + tempDiff )
{ // HEATER OFF/ COOLER ON
CS.digitalWrite(2, HIGH);//Heater Relay OFF. Pin# assigment corresponds to MCP23017 I/O PORT A register #'s
CS.digitalWrite(1, LOW);//Cooler Relay ON
CS.digitalWrite(8, LOW);//FAN CONTROLLER ON
Serial.println(“COOLING”);
led2.off();//HEATING INDICATOR OFF
led3.on();//COOLING INDICATOR ON
led4.off();//MALFUNCTION INDICATOR OFF
led5.off();//TEMP IN RANGE OFF

}
if (temp > desiredTemp - tempDiff && temp < desiredTemp + tempDiff)
{ // HEATER OFF/ COOLER OFF… TEMP IN DESIRED RANGE.
CS.digitalWrite(2, HIGH);//Heater Relay OFF. Pin# assigment corresponds to MCP23017 I/O PORT A&B register #'s
CS.digitalWrite(1, HIGH);//Cooler Relay OFF
CS.digitalWrite(8, HIGH);//FAN CONTROLLER OFF
Serial.println(“TEMP = DESIRED RANGE”);
led2.off();//HEATING INDICATOR OFF
led3.off();//COOLING INDICATOR OFF
led4.off();//MALFUNCTION INDICATOR OFF
led5.on();//TEMP IN RANGE ON

}
}
void spare()// Spare relay (GPA I/Opin 0) saved for future use.
{
// CS.digitalWrite(0, HIGH);//Spare Relay OFF
}

WidgetRTC rtc;// REAL TIME CLOCK added to BLYNK app dashboard

// Digital clock display of the time
void clockDisplay()
{
// You can call hour(), minute(), … at any time
// Please see Time library examples for details

String currentTime = String(hour()) + “:” + minute() + “:” + second();
String currentDate = String(month()) + " " + day() + " " + year();
Serial.print("Current time: “);
Serial.print(currentTime);
Serial.print(” ");
Serial.print(currentDate);
Serial.println();

// Send time to the App
Blynk.virtualWrite(V4, currentTime);
// Send date to the App
Blynk.virtualWrite(V2, currentDate);

}
void GetPresets() {// Pre-set Humidity/Temperature values are stored in ESP8266 memory.

if (desiredTemp != PreviousdesiredTemp) { //update the EEPROM if desired temperature has changed.
EEPROM.write(1, desiredTemp);
EEPROM.commit();
Serial.print(F("New desired temperature saved: "));
Serial.println(desiredTemp);
PreviousdesiredTemp = desiredTemp;
}
desiredTemp = EEPROM.read(1);
if ((desiredTemp < 50) || (desiredTemp > 100)) {
desiredTemp = 75;
Serial.println(F(“No saved temperature setting.”));
}
if (desiredHum != PreviousdesiredHum) { //update the EEPROM if desired temperature has changed.
EEPROM.write(2, desiredHum);
EEPROM.commit();
Serial.print(F("New desired humidity level saved: "));
Serial.println(desiredHum);
PreviousdesiredHum = desiredHum;
}
desiredHum = EEPROM.read(2);
if ((desiredHum < 0) || (desiredHum > 100)) {
desiredHum = 50;
Serial.println(F(“No saved humidity setting.”));
}
}

void setup()
{
// put your setup code here, to run once:
ArduinoOTA.begin();
Serial.begin(115200);// Start serial communication…
Serial.println();
Wire.begin(); // start the I2C communication protocol

CS.initialize(); // initalize the MCP23017 I/O expander chip.
CS.portMode(0, 0b0000000000000000); // set all 16 pins on the MCP23017 port expander chip to OUTPUT (0 to 15)
CS.pinMode(3, OUTPUT); //SET PIN TO OUTPUT. Pin# assigment corresponds to MCP23017 I/O PORT A&B register #'s
CS.pinMode(2, OUTPUT); //SET PIN TO OUTPUT
CS.pinMode(1, OUTPUT); //SET PIN TO OUTPUT
CS.pinMode(0, OUTPUT); //SET PIN TO OUTPUT
CS.pinMode(8, OUTPUT); //SET PIN TO OUTPUT

CS.digitalWrite(3, HIGH); //(#1) HUMIDIFIER RELAY set to OFF when HIGH. Pin# assigment corresponds to MCP23017 I/O PORT A&B register #'s
CS.digitalWrite(2, HIGH); //(#2) HEATER RELAY set to OFF when HIGH
CS.digitalWrite(1, HIGH); //(#3) COOLER RELAY set to OFF when HIGH
CS.digitalWrite(0, HIGH); //(#4) SPARE RELAY set to OFF when HIGH
CS.digitalWrite(8, HIGH); // FAN CONTROL set to OFF when HIGH (used with custom PcB only)

//Load any saved settings from the EEPROM
EEPROM.begin(20);
Serial.println(F(“STARTUP : LOADING SETTINGS FROM MEMORY”));
Serial.println(F(""));
delay(1000);

// Simple Timer functions-
timer.setInterval(5000L, GetPresets);
timer.setInterval(5000L, sendSensor);// set sensor read interval for 5 seconds to eliminate self heating of the sensor.
timer.setInterval(5000L, determineHeatorCool);
timer.setInterval(5000L, determineHumidifierOnOff );
timer.setInterval(1000L, clockDisplay);
timer.setInterval(10000L, spare);

//The following code is borrowed from WiFiManager
//clean FS, for testing
//SPIFFS.format();

//read configuration from FS json
Serial.println(“mounting FS…”);

if (SPIFFS.begin()) {
Serial.println(“mounted file system”);
if (SPIFFS.exists("/config.json")) {
//file exists, reading and loading
Serial.println(“reading config file”);
File configFile = SPIFFS.open("/config.json", “r”);
if (configFile) {
Serial.println(“opened config file”);
size_t size = configFile.size();
// Allocate a buffer to store contents of the file.
std::unique_ptr<char[]> buf(new char[size]);

  configFile.readBytes(buf.get(), size);
  DynamicJsonBuffer jsonBuffer;
  JsonObject& json = jsonBuffer.parseObject(buf.get());
  json.printTo(Serial);
  if (json.success()) {
    Serial.println("\nparsed json");
    strcpy(blynk_token, json["blynk_token"]);

  } else {
    Serial.println("failed to load json config");
  }
}

}
} else {
Serial.println(“failed to mount FS”);
}
//end read
// The extra parameters to be configured (can be either global or just in the setup)
// After connecting, parameter.getValue() will get you the configured value
// id/name placeholder/prompt default length
WiFiManagerParameter custom_blynk_token(“blynk”, “blynk token”, blynk_token, 34);
Serial.println(blynk_token);
//WiFiManager
//Local intialization. Once its business is done, there is no need to keep it around
WiFiManager wifiManager;

//set config save notify callback
wifiManager.setSaveConfigCallback(saveConfigCallback);

//add all your parameters here
wifiManager.addParameter(&custom_blynk_token);

//reset settings - for testing
//wifiManager.resetSettings();

//set minimu quality of signal so it ignores AP’s under that quality
//defaults to 8%
wifiManager.setMinimumSignalQuality();

//sets timeout until configuration portal gets turned off
//useful to make it all retry or go to sleep
//in seconds
wifiManager.setTimeout(180);

//fetches ssid and pass and tries to connect
//if it does not connect it starts an access point with the specified name
//here “TERRA-HVAC-AP”
//and goes into a blocking loop awaiting configuration
if (!wifiManager.autoConnect(“TERRA-HVAC AP”)) {
Serial.println(“failed to connect and hit timeout”);
delay(3000);
//reset and try again, or maybe put it to deep sleep
ESP.reset();
delay(5000);
}

//if you get here you have connected to the WiFi
Serial.println(“TERRA-HVAC connected :)”);

//read updated parameters
strcpy(blynk_token, custom_blynk_token.getValue());

//save the custom parameters to FS
if (shouldSaveConfig) {
Serial.println(“saving config”);
DynamicJsonBuffer jsonBuffer;
JsonObject& json = jsonBuffer.createObject();
json[“blynk_token”] = blynk_token;

File configFile = SPIFFS.open("/config.json", “w”);
if (!configFile) {
Serial.println(“failed to open config file for writing”);
}

json.printTo(Serial);
json.printTo(configFile);
configFile.close();
//end save
}

Serial.println(“local ip”);
Serial.println(WiFi.localIP());
Blynk.connect();
Blynk.config(blynk_token);
rtc.begin();
delay(1000);
Blynk.syncAll();

if (!blynk_token)
{
Serial.println(“Failed to connect to Blynk server”);
wifiManager.resetSettings(); //ESP.reset();
delay(1000);
}
}

void loop()
{
ArduinoOTA.handle();
Blynk.run();
timer.run(); // Initiates SimpleTimer

}

Hi William
yes I am very much interested to get two but also interested in learning how to make it function let me know how I can make the contribution
thank you it is a great job
Harry

I made some progress! Since I suck at woodworking, I stopped by my local home improvement center and purchased a prefabbed cabinet with just the right dimensions for my project. I’ve still got a few modifications to make to make it cosmetically appealing, but the all important electronics are already up and running. I installed Styrofoam sheeting on the inside to act as insulation to help raise the efficiency of the 60 watt peltier devices. So far, so good… the system has been able to easily maintain 75F with ambient temps in the high 90’s. Here’s some photos-

Overview of the Peltier Heater/Coolers with their associated electronics…

Front view-

Close up view- (There’s a Crested Gecko hanging out in the lower left corner)

4 Likes

WOW! :open_mouth:

This is such an awesome, real world, project!!
I can’t believe I missed this thread the first time around…

It’s kinda off-topic, but I’d really love a hand building and ordering a pcd like that for one of my own projects :smiley:

Thanks! The title of this thread probably had you thinking that it was about something else entirely…LOL. I should have went with “Another Terrarium powered by Blynk”. As it’s named now, I don’t think that even a Google Search would find it.

I’m already planning another ESP/Blynk related project… I think this one people will find useful. It will be a mother board of sorts, with a socket mounting slots for a Wemos D1 Mini, NODEmcu, and other IOT boards. Plus a few I2C/Serial based I/O port expanders, PWM controllers, and ADC’s, all broken out to screw/pin headers. Plus pre-configured connection points for the most popular sensors. I’m going to do my best to include everything anyone could want, and release the design files for anyone to use. Please provide feedback, if there’s something in particular that you’d like to have included.

On the subject of custom PcB’s… I’ve been using EAGLE since 2009. There’s a FreeWare version available here- http://www.cadsoftusa.com/downloads/logint. The Freeware version offers just enough work space to cover small ESP8266 related projects.
Locating parts… I use http://www.findchips.com/ to locate and compare prices. More often then not, I order everything I need from Digikey - http://www.digikey.com/.
For the PcB’s themselves… I use Elecrow for conventional FR4 boards - http://www.elecrow.com/ and Seeedstudios for single sided Aluminum boards - http://www.seeedstudio.com/depot/login.html . Both PcB suppliers offer very good quality, cheap prices, and exceptionally fast service. My last order from Elecrow was delivered to my hands in 6 days! Nearly instant gratification!

1 Like

hello @William, very nice project!

i saw that you used the MCP23017 extenders. this is what i would like to use in my current project, with a wemos d1 mini pro.

you used the extender just for output, or it is possible to use for input too. (to hook up multiple, different sensors?)

any advice for beginner experimenting with MCP23017?

thanks!
btw, you are hungarian? :slight_smile:

@wanek

Yes, the MCP23017 can be used for INPUT too. In my project there’s actually two pins on the MCP23017 that are wired as inputs. I’ll be using them for Fan failure detection, just as soon as I can figure out how to write the code(). From what I’ve read, the fans use Hall effect switches to generate a square wave signal. I might be able to use the PulseIn() function for detection, but I haven’t had time to explore that possibility yet.

As for being Hungarian? Yes, but I’m 4 generations away from the “Old Country”. My Great Grandfather emigrated to the USA in 1890. What digital clues did I leave laying around to give me away? :blush:

LOL- I’d never bothered to look at my own profile since joining this forum. So much for online anonymity :smirk:
I took another look at the Centipede library and found that there’s no provision for an analogRead() when using the MCP23017 pins as inputs. Only a digitalRead() is available. This will definitely limit what sensors, if any, that can be used with this chip.
I did some tests by mocking up a spare board with a couple of computer fans, and ran a simple digitalRead() script that outputs the tach input pin state to Serial.print(). Being that the tach circuit on my PcB has external PULL UP resistors tied to 3.3V, The Serial output showed the tach inputs toggling between LOW & HIGH as the fans spun at full speed. With the fans turned OFF or disconnected, the Serial output showed HIGH.
I’m guessing that if I want to detect a fan failure, I’ll need to write a non blocking function that looks for the input pin to remain HIGH for a set amount of time. Maybe a millis() based code will do the trick?

did you tried the above code? it is not suitable for what you need?

The code that you posted uses Arduino pins directly, and probably works just fine. Being that the MCP23017 can only perform digitalRead() and send the data to the ESP8266, I don’t see how your code could be modified to work in that situation?:frowning:

if the mcp23017 can not handle pulsein, it is not possible to use my code. it relies heavily on pulsein.
or, if you can use some digital pin directly on the esp and if the esp knows pulsein, than it could work.

just for error detection, without rpm reading, you can try to use something similar (pseudocode follows):

    bool fanError(byte fanPin)
    {
      unsigned long timestamp = millis();

      while (digitalRead(fanPin)){                    // if there is no low reading for certain millis, the function returns true -> error
        if (millis() - timestamp > 1000 / (minRpm / 60)) return true;  // minRpm**: you can set min. accepted rpm here
      }

      return false;
    }

** regarding minRpm, if i remember correctly, these fans send 2 low signals / revolution, so maybe minRpm should be divided by 120, instead 60. you should check this!

for example with a min rpm set to 300, this code will block the main loop for ~200 millis. that is fine, it should not create any problems with blynk or other code.

and in your code, you can implement it like this:

if (fanError(FAN1){
// stop the fan
// sound buzzer
// send email
// call chuck norris
}