Project showcase

Cloud Controlled Pellet Smoker © LGPL

I decided to build something better that I can monitor my smoker from the inside.

  • 53 views
  • 0 comments
  • 1 respect

Components and supplies

Apps and online services

About this project

Couldn't sleep so thought I'd put this in here as a work in progress...

About This Project

Before moving back to Canada, I lived for 10 years in the deep south of Texas and Alabama. Needless to say, I grew a fondness for the delectable smoked meats there, namely Texas brisket, and Southern pulled pork.

I would often spend lazy Sundays smoking beef, pork butts, and ribs in a simple drum smoker over several beers and college football. Well that all changed when I moved back to Canada and the winter temperatures plummet to way below zero!

My current smoker feeds wood pellets into a hot box with an igniter and a fan to get the pellets started and create convection flow around the meat. My current controller is open loop, meaning there is no automatic adjustment of the hot box temperature when external factors change. Alberta weather is impossible to predict!

On a long smoke, the internal temperature can change by as much as 70 degrees. Too much, so I decided to build something better that I can monitor from inside, on the couch, drinking beers and watching college football.

The project operates on a state machine and implements a PID loop.

When START pressed, do the following: Set running state:

  • Turn FAN on.
  • Turn IGNITOR ON
  • Start timer to turn off IGNITER after X seconds
  • Start PID loop

When STOP pressed, do the following:

  • Set shutting down state
  • Turn PID off
  • Start cool down timer
  • Turn off fan

PID operation:

  • Monitor hot box temperature
  • Adjust Auger feed rate to maintain setpoint.

Monitors:

  • Monitor ambient and hot box temperatures

Show states of operation:

  • running
  • shutting down fan auger igniter hopper low

What's Connected

MKR1000 with a 1200mAH Lithium Polymer backup battery

  • this is in case the GFCI trips due to a hard rain. MAX31855 SPI based thermocouple amplifier
  • provides ambient (cold junction) and hot box temp K Type Thermocouple connected to the MAX31855
  • mounted in centre of hot box 4 port relay board
  • controls the fan, igniter, auger, and a spare hopper low switch
  • alarms when the hopper is getting empty

* Need to add power loss detection alarm * Perhaps other more complex alarms like failed to ignite

Triggers & Alerts

  • Hopper low alarm: out of fuel
  • Bob Over-temp Alarm: Oh Lord, there's a fire
  • Out of control Alarm: Temperature is not tracking with setpoint

Scheduling

Haven't used yet, but I could easily add a schedule to warm up the smoker before I get home, or do a rest, or stay warm program.

Dashboard Screenshots

Code

//#define CAYENNE_DEBUG         // Uncomment to show debug messages
#define CAYENNE_PRINT Serial  // Comment this out to disable prints and save space
#include <WiFi101.h>
#include <CayenneMKR1000.h>
#include <Timer.h>
#include <PID_v1.h>
#include <SPI.h>
#include <MAX31855_SPI.h>
//Define Variables we'll be connecting to
double Setpoint, Input, Output,PID_Input;
#define AugerPin 2
//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint,0.5,1,5, DIRECT);
int WindowSize = 30000;
unsigned long windowStartTime;
byte cs = 0; //chip select is DIO 0
double internal_temp;
double coupler_temp;
MAX31855_SPI *max31855;
int running = 0;
Timer t;
// Cayenne authentication token. This should be obtained from the Cayenne Dashboard.
char token[] = "";
//char token[] = "";
// Your network name and password.
char ssid[] = "";
char password[] = "";
void setup()
{
  max31855 = new MAX31855_SPI(cs);
  SPI.begin();
  //relays
  pinMode(1,OUTPUT);
  pinMode(2,OUTPUT);
  pinMode(3,OUTPUT);
  pinMode(4,OUTPUT);
  
  //setup the pid
  windowStartTime = millis();
  //initialize the variables we're linked to
  Setpoint = 100;
  //tell the PID to range between 0 and the full window size
  myPID.SetOutputLimits(0, WindowSize);
  //turn the PID on
  myPID.SetMode(AUTOMATIC);
  
  Serial.begin(9600);
  Cayenne.begin(token, ssid, password);
}
void loop()
{
  internal_temp = max31855->readInternal();
  internal_temp = internal_temp * 9 / 5 + 32;
  coupler_temp = max31855->read();
  coupler_temp = coupler_temp * 9 / 5 + 32;
  
  Cayenne.run();
  
  t.update();
  if(running)
  {
    Input = coupler_temp;
    myPID.Compute();
  
    /************************************************
     * turn the output pin on/off based on pid output
     ************************************************/
    unsigned long now = millis();
    if(now - windowStartTime>WindowSize)
    { //time to shift the Relay Window
      windowStartTime += WindowSize;
    }
    if(Output > now - windowStartTime) digitalWrite(AugerPin,1);
    else digitalWrite(AugerPin,0);    
  } 
  else digitalWrite(AugerPin,0);    
}
//setpoint
CAYENNE_IN(V0)
{
  Setpoint = getValue.asInt()/1000;
  Serial.print("Setpoint:");
  Serial.println(Setpoint);
}
//cold junction temp
CAYENNE_OUT(V1)
{
  Cayenne.virtualWrite(V1,internal_temp);
}
//thermocouple temp
CAYENNE_OUT(V2)
{
  Cayenne.virtualWrite(V2,coupler_temp);
}
//start button
CAYENNE_IN(V3)
{
  if (getValue.asInt())
  {
    running = 1;    
    //indicate running
    Cayenne.virtualWrite(V8,1);
    //clear start button
    Cayenne.virtualWrite(V3,0);
    //start fan
    digitalWrite(3,1);
    //indicate fan running
    Cayenne.virtualWrite(V7,1);
    
    //start ignitor cycle
    ignite();           
  }
}
//shutdown button
CAYENNE_IN(V4)
{
  if (getValue.asInt())
  {
    running = 0;    
    //indicate shutting down
    Cayenne.virtualWrite(V8,0);
    Cayenne.virtualWrite(V9,1);
    //do some stuff
    
    t.after(5000,shutdown);
    //clear shutdown button
    Cayenne.virtualWrite(V4,0);
  
  }
}
//hopper low
CAYENNE_OUT(V6)
{
  Cayenne.virtualWrite(V6,digitalRead(2));
}
void shutdown()
{
  //check temp is cool
  //send error messages, etc.
  //turn off fan
  //clear internal variables
  //ignitor
  Cayenne.virtualWrite(V5,0);
  //auger
  Cayenne.virtualWrite(V6,0);
  //fan
  Cayenne.virtualWrite(V7,0);
  //indicate no longer running
  Cayenne.virtualWrite(V9,0);
  
}
void ignite()
{
  //indicate ignitor on
  Cayenne.virtualWrite(V5,1);
  //actually start the ignitor
  digitalWrite(1,1);
  //set callback timer
  t.after(10000,ignitor_off);
}
void ignitor_off()
{
  //turn off the ignitor
  digitalWrite(1,0);
  //check temperature
  //alert if not hot enough
  //indicate ignitor off
  Cayenne.virtualWrite(V5,0);
}
void auger_time()
{
  digitalWrite(AugerPin,1);
  Cayenne.virtualWrite(V6,1);
  delay(1000);
  digitalWrite(AugerPin,0);
  Cayenne.virtualWrite(V6,0);
}

Photos of the Project

Need to wire it up once it's a little warmer. Still sub-zero here.

Video

Soon!

Code

Code snippet #1Plain text
//#define CAYENNE_DEBUG         // Uncomment to show debug messages
#define CAYENNE_PRINT Serial  // Comment this out to disable prints and save space
#include <WiFi101.h>
#include <CayenneMKR1000.h>
#include <Timer.h>
#include <PID_v1.h>
#include <SPI.h>
#include <MAX31855_SPI.h>

//Define Variables we'll be connecting to
double Setpoint, Input, Output,PID_Input;

#define AugerPin 2

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint,0.5,1,5, DIRECT);

int WindowSize = 30000;
unsigned long windowStartTime;
byte cs = 0; //chip select is DIO 0
double internal_temp;
double coupler_temp;
MAX31855_SPI *max31855;

int running = 0;

Timer t;

// Cayenne authentication token. This should be obtained from the Cayenne Dashboard.
char token[] = "";
//char token[] = "";
// Your network name and password.
char ssid[] = "";
char password[] = "";

void setup()
{
  max31855 = new MAX31855_SPI(cs);
  SPI.begin();

  //relays
  pinMode(1,OUTPUT);
  pinMode(2,OUTPUT);
  pinMode(3,OUTPUT);
  pinMode(4,OUTPUT);
  
  //setup the pid
  windowStartTime = millis();

  //initialize the variables we're linked to
  Setpoint = 100;

  //tell the PID to range between 0 and the full window size
  myPID.SetOutputLimits(0, WindowSize);

  //turn the PID on
  myPID.SetMode(AUTOMATIC);
  
  Serial.begin(9600);
  Cayenne.begin(token, ssid, password);
}

void loop()
{
  internal_temp = max31855->readInternal();
  internal_temp = internal_temp * 9 / 5 + 32;
  coupler_temp = max31855->read();
  coupler_temp = coupler_temp * 9 / 5 + 32;
  
  Cayenne.run();
  
  t.update();

  if(running)
  {
    Input = coupler_temp;
    myPID.Compute();
  
    /************************************************
     * turn the output pin on/off based on pid output
     ************************************************/
    unsigned long now = millis();
    if(now - windowStartTime>WindowSize)
    { //time to shift the Relay Window
      windowStartTime += WindowSize;
    }
    if(Output > now - windowStartTime) digitalWrite(AugerPin,1);
    else digitalWrite(AugerPin,0);    
  } 
  else digitalWrite(AugerPin,0);    
}

//setpoint
CAYENNE_IN(V0)
{
  Setpoint = getValue.asInt()/1000;

  Serial.print("Setpoint:");
  Serial.println(Setpoint);
}

//cold junction temp
CAYENNE_OUT(V1)
{
  Cayenne.virtualWrite(V1,internal_temp);
}

//thermocouple temp
CAYENNE_OUT(V2)
{
  Cayenne.virtualWrite(V2,coupler_temp);
}

//start button
CAYENNE_IN(V3)
{
  if (getValue.asInt())
  {
    running = 1;    

    //indicate running
    Cayenne.virtualWrite(V8,1);

    //clear start button
    Cayenne.virtualWrite(V3,0);

    //start fan
    digitalWrite(3,1);

    //indicate fan running
    Cayenne.virtualWrite(V7,1);
    
    //start ignitor cycle
    ignite();           
  }
}

//shutdown button
CAYENNE_IN(V4)
{
  if (getValue.asInt())
  {
    running = 0;    

    //indicate shutting down
    Cayenne.virtualWrite(V8,0);
    Cayenne.virtualWrite(V9,1);

    //do some stuff
    
    t.after(5000,shutdown);

    //clear shutdown button
    Cayenne.virtualWrite(V4,0);
  
  }
}

//hopper low
CAYENNE_OUT(V6)
{
  Cayenne.virtualWrite(V6,digitalRead(2));
}

void shutdown()
{
  //check temp is cool
  //send error messages, etc.
  //turn off fan
  //clear internal variables

  //ignitor
  Cayenne.virtualWrite(V5,0);
  //auger
  Cayenne.virtualWrite(V6,0);
  //fan
  Cayenne.virtualWrite(V7,0);

  //indicate no longer running
  Cayenne.virtualWrite(V9,0);
  
}

void ignite()
{
  //indicate ignitor on
  Cayenne.virtualWrite(V5,1);

  //actually start the ignitor
  digitalWrite(1,1);

  //set callback timer
  t.after(10000,ignitor_off);
}

void ignitor_off()
{
  //turn off the ignitor
  digitalWrite(1,0);

  //check temperature
  //alert if not hot enough

  //indicate ignitor off
  Cayenne.virtualWrite(V5,0);
}

void auger_time()
{
  digitalWrite(AugerPin,1);
  Cayenne.virtualWrite(V6,1);
  delay(1000);
  digitalWrite(AugerPin,0);
  Cayenne.virtualWrite(V6,0);
}
Code snippet #2Plain text
//#define CAYENNE_DEBUG         // Uncomment to show debug messages
#define CAYENNE_PRINT Serial  // Comment this out to disable prints and save space
#include <WiFi101.h>
#include <CayenneMKR1000.h>
#include <Timer.h>
#include <PID_v1.h>
#include <SPI.h>
#include <MAX31855_SPI.h>

//Define Variables we'll be connecting to
double Setpoint, Input, Output,PID_Input;

#define AugerPin 2

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint,0.5,1,5, DIRECT);

int WindowSize = 30000;
unsigned long windowStartTime;
byte cs = 0; //chip select is DIO 0
double internal_temp;
double coupler_temp;
MAX31855_SPI *max31855;

int running = 0;

Timer t;

// Cayenne authentication token. This should be obtained from the Cayenne Dashboard.
char token[] = "";
//char token[] = "";
// Your network name and password.
char ssid[] = "";
char password[] = "";

void setup()
{
  max31855 = new MAX31855_SPI(cs);
  SPI.begin();

  //relays
  pinMode(1,OUTPUT);
  pinMode(2,OUTPUT);
  pinMode(3,OUTPUT);
  pinMode(4,OUTPUT);
  
  //setup the pid
  windowStartTime = millis();

  //initialize the variables we're linked to
  Setpoint = 100;

  //tell the PID to range between 0 and the full window size
  myPID.SetOutputLimits(0, WindowSize);

  //turn the PID on
  myPID.SetMode(AUTOMATIC);
  
  Serial.begin(9600);
  Cayenne.begin(token, ssid, password);
}

void loop()
{
  internal_temp = max31855->readInternal();
  internal_temp = internal_temp * 9 / 5 + 32;
  coupler_temp = max31855->read();
  coupler_temp = coupler_temp * 9 / 5 + 32;
  
  Cayenne.run();
  
  t.update();

  if(running)
  {
    Input = coupler_temp;
    myPID.Compute();
  
    /************************************************
     * turn the output pin on/off based on pid output
     ************************************************/
    unsigned long now = millis();
    if(now - windowStartTime>WindowSize)
    { //time to shift the Relay Window
      windowStartTime += WindowSize;
    }
    if(Output > now - windowStartTime) digitalWrite(AugerPin,1);
    else digitalWrite(AugerPin,0);    
  } 
  else digitalWrite(AugerPin,0);    
}

//setpoint
CAYENNE_IN(V0)
{
  Setpoint = getValue.asInt()/1000;

  Serial.print("Setpoint:");
  Serial.println(Setpoint);
}

//cold junction temp
CAYENNE_OUT(V1)
{
  Cayenne.virtualWrite(V1,internal_temp);
}

//thermocouple temp
CAYENNE_OUT(V2)
{
  Cayenne.virtualWrite(V2,coupler_temp);
}

//start button
CAYENNE_IN(V3)
{
  if (getValue.asInt())
  {
    running = 1;    

    //indicate running
    Cayenne.virtualWrite(V8,1);

    //clear start button
    Cayenne.virtualWrite(V3,0);

    //start fan
    digitalWrite(3,1);

    //indicate fan running
    Cayenne.virtualWrite(V7,1);
    
    //start ignitor cycle
    ignite();           
  }
}

//shutdown button
CAYENNE_IN(V4)
{
  if (getValue.asInt())
  {
    running = 0;    

    //indicate shutting down
    Cayenne.virtualWrite(V8,0);
    Cayenne.virtualWrite(V9,1);

    //do some stuff
    
    t.after(5000,shutdown);

    //clear shutdown button
    Cayenne.virtualWrite(V4,0);
  
  }
}

//hopper low
CAYENNE_OUT(V6)
{
  Cayenne.virtualWrite(V6,digitalRead(2));
}

void shutdown()
{
  //check temp is cool
  //send error messages, etc.
  //turn off fan
  //clear internal variables

  //ignitor
  Cayenne.virtualWrite(V5,0);
  //auger
  Cayenne.virtualWrite(V6,0);
  //fan
  Cayenne.virtualWrite(V7,0);

  //indicate no longer running
  Cayenne.virtualWrite(V9,0);
  
}

void ignite()
{
  //indicate ignitor on
  Cayenne.virtualWrite(V5,1);

  //actually start the ignitor
  digitalWrite(1,1);

  //set callback timer
  t.after(10000,ignitor_off);
}

void ignitor_off()
{
  //turn off the ignitor
  digitalWrite(1,0);

  //check temperature
  //alert if not hot enough

  //indicate ignitor off
  Cayenne.virtualWrite(V5,0);
}

void auger_time()
{
  digitalWrite(AugerPin,1);
  Cayenne.virtualWrite(V6,1);
  delay(1000);
  digitalWrite(AugerPin,0);
  Cayenne.virtualWrite(V6,0);
}

Comments

Similar projects you might like

Water Softener

Project tutorial by moty

  • 132 views
  • 0 comments
  • 4 respects

Safe City: A Device to Keep You Safe

Project tutorial by Patel Darshil

  • 407 views
  • 0 comments
  • 7 respects

Simple Obstacle Senor With Arduino

Project tutorial by muhammed shameel k.v

  • 41 views
  • 0 comments
  • 2 respects

Know the Temp of Your Home From Anywhere with Arduino Cloud!

Project tutorial by Isaac100

  • 688 views
  • 0 comments
  • 6 respects
Add projectSign up / Login