Cloud Controlled Pellet Smoker

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 ignitor 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 and adjust Auger feed rate to maintain setpoint

Monitors
Monitor ambient and hot box temperatures
Show states of operation:
running
shutting down
fan
auger
ignitor
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!

4 Likes

Don’t they have some sort of smoked meat that’s native to Montreal?

@rob,

It’s basically just pastrami. The brisket is dry cured with salt and spices, then sometimes soaked in clean water to remove some of the salt, then rubbed again with spices and smoked. Usually more smoke dominant and the spice rubs more pepper and spice dominant.

All told, a great variation!

Cheers,

Craig

ps. Edmonton to Denver is half the distance as it is from Edmonton to Montreal. USA has smaller states like your president has tiny hands.

1 Like

I am WiFi pellet smoker. Can you please guide me in more depth about it? Shall be very thankful.

@williamelijah951 what more info do you need. can you be more specific.

Code needs to be converted to latest Cayenne MQTT, then it can be tweaked for use on your smoker.

I now recommend using the max31865 with a PT1000 sensor. If your smoker is already automated, it’s likely PT1000. If not, PT1000 is used in most ovens and available everywhere for $10.

I used this guy myself LinkNode R4: Arduino-compatible WiFi relay controller - LinkSprite Playgound, I’m sure there is something with more I/O breakouts now.

After watching how my CS570 controller works, I’ll consider better algorithms. The PID loop is really not fit for this kind of usage. A simpler algorithm will suffice.

Cheers,

Craig

Thanks for sharing the information
Fieldengineer

If you want, we can collaborate on a final solution.

Kreggly