My BBQ cooking monitor - ESP8266 dual K-probe via MAX31855 & OLED display

here’s my BBQ cooking monitor, it arose from my fireplace monitor and can quite easily be used for an oven monitor, a fridge/freezer monitor or anything where you want to measure temps with K-probes.

K-probes are best for temperatures that are extremes, as the ‘accuracy’ of the $5 eBay sensors is much more ‘variable’ compared to other eBay sensors like the DHT22 and DS18B’s & BME280’s I have used…

the hardware is:

  • ESP8266-12F
  • dual MAX31855 boards (Adafruit copies)
  • 5v to 3.3V converter
  • 0.96in OLED display

the software has:

  • Blynk as smartphone interface
  • Blynk for smartphone notification
  • dual MAX31855 sensor inputs
  • running median for temps
  • OTA update
  • user defined temp warnings




the code:

i have not set up the piezo alarm yet, but the notifys seem to work ok…

have a look at how i did the checkTempChange() series of functions - it was weird to do it like this with the second function step in between, but using simpleTimer i could not think of another way to get it done? maybe i was just being a bit dim-witted about it?

//heat and food temp monitor
//MAX31855 (2 pieces)
//Blynk
//Piezo alarm - TBC
//OLED display
//OTA update = http://192.168.0.5/update

#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <ESP8266HTTPUpdateServer.h>
#include <BlynkSimpleEsp8266.h>
#include <SimpleTimer.h>
#include <Wire.h>
#include <MAX31855.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <TimeLib.h>
#include <WidgetRTC.h>
#include "RunningMedian.h"

#define debug 1 // turns debugging serial prints on/off.
#define BLYNK_PRINT Serial //this is the debugging for Blynk

#define MISO 12 //data/"signal out"
#define SCK 13 // clock
#define heatCS 14 //chip select
#define foodCS 16 //chip select
#define piezoBuzzer 2 // pin for piezo buzzer, is ALSO the internal ESP8266 blue LED... 
// NB - pins 4 & 5 are SDA & SCL for OLED on ESP8266
#define OLED_RESET 3 //CAREFUL OF CHANGING THIS PIN ASSIGNMENT!!!!!!!!! (try to remove it later on...)

#define NUMFLAKES 10
#define XPOS 0
#define YPOS 1
#define DELTAY 2
#define LOGO16_GLCD_HEIGHT 16
#define LOGO16_GLCD_WIDTH  16
static const unsigned char PROGMEM logo16_glcd_bmp[] =
{ B00000000, B11000000,
  B00000001, B11000000,
  B00000001, B11000000,
  B00000011, B11100000,
  B11110011, B11100000,
  B11111110, B11111000,
  B01111110, B11111111,
  B00110011, B10011111,
  B00011111, B11111100,
  B00001101, B01110000,
  B00011011, B10100000,
  B00111111, B11100000,
  B00111111, B11110000,
  B01111100, B11110000,
  B01110000, B01110000,
  B00000000, B00110000
};

Adafruit_SSD1306 display(OLED_RESET);

MAX31855 foodProbe = MAX31855(MISO, foodCS, SCK);
MAX31855 heatProbe = MAX31855(MISO, heatCS, SCK);

RunningMedian samplesFoodTemperature = RunningMedian(20); //this takes XX samples
RunningMedian samplesHeatTemperature = RunningMedian(20); //this takes XX samples

WidgetRTC rtc;

WiFiClient client;
ESP8266WebServer httpServer(80);
ESP8266HTTPUpdateServer httpUpdater;
IPAddress gateway(192, 168, 0, 1);
IPAddress subnet(255, 255, 255, 0);

SimpleTimer timer;

const char* host = "esp8266_DUAL_MAX31855"; // will be "esp8266_XYZ.local/update" in web browser

const char* ssid = "abc";
const char* password = "123";
char authBlynk[] = "Costas_rulez";

int startHeat, heatTime, startHeatTime, heatTimerTimer;
int startFood, foodTime,  startFoodTime, foodTimerTimer;

int heatTemperature, foodTemperature;
int medianHeatTemperature, medianFoodTemperature;
int heatTemperatureMax = 0;
int foodTemperatureMax = 0;
int displayFreq = 2000L; // sets display update frequency
int displayTimer2, displayTimer3; //timers to call display update functions

int tempChangeFreq = 120L * 1000L; //time period to check temp changes
int tempChangeTimer; //name of timer to call temp change calculation function
int previousHeatTemperature, previousFoodTemperature; // to track temp changes over time
int oldPreviousHeatTemperature, oldPreviousFoodTemperature;
int newHeatTemperature, newFoodTemperature;
int heatTempChangePercent, foodTempChangePercent; //the variables produced from the calculation

int foodWarningTemp, heatWarningTemp; //temp that the notify is set to go off at
int heatWarningOffset = 12; //this is how much below the full warning temp the "pre" warning temp is.
int foodWarningOffset = 4; //this is how much below the full warning temp the "pre" warning temp is.

int food_h, food_m, food_s; //used for the food timer
int heat_h, heat_m, heat_s; //used for the heat timer

int setMode; //this is for the Blynk menu widget with a few commands in it, like reset temps and changing the warning parameters...

unsigned long notifyPeriodHeat = 0;
const long notifyTimeHeat = 60L * 1000L; //means its XX seconds between Heat notifications
unsigned long notifyPeriodFood = 0;
const long notifyTimeFood = 60L * 1000L; //means its XX seconds between Food notifications

unsigned long tempChangePeriodFood = 0;
const long tempChangeTimeFood = 15L * 1000L; //means its XX seconds between Food calcs
unsigned long tempChangePeriodHeat = 0;
const long tempChangeTimeHeat = 15L * 1000L; //means its XX seconds between Heat calcs

BLYNK_WRITE(V1)   // start heat timer button
{
  startHeat = param.asInt();
  if (startHeat == 1)
  {
    startHeatTime = now();
    heatTimer();
  }
}

void heatTimer()
{
  heatTimerTimer = timer.setTimeout(20L * 1000L, heatTimer);// timing counts every XX seconds
  if (startHeat == 1)
  {
    heat_s = (now() - startHeatTime);
    heat_m = heat_s / 60;
    heat_h = heat_s / 3600;
    heat_s = heat_s - heat_m * 60;
    heat_m = heat_m - heat_h * 60;
    Blynk.virtualWrite(V5, heat_h, "h ", heat_m, "m");
  }
}

BLYNK_WRITE(V2)   // start food timer button
{
  startFood = param.asInt();
  if (startFood == 1)
  {
    startFoodTime = now();
    foodTimer();
  }
}

void foodTimer()
{
  foodTimerTimer = timer.setTimeout(20L * 1000L, foodTimer);// timing counts every XX seconds
  if (startFood == 1)
  {
    food_s = (now() - startFoodTime);
    food_m = food_s / 60L;
    food_h = food_s / 3600L;
    food_s = food_s - food_m * 60L;
    food_m = food_m - food_h * 60L;
    Blynk.virtualWrite(V6, food_h, "h ", food_m, "m");
  }
}

BLYNK_WRITE(V10)  // food warning stepper
{
  foodWarningTemp = param.asInt();
  Blynk.virtualWrite(V4, foodWarningTemp, "'C");
}

BLYNK_WRITE(V9)  // heat warning stepper
{
  heatWarningTemp = param.asInt();
  Blynk.virtualWrite(V3, heatWarningTemp, "'C");
}

BLYNK_WRITE(V0) // mode selection drop down menu
{
  setMode = param.asInt();
  {
    if (setMode == 1) // reset max temps
    {
      foodTemperatureMax = 0;
      heatTemperatureMax = 0;
      Blynk.virtualWrite(V7, 0);
      Blynk.virtualWrite(V8, 0);
    }
    else if (setMode == 2) // heat pre-warning offset up
    {
      heatWarningOffset = heatWarningOffset + 1;
      Blynk.virtualWrite(V13, heatWarningOffset, "'C");
    }
    else if (setMode == 3) // heat pre-warning offset dwn
    {
      heatWarningOffset = heatWarningOffset - 1;
      Blynk.virtualWrite(V13, heatWarningOffset, "'C");
    }
    else if (setMode == 4) // food pre-warning offset up
    {
      foodWarningOffset = foodWarningOffset + 1;
      Blynk.virtualWrite(V14, foodWarningOffset, "'C");
    }
    else if (setMode == 5) // food pre-warning offset dwn
    {
      foodWarningOffset = foodWarningOffset - 1;
      Blynk.virtualWrite(V14, foodWarningOffset, "'C");
    }
    else if (setMode == 6) // spare
    {
      ///TBA
    }
  }
}

void readMAX31855HeatProbe()
{
  heatTemperature = heatProbe.readThermocouple(CELSIUS);
  Blynk.virtualWrite(V15, heatTemperature, "'C");
  switch ((int) heatTemperature)
  {
    case FAULT_OPEN:
      Serial.println("heatprobe: FAULT_OPEN");
      break;
    case FAULT_SHORT_GND:
      Serial.println("heatprobe: FAULT_SHORT_GND");
      break;
    case FAULT_SHORT_VCC:
      Serial.println("heatprobe: FAULT_SHORT_VCC");
      break;
    case NO_MAX31855:
      Serial.println("heatprobe: NO_MAX31855");
      break;
    default:
      Serial.print("heatprobe: ");
      Serial.println(heatTemperature);
      break;
  }

  medianHeatTemperature = samplesHeatTemperature.getMedian();
  samplesHeatTemperature.add(heatTemperature);

  if ((medianHeatTemperature < 888) && (heatTemperatureMax >= medianHeatTemperature))
  {
    Blynk.virtualWrite(V11, medianHeatTemperature);
  }
  else if ((medianHeatTemperature < 888) && (heatTemperatureMax < medianHeatTemperature))
  {
    Blynk.virtualWrite(V11, medianHeatTemperature);
    heatTemperatureMax = medianHeatTemperature;
    Blynk.virtualWrite(V7, heatTemperatureMax, "'C");
  }
  else if (medianHeatTemperature > 888)
  {
    Blynk.virtualWrite(V11, 999);
  }
  readMAX31855FoodProbe();
}

void readMAX31855FoodProbe()
{
  foodTemperature = foodProbe.readThermocouple(CELSIUS);
  Blynk.virtualWrite(V16, foodTemperature, "'C");
  switch ((int) foodTemperature)
  {
    case FAULT_OPEN:
      Serial.println("foodprobe: FAULT_OPEN");
      break;
    case FAULT_SHORT_GND:
      Serial.println("foodprobe: FAULT_SHORT_GND");
      break;
    case FAULT_SHORT_VCC:
      Serial.println("foodprobe: FAULT_SHORT_VCC");
      break;
    case NO_MAX31855:
      Serial.println("foodprobe: NO_MAX31855");
      break;
    default:
      Serial.print("foodprobe: ");
      Serial.println(foodTemperature);
      break;
  }

  medianFoodTemperature = samplesFoodTemperature.getMedian();
  samplesFoodTemperature.add(foodTemperature);

  if ((medianFoodTemperature < 888) && (foodTemperatureMax >= medianFoodTemperature))
  {
    Blynk.virtualWrite(V12, medianFoodTemperature);
  }
  else if ((medianFoodTemperature < 888) && (foodTemperatureMax < medianFoodTemperature))
  {
    Blynk.virtualWrite(V12, medianFoodTemperature);
    foodTemperatureMax = medianFoodTemperature;
    Blynk.virtualWrite(V8, foodTemperatureMax, "'C");
  }
  else if (medianFoodTemperature > 888)
  {
    Blynk.virtualWrite(V12, 999);
  }
  checkHeatTemp();
}

void checkHeatTemp()
{
  unsigned long currentMillis = millis();
  if ((medianHeatTemperature < heatWarningTemp) && ((medianHeatTemperature + heatWarningOffset) > heatWarningTemp))
  {
    if (((currentMillis - notifyPeriodHeat) > notifyTimeHeat) && (medianHeatTemperature < 888))
    {
      Blynk.notify(String("Heat temp APPROACHING warning temp: ") + medianHeatTemperature + ("'C - go check it..."));
      notifyPeriodHeat = currentMillis;
    }
  }
  else if (medianHeatTemperature >= heatWarningTemp)
  {
    if (((currentMillis - notifyPeriodHeat) > notifyTimeHeat) && (medianHeatTemperature < 888))
    {
      Blynk.notify(String("Heat temp getting TOOOO HIGHHH: ") + medianHeatTemperature + ("'C - go check it!!!"));
      notifyPeriodHeat = currentMillis;
    }
    //runAlarmFunction();
  }
  Blynk.virtualWrite(V13, "-", heatWarningOffset, "'C");
  checkFoodTemp();
}

void checkFoodTemp()
{
  unsigned long currentMillis = millis();
  if ((medianFoodTemperature < foodWarningTemp) && ((medianFoodTemperature + foodWarningOffset) > foodWarningTemp))// temp creeping up
  {
    if (((currentMillis - notifyPeriodFood) > notifyTimeFood) && (medianFoodTemperature < 169))
    {
      Blynk.notify(String("Food temp APPROACHING warning temp: ") + medianFoodTemperature + ("'C - go check it..."));
      notifyPeriodFood = currentMillis;
    }
  }
  else if (medianFoodTemperature >= foodWarningTemp)
  {
    if (((currentMillis - notifyPeriodFood) > notifyTimeFood) && (medianFoodTemperature < 169))
    {
      Blynk.notify(String("Food temp getting TOOO HIGGHHH: ") + medianFoodTemperature + ("'C - go check it!!!"));
      notifyPeriodFood = currentMillis;
    }
    //runAlarmFunction();
  }
  Blynk.virtualWrite(V14, "-", foodWarningOffset, "'C");
  displayUpdate1();
}

void checkTempChange()
{
  previousHeatTemperature = heatTemperature;
  previousFoodTemperature = foodTemperature;
  timer.setTimeout(10000L, checkTempChange1);
}

void checkTempChange1()
{
  oldPreviousFoodTemperature = previousFoodTemperature;
  timer.setTimeout(10000L, checkTempChange2);
}

void checkTempChange2()
{
  newHeatTemperature = heatTemperature;
  heatTempChangePercent = (((newHeatTemperature - oldPreviousHeatTemperature) * 100) / newHeatTemperature);
  Blynk.virtualWrite(V3, heatTempChangePercent, "%");

  newFoodTemperature = foodTemperature;
  foodTempChangePercent = (((newFoodTemperature - oldPreviousFoodTemperature) * 100) / newFoodTemperature);
  Blynk.virtualWrite(V4, foodTempChangePercent, "%");
  Blynk.virtualWrite(V17, oldPreviousFoodTemperature);
  Blynk.virtualWrite(V18, newFoodTemperature);
}

void displayUpdate1()
{
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.println("ELAPSED HEAT TIME:");

  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(32, 16);
  display.print(heat_h);
  display.print("h ");
  display.print(heat_m);
  display.println("m");

  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0, 32);
  display.println("ELAPSED COOK TIME:");

  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(32, 48);
  display.print(food_h);
  display.print("h ");
  display.print(food_m);
  display.println("m");
  display.display();

  displayTimer2 = timer.setTimeout(displayFreq, displayUpdate2);
}

void displayUpdate2()
{
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.println("HEAT WARNING TEMP:");

  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(32, 16);
  display.print(heatWarningTemp);
  display.print((char)247);
  display.println("C");

  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0, 32);
  display.println("FOOD WARNING TEMP:");

  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(32, 48);
  display.print(foodWarningTemp);
  display.print((char)247);
  display.println("C");
  display.display();

  displayTimer3 = timer.setTimeout(displayFreq, displayUpdate3);
}

void displayUpdate3()
{
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.println("HEAT PROBE TEMP:");

  if (medianHeatTemperature > 1000)
  {
    medianHeatTemperature = 0;
  }
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(32, 16);
  display.print(medianHeatTemperature);
  display.print((char)247);
  display.println("C");

  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0, 32);
  display.println("FOOD PROBE TEMP:");

  if (medianFoodTemperature > 1000)
  {
    medianFoodTemperature = 0;
  }
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(32, 48);
  display.print(medianFoodTemperature);
  display.print((char)247);
  display.println("C");
  display.display();
  Serial.println(F("__________"));
}


void runAlarmFunction()
{ //tone(piezoBuzzer, 1000); // Send 1KHz sound signal...
  //delay(1000);        // ...for 1 sec
  //noTone(piezoBuzzer);     // Stop sound...
  //delay(1000);        // ...for 1sec
}

void setup()
{
  Serial.begin(115200);
  Serial.println(F(""));
  Serial.println(F("Cooking heat & food temp MONITOR"));
  Serial.print(F("File name: "));
  Serial.println(__FILE__);

  //Blynk.begin(authBlynk, ssid, password); // this is for roaming profile
  Blynk.begin(authBlynk, ssid, password, IPAddress(192, 168, 0, 7));

  ArduinoOTA.setHostname("Multi-MAX31855-Node08");

  ArduinoOTA.onStart([]() {
    Serial.println("Start");
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nEnd");
  });
  //ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
  //  Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  //});
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
    else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
    else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
    else if (error == OTA_END_ERROR) Serial.println("End Failed");
  });
  ArduinoOTA.begin();
  Serial.println("Ready - OTA Success!!!");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  MDNS.begin(host);
  httpUpdater.setup(&httpServer);
  httpServer.begin();
  MDNS.addService("http", "tcp", 80);
  String ipaddress = WiFi.localIP().toString();
  String chipID = String(ESP.getChipId(), HEX);
  char charChipID[10];
  chipID.toCharArray(charChipID, sizeof(charChipID));
  char charipaddress[16];
  ipaddress.toCharArray(charipaddress, sizeof(charipaddress));
  Serial.printf("Now open http://%s.local/update in your browser or \n", host);
  Serial.printf("http://%s/update or http://%s.lan/update if you prefer.\n", charipaddress, charChipID);

  Serial.println(F("Where's Blynk??"));
  while (!Blynk.connect())
  {
    delay(50);
    Serial.print(F(". "));
  }

  Serial.println(F(""));
  Serial.println(F("Found some WiFi!"));
  Serial.println(F("------------"));

  Blynk.virtualWrite(V1, 0);
  Blynk.virtualWrite(V2, 0);
  Blynk.virtualWrite(V5, 0);
  Blynk.virtualWrite(V6, 0);
  Blynk.virtualWrite(V7, 0);
  Blynk.virtualWrite(V8, 0);
  Blynk.virtualWrite(V11, 0);
  Blynk.virtualWrite(V12, 0);
  Blynk.virtualWrite(V13, 0);
  Blynk.virtualWrite(V14, 0);

  bool isFirstConnect = true; // Keep this flag not to re-sync on every reconnection

  BLYNK_CONNECTED(); // This function will run every time Blynk initial connection is established
  {
    if (isFirstConnect)
    {
      Blynk.syncAll();
      isFirstConnect = false;
    }
  }
  rtc.begin();

  Serial.println(F("We operate under <SimpleTimer.h> now..."));
  timer.setInterval(8000L, readMAX31855HeatProbe);
  timer.setInterval(20L * 1000L, checkTempChange);

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3_
  display.display();
  display.clearDisplay();
}

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

let me know of any thoughts, ideas, comments or suggestions… :slight_smile:

16 Likes

on the job:

perfection achieved :slight_smile:

13 Likes

:heart_eyes::clap::poultry_leg:

2 Likes

Cool Projekt.
Can you show me your wiring of all the components ?

I Have a NodeMCU and some MAX31855 Boards and want to play with that.
OLED is ordered on Ebay. I only have a 1,6" TFT Display with SPI Interface.

Mattes1007

1 Like

Sure mattes, it’s something that took me a while to determine, so I’ll do a schematic today for you…

I think the OLED is much better than TFT in outdoor situation, even though it is smaller size.

What a delicious project! :yum: The Taste Of Blynk… Would say: Imagination is the limit - never thought about using blynk for cooking…

1 Like

here is the wiring schema:

2 Likes

Hello Dave.
What type of K-Probe do you use? Any reliable seller’s link?
Thanks a lot

Best Regards,
Mike Kranidis

nah mate, i just buy the $5 k-probes, various sellers, i just sort by “Price + postage: lowest first” :wink:

im pretty sure there’s not much to a cheap k-probe… as long as you personally calibrate the readings, and recalibrate often, you should be fine…

[google “calibrate thermocouple” e.g. Adafruit discussion]

you could get expensive ones, but only if lives depended on it…

2 Likes

@Dave1829: I do understand, that MAX31855 have onboard voltage regulators? - these are 3,3v parts

i have no idea marvin.

i just feed the breakout boards with 5VDC and they seem to work…

the black thing on the LHS is a voltage regulator i think :slight_smile:

1 Like

the data sheets says 4VDC MAX

I know :slight_smile: That is why I asked, after seeing your schematic.

Yes, I can see the LDO on the left, on the picture above, so it is fine to feed it with 5V on Vin pin (or 3,3 on 3Vo)

So… next BBQ at your house? I’ll bring some beers, LOL.

firstly, it is NOT a schematic it is a “schema” - slightly different…

secondly, everything in this thread should be read in conjunction with the first post, which states:

the Adafruit board can be referenced here: Thermocouple Amplifier MAX31855 breakout board (MAX6675 upgrade) : ID 269 : $14.95 : Adafruit Industries, Unique & fun DIY electronics and kits

lets invite @philmurp too!

the best thing about this app is “watching” the BBQ from the comfort of the lounge room…

Thank you, for the wiring.
But after 3 days of testing i can´t get it work, because my 3 MAX31855 are on the way to the garbage can :wink:

They do not work properly and supply incorrect values. Cheap china scrap.
So I have to order only a few new MAX31855, then start again.

Thanks mattes

did you add a 0.1uF capacitors across the k-probe +/- wires for each board?

these seem to be essential to stabilize the readings… also NEVER let the shielded cables touch each other…

perhaps make sure you get ONE of the MAX31855 working THEN add the next one etc…

Hi,
I just tried again and connected the capacitor between + and -. Unfortunately it does not work better.
It shows -2085.25 °C.

Do you get 10000 when it’s disconnected? What does serial monitor say?