Google Analytics

Saturday, February 25, 2012

Banjo on ... Security and Spoofing

People on the Internet that are out to take advantage of you look for any way possible to do so.  I'm going to show you one of their favorite scams.  This is already well documented, so I'm not showing the crooks anything new.

One trick is to show you a web link to your bank, then tell you to use that link to log into your account.  The link will look just like a valid link to your bank.  But it's wolf in sheep's clothing - it only looks like you are going to your bank - instead, it's sending you to the crook's site, where the crook presents you with a fake login screen that looks just like your banks login screen!

I'm going to show you an example, using a well known site - CNN's web site.  I'm going to show yo a link that says it's going to take you to CNN's web site, but instead, I'm going to send you to The Drudge Report.  I'm going to open both of these web pages in a new window.

Here's the real link.  It's actually going to open a window to CNN's web site.  http://www.cnn.com

Here's the fake link.  It's showing that it's taking you to CNN's site, but it's taking you to Drudge:  http://www.cnn.com

Here's another fake example.  Click here to go to CNN's Trusted Web Site.  Didn't take you there did it?  Or how about this fake link, Click here to go to YOUR Trusted Bank Site (of course, it would look like your actual bank).

Notice what I did here.  I gave you a convenience link to a site you want to go to, but I've spoofed you into actually going somewhere else.  It certainly looked like you were going to be taken to CNN's web site, didn't it?  And once you get to that site I want you to go to, you can easily be taken advantage of.

Get the way they are taking advantage of you?  They are showing you one thing, making you think you are going to your desired site, but they are taking you somewhere they want you to go, and are presenting you with fake logins to capture your private information.

So, don't go to a supposed link to your bank via an email sent to you.  If you feel the need to go to your bank, or somewhere else that a crook would love to have your account information to, then use your own independent method for getting to your bank!


Remember this rule:  never use a convenience link supplied to you in an email that is taking you somewhere involved with your money or valuables.  If you are going to somewhere you keep your valuables, then use your own unique method for getting there!


Saturday, February 18, 2012

Wired's 'Must-Complete Projects for Geeks'

Wired Magizine just published an article entitled: Must-Complete Projects for Every Hardcore Tech Geek.

So let's see where I stand:

  1. Build a PC from Scratch.  Done that.  Only when they say 'from scratch' they mean from modules.  I've done that 10 times or more.  However, the first computer I built was in 1978, and it was way more than sliding in modules - it entailed soldering in every IC chip, every keyboard spring, the flyback yoke on the video monitor, solenoids on the paper-tape punch-reader - stuff like that.  So been there, done that, squared.  One interesting caveat - it cost me $3,600, plust $2,500 (dual 8" floppy drives with 256K storage each) for a total of $6,100; I could have bought a new car at that time for that much money (I paid $9,000 for a brand new 280Z at that time).
  2. Overclock a PC.  The most recent ASUS motherboard I bought and installed into my trading platform makes it way-easier than it used to be to do this.
  3.  Build a Robot or Drone Flying Machine.  Nope, haven't done this, as I haven't had the desire.  But do-able?  Sure.  Unless...we include my robot to turn my porch heater on and off...so, unless, by definition a 'robot' includes mobility, then I've done this.  But I think the 'flavor' of this question probably includes mobility.
  4. Hack your body with a geek tattoo.  That may be in their definition of what constitutes a 'geek', but it isn't in mine, so I'm putting a rubber stamp on this one that says: "AAAAhhhhh - wrong question'.  If you can't find anything more geeky then this, the go back to sleep.
  5. Live on Linux for at least a month.  Lived on UNIX for years, first time was on Coherent, which preceded Linux by a couple of years, and have been running Linux on various older machines since 1997 or so.  I remember getting a UseNet invitation from Linus Torvalds to participate in building Linux.
  6. Perform Invasive Surgery on a Mobile Device.  Nope, not interested in 'jail-braking' any of my devices, and voiding warranties.  There's too many gadgets out there that do everything I want without having to go jail-break something and voiding the warranty.  I don't agree with the premise of this question either.  So, invalid question for geeky.
  7. Creat a man cave.  Boy, have I ever done that!  Three tree-houses, one porch (where I can drink Scotch and smoke cigars while listening to Blues), and one finished pine-paneled basement (even the ceiling!) with recessed wooden ceiling trays.  One difference: I love having females in my man-caves! And they appear to love being there!
  8. Carry Around Essential PC Apps on a USB Stick.  Well, they aren't in my pocket, because - what's the point?  But I do have them at my desk and in my personally belongings.  I frequently use Linux live-cd boots to correct Windows PC boot issues.
  9. Brew Your Own Beer or Distill Your Own Moonshine.  Are you kidding me?  I'm a Southerner!  Bet-cher-ass.
So, I'm claiming I passed.  And if you look at #1, there's hardly anyone in my league ... not because they aren't smart enough, but because I don't think you can actually by the parts to do this anymore.  

Important stuff they left out:
  1. Have a home network with 10 PCs or more.
  2. Have a home network with Linux, Windows, and Mac OSes on it.
  3. Designed and built hardware stereo sound amplifiers.  Extra points for putting them into Altoid tin boxes.
  4. Designed and built Arduino devices, such as: BBQ Smoker Controllers; Gas Heater temperature controllers; PID controllers; Sous Vide controller; Low Temperature Oven controller; air valve controller.
  5. Know how to use an oscilloscope, function generator, frequency counter.  And when to use them.
  6. Instrument rating for private pilot.
  7. Design sundials.  And that means derive your own spherical triganometry calculations - not getting them from a book.
  8. Know why the answer is wrong when the teacher tells you that astronauts float in the space shuttle because there isn't any gravity.....well double-damn crap - if there wasn't any gravity, then the space shuttle wouldn't stay circling the earth - it would shoot off into space.
So, I think they are either geek-wannabees, or else I've got a Phd in Geek.

Wednesday, February 15, 2012

Banjo's Valentine's Day

Valentine's Day 2012


My wife and I decided to do something a little bit different for Valentine's Day this year (2012)...we decided to go out to a nice place for lunch, followed by an excursion to a local pub.

First, Lunch
We wanted to go somewhere that would have wonderful food, be reasonably priced, serve wine, cater to adults, not be too loud, and not be far away.  Sip Wine, on the west side of Alpharetta, immediately came to mind!

As we walked in, a young couple came out, with the man carrying a dozen red roses.  That looked like others had a similar idea!  And, it was nice to see this.

We chose from their special menu, which provided you with a selection of Tapas to choose from; choose any 2 for $10.00.  Wow!  That sounded really great!

I chose my two: 1st - Italian Sausage and Wild Mushroom Risotto,  and 2nd - a braised beef (I've gone to their website to look at the menu, but their currently displayed menu doesn't show this item, so I can't be more specific).  I also added, from their main menu, an Apple Salad.

My wife also chose two:  1st - Southern Fried Chicken Livers, and 2nd - stuffed spring rolls (again, this is not on the current website menu, so can't be too specific).

All items were wonderful!  Full of flavor.  We each shared a tasting from each of our respective tapas items.  I think I liked my wife's better!

For wine, we chose Roth Sonoma Coast Pinot Noir 2009.  We enjoyed this very much.

An excellent choice for our lunch!

After lunch, we proceeded to Roswell, to a pub we enjoy.

AFTERNOON SOCIAL
We left Sip and headed south to the historic district of Roswell, and went into Salt Factory Pub.  We were behind the lunch crowd getting there, so we had good choices for tables.  We generally enjoy the bar, as it gives us a chance to interact with the bar tender (a really nice woman) and local patrons.

We choose to have some wine as our beverage (Aquinas, Napa Valley), along with my wife's appetizer order of Liverwurst.

We spent several hours here, enjoying ourselves, before returning home and watching a movie (a chick flick - Hope Floats).  And, believe it or not, a little dancing!

IN SUMMARY
We both enjoyed this so much, we think it will be our model for Valentine's Day for future years!

Monday, February 13, 2012

Banjo's Sous Vide Controller - Arduino Thermocouple Controller

Arduino Sous Vide Controller!

This is part 3 of a 3 part project
             (Part 1 is a Timer Controller)

Note: the software limits you to a minimum food-safety temperatuer setpoint of 130 dF.  





This project makes use of a previous project, the Arduino Thermocouple Controlled Thermostat for the ability to precisely detect and control the temperature of the water-bath, along with a device - power switch tail - from Adafruit.  This power switch tail needs 5 vdc to drive it.




So I've changed the wiring from the pure NO contacts on the timer and thermocouple controller projects (see previous posts), to have a switched 5 vdc across the contacts.  So, as wired, the 5 vdc comes from the Arduino 5 vdc pin, then through the NO contacts, then out to the power switch tail, and then back to the Arduino gnd pin.  Per the power switch tail, it needs 5 vdc at 40 ma, so that's the reason I'm still using the relay I used in the thermocouple controller.



This version of the project does not incorporate a display, in order to keep the project costs and simplicity down.  However, the temperature setpoint will be able to be established using a DVM, or analog panel voltmeter.  I used a (currently listed at $5.00) 5vdc panel meter from SparkFun.   This works great, as I assigned it 0 - 5 volts to correspond to 100 - 150 dF, which the project can generate using a PWM pin (I used pin 11 in my software).  That's 10 degrees F per volt, or 1 dF per 1/10th volt, which is easy to read on the little meter.  I mounted this on my cigar box (see picture).  Note: with a simple software change, I can use this same device for the gas heater thermocouple temperature controller, and assign it a range of 0 - 5 vdc for 70 - 75 dF, then each volt equals 1 dF.






Some useful links:



Software Changes
Note the software changes from that posted for the thermocouple controller.  Not significant, but I added:

  • PWM output on pin 11, in order to be able to view the setpoint without having an expensive LCD display or a laptop connected.  You can use a DVM, or as I did, mount a 5 vdc panel meter on the face of your project box.
  • Software generate the analog voltage values.
  • Changed software to reflect use of 'const'.
  • Gave predefined values for Sous Vide vs thermostat values.
I'm going to go ahead and post this, along with the software.  I'll update a Fritzing layout later and add back.


SOFTWARE LISTING



//Author: Banjo 1/29/12
// Rev 1.0 added ability to use dvm to monitor setpoint via pwm output
//     1.1 refactored some duplications in turnHeaterOn() and turnHeaterOff()
//     1.2 change setpoint so it can't be less than 130 dF for food safety reasons.  Added LOWEST_TEMPETURE_DISPLAY vs LOWEST_TEMPERATURE_SETPOINT
//
//  HARDWARE: 
//  - This program makes use of the Thermocouple breakout board from www.adafruit.com MAX6675, inserted into 
//    Arduino UNO Digital pin positions 2 - 6.  Correctly inserted, this sheild will lay over the board, not project outside of it.
//  SOFTWARE: This program makes use of a thermocouple library from: // www.ladyada.net/learn/sensors/thermocouple.html
//    you will also need to install the library and rename it, from that same page.
//  - displays 
//       RED - heater is on
//       GREEN - heater is off
//       Alternating RED and GREEN - pilot doesn't appear to be lit.
//       5VDC volt meter.  This corresponds to temperature, with zero volts equal to your base temperature, and 5 volts equal to your base temperature plus range.
//
//For use:
//  - Wire manual heater switch to NO contacts on relay.  This relay is controlled by this Arduino.
//    These contacts are not polarized, so it doesn't matter which contact goes to which
//    leg of the switch.
//  - Wire Type K Thermocouple to screws on Thermocouple breakout board, observing correct polarity.
//      (Note: if you get the thermocouple poloratiy wrong, it won't hurt anything, it just won't work right.)
//
//for testing:
//  - If you want LED for HEATER ON, then add LED at digital pin 9, then through resistor to ground
//  - if you want LED for HEATER OFF, then add LED at digital pin 10, then through resistor to ground 
//  - add jumper from 5 vdc to pin A1 - this is the pilot light permissive
//  - add jumper from pin A0 to 3.3 vdc ref; this will supply about 66% ratio
//  - change MAX_CYCLE_TIME to something short, like 10 ONE_SECOND
//  - change IGNORE_MINIMUM to something short, like 2 ONE_SECOND
//  - wire dvm across gnd and pin 11 for PWM setpoint value in volts
//  - remember to change back!
//
//some handy time constants
const unsigned long ONE_SECOND = 1000;
const unsigned long HALF_SECOND = 500;
const unsigned long QUARTER_SECOND = 250;
const unsigned long ONE_MINUTE = ONE_SECOND * 60;
const int MAX_ADC_COUNTS = 1024; //ADC
const unsigned long SENSOR_READ_DELAY_TIME = QUARTER_SECOND;


//heater controlled by temperature
#include
//pin assignments 
const int HEATER_OUTPUT_PIN = 8;              //This controls the onboard relay that controls the heater gas valve
const int STATE_LED_PIN = 9;                  //LED if using an external state LED
const int OFF_LED_PIN = 10;                   //LED heater off 
const int REF_VOLTAGE_SETPOINT = 11;          //pwm output pin.  Connecting DVM here will give you corresponding temperature setpoint, added to LOWEST_TEMPERATURE_DISPLAY.  
const int UNO_LED_PIN = 13;                   //LED on the uno board

const int HEATER_RATIO_INPUT_PIN = A0;        //this is the onboard potetiometer that controls the ratio of on to off
const int HEATER_INPUT_PERMISSIVE_PIN = A1;   // Read this pin for input permissive from thermocouple pile.  


//     E.g., if LOWEST_TEMPERATURE_DISPLAY = 70, then 1 vdc output would be 71 deg; 2 volts = 72, ...5 volts = 75, which would be maximum value.
//for adafruit thermocouple amp
const int thermoDO = 4;  //corresponds to adafruit thermocouple amp shield
const int thermoCS = 5;  //corresponds to adafruit thermocouple amp shield
const int thermoCLK = 6; //corresponds to adafruit thermocouple amp shield
const int vccPin = 3;  //corresponds to adafruit thermocouple amp shield used via set high
const int gndPin = 2;  //corresponds to adafruit thermocouple amp sheild coupled to ground via set low
MAX6675 thermocouple(thermoCLK, thermoCS, thermoDO);  //function declaration
void docommonCalcs(double *pTemperature, double *pPreviousTemperature, float *pSetpointRatio, float *pSetpointValue);

//stuff for calcs
//
//**** normal usage ****
//const float LOWEST_TEMPERATURE_SETPOINT = 70.0;  //This corresponds to 0% on temperature setpoint adjustment knob.  It is the lowest temperature setpoint.
//const float LOWEST_TEMPERATURE_DISPLAY = 70.0;   //When setpoint minimum (food safety) and display are the same, set both to be same
//const float MAX_CONTROL_RANGE_dF = 5.0;          //useful to stay with 5, as it is also the pwm output voltage capability for use with dvm for setpoint.  see showSetpoint() for insight 
//**** end of normal usage ****
//
//**** for Sous Vide cooking replace with following values ****
const float LOWEST_TEMPERATURE_SETPOINT = 130.0;
const float LOWEST_TEMPERATURE_DISPLAY = 100.0;
const float MAX_CONTROL_RANGE_dF = 50.0;
//****  these values will give you a base temperature of 100.0dF, and with a range of 50.0 dF, you will have a total control temperature range of 100.0 dF to 150.0 dF.
//****  at these values, the 5 volt meter will represent 50 dF.  So each 1/10 volt will equal 1 dF.
//****  e.g., a value of 1 volt  would equate to 10 dF, plus the base of 100.0 dF, for a total of 110 dF.
//****        a value of 2 volts would equate to 20 dF plus base of 100.0 for a total of 120.0 dF
//****        a value of 3.1 volts would equate to 31dF pluse base of 100.0 for a total of 131 dF.
//****
//****  Note for Sous vide cooking: I would not trust this device totally for setting by a volt meter.  I would 
//****       set the temperature, then verify the water bath after it has had time to stabalize by using an accurate thermometer.
//****       I would also extend considerably the amont of time needed for pasteurazation - I would not use minimum time values
//****       so I could be assured that the food has been pasteurized.  Please consult 
//****       Doublas E. Baldwin's excellant book "Sous Vide for the home cook" for information, and his web page at
//****       http://www.douglasbaldwin.com/index.html for additional safety information.
//****  Note for Souse Vide cooking equipment:  I would suggest using a crock-pot, placing it on low temperature if it is capable of 
//****       water up to 160 dF when full, or on high otherwise.  The lower setting is desirable, if usable with your cooker, in order to
//****       lesson the amount of temperature swings from heat on to heat off.  Also, use a device to control the crock pot that is capable 
//****       of safely handling the current and voltages involved.  I use something like the following device from Adafruit:
//****       https://www.adafruit.com/products/268 power switch tail, which is rated at 15 amps at 120 vac.  
//**** end of Sous vide cooking values

//const float 
float DEADBAND_dF = 0.5;
float CALIBRATION = -3.25; //Testing boiling water adjusted for altitude (-1 deg for every 500 ft).  If you don't test, then set to 0.0 
const int TEMPERATURE_AVERAGING_ARRAY_SIZE = 10 * (ONE_SECOND/SENSOR_READ_DELAY_TIME);  //Samples to be averaged.  10 SECONDS WORTH OF AVERAGES
float temperatureReadings[TEMPERATURE_AVERAGING_ARRAY_SIZE];
boolean firstTime = true;


int HEATER_ON = HIGH;  //used because may use sink current at some point
int HEATER_OFF = LOW;  //used because may use sink current at some point
int ALARM_COUNTS = 250;  //blink rate

float lowest_temperature_setpoint_ratio = 0.0; //global

void setup() {                
  Serial.begin(9600);
  // initialize the digital pin as an output.
  // Pin 13 has an LED connected on most Arduino boards:
  pinMode(UNO_LED_PIN, OUTPUT);
  pinMode(STATE_LED_PIN, OUTPUT);
  pinMode(OFF_LED_PIN, OUTPUT);
  pinMode(HEATER_OUTPUT_PIN, OUTPUT);
  pinMode(REF_VOLTAGE_SETPOINT, OUTPUT);  //Will provide PWM output voltage

  pinMode(vccPin, OUTPUT); digitalWrite(vccPin, HIGH); //voltage source
  pinMode(gndPin, OUTPUT); digitalWrite(gndPin, LOW); //current sink

  //This calculation needs to be done only once
  lowest_temperature_setpoint_ratio = (LOWEST_TEMPERATURE_SETPOINT - LOWEST_TEMPERATURE_DISPLAY) / MAX_CONTROL_RANGE_dF;
  
  // wait for MAX chip to stabilize
  delay(500);

  Serial.println("setup()...Entry & Exit"); 

}

void loop() {
  Serial.println("loop()...Entry"); 
  //digitalWrite(ONBOARD_LED_PIN, HIGH);   // set the LED on
  turnHeaterOff();
  turnHeaterOn();
  Serial.println("loop()...Exit"); 
}

float getSetpointRatio() {
  //rather than counts of 0 - 1023 (range of the onboard ADC), return the 0% - 100% of range
  float ratio = 0.0;
  int sensorValue = 0;

  sensorValue = analogRead(HEATER_RATIO_INPUT_PIN);
  ratio = float(sensorValue) / float(MAX_ADC_COUNTS);

//  Serial.print("getSetpointRatio()....sensorValue: "); Serial.println(sensorValue); 
//  Serial.print("getSetpointRatio()....ratio: "); Serial.println(ratio);
  return ratio;
}


//-------------------------------------------------------------------------------------------------------------------------------------

void doCommonCalcs(double *pTemperature, double *pPreviousTemperature, float *pSetpointRatio, float *pSetpointValue) {
  //note this uses pass-by-reference not pass-by-value in order to be able to change these values
  //warning - this method will change values on the passed references
    *pPreviousTemperature = *pTemperature; //first time will be 0.0;
    *pTemperature = thermocouple.readFarenheit();
    *pTemperature += CALIBRATION; //Determine this by testing with boiling water for your probe.  
    *pTemperature = getTemperatureAverage(*pTemperature);
    *pSetpointRatio = getSetpointRatio();
    if(*pSetpointRatio < lowest_temperature_setpoint_ratio) {
      *pSetpointRatio = lowest_temperature_setpoint_ratio;
      Serial.print("doCommonCalcs() ***!!!*** setpoint override to minimum food-safe value of: "); Serial.println(LOWEST_TEMPERATURE_SETPOINT);       
    }
    showSetpoint(*pSetpointRatio);  //pwm
    *pSetpointValue = LOWEST_TEMPERATURE_DISPLAY + (MAX_CONTROL_RANGE_dF * *pSetpointRatio);
    Serial.print("doCommonCalcs()...setpointValue: "); Serial.println(*pSetpointValue);
    Serial.print("doCommonCalcs()...Temperature avg: "); Serial.println(*pTemperature);
    Serial.print("doCommonCalcs()...previousTemperature: ");Serial.print(*pPreviousTemperature); //useful to see how noise the data is
      Serial.print(", difference: "); Serial.println(*pTemperature - *pPreviousTemperature);
  
}


void turnHeaterOn () {  //needs to be refactored with turnHeaterOff
  double temperature = 0.0;
  double previousTemperature = 0.0;
  float setpointRatio = 0.0;
  float setpointValue = 0.0;  //dF
  static boolean initNeeded = true;  //needed first time we start up
  
  
  Serial.println();
  Serial.println("turnHeaterOn()...temperature controlled...entry.......................");
  
  do {  
    doCommonCalcs(&temperature, &previousTemperature, &setpointRatio, &setpointValue);
    heaterOn();
    delay(SENSOR_READ_DELAY_TIME);
  }  while(temperature < (setpointValue+DEADBAND_dF));
  Serial.println("turnHeaterOn()...exit");
}

void turnHeaterOff() {  //needs to be refactored with turnHeaterOn
  double temperature = 0.0;
  double previousTemperature = 0.0;
  float setpointRatio = 0.0;
  float setpointValue = 0.0;  //dF

  Serial.println();
  Serial.println("turnHeaterOff()...temperature controlled...entry......................");

  do {
    doCommonCalcs(&temperature, &previousTemperature, &setpointRatio, &setpointValue);
    heaterOff();
    delay(SENSOR_READ_DELAY_TIME);
  }  while(temperature > (setpointValue-DEADBAND_dF));
  Serial.println("turnHeaterOff()...exit");
}

void showSetpoint(float setpointRatio) {
  //write the voltage to pwm pin.
  //voltage out is defined as 0 - 5 vdc.  0 volts = LOWEST_TEMPERATURE_DISPLAY, and 5 volts = LOWEST_TEMPERATURE_DISPLAY + MAX_CONTROL_RANGE_dF
  //so, if LOWEST_TEMPERATURE_DISPLAY is defined as 70 dF, and MAX_CONTROL_RANGE_dF is defined as 5 dF, then
  // 70 dF would equal 0 Volt, 71 dF would equal 1 Volt, 72 dF would equal 2 volts, 73 would equal 3 volts, 74 would equal 4 volts, and 75 would equal 5 volts
  float voltageOut = 0.0;
  int pwmValue = 0;
  
//  setpointRatio = 1.0 - setpointRatio; //depending on which way you wire your pot, you may have to have 1.0 - setpointRatio or just setpointRatio.
  pwmValue = 255.0 * setpointRatio;
  voltageOut = 5.0 * setpointRatio;  //only valid if you keep the MAX_CONTROL_RANGE_dF to 5!
  analogWrite(REF_VOLTAGE_SETPOINT, pwmValue);
  Serial.print("showSetpoint()...pwmValue: "); Serial.println(pwmValue);
  Serial.print("showSetpoint()...voltage: "); Serial.println(voltageOut);
}

float getTemperatureAverage(float readValue) {
  static int element = 0;
  float oldReadingValue = 0.0;
  float newAverageValue = 0.0;
  static float oldAverageValue = 0.0;
  
//  Serial.println("getTemperatureAverage()...entry ");

  if(firstTime) {  //first time, there are no previous readings, so populate the whole thing with current reading
//    Serial.println("getTemperatureAverage()...First Time.");
    firstTime = false;
    for(int i=0; i<TEMPERATURE_AVERAGING_ARRAY_SIZE; i++) {
      temperatureReadings[i] = readValue;  //do the population with current reading
    }
    oldAverageValue = readValue;  //on first time, all are same, so this is also the average
    return oldAverageValue;  //bailout shortcut on first time
  }
  
  //doing it this way avoids having to loop through whole array and then taking the average
  //replace the oldest reading with the newest reading, then offset the average value
  //this is a circular queue
  oldReadingValue = temperatureReadings[element];
  temperatureReadings[element] = readValue;  //replace old value with new value
  newAverageValue = (readValue - oldReadingValue)/ float(TEMPERATURE_AVERAGING_ARRAY_SIZE);  //get averaged change between old value and new value
  oldAverageValue += newAverageValue; //change average by average increase or decrease
//  Serial.print("getTemperatureAverage()...average temperature: "); Serial.println(averageTemperature);
  
  element++;
  if(element==TEMPERATURE_AVERAGING_ARRAY_SIZE)  //this makes it a circular queue
    element = 0;
//  Serial.println("getTemperatureAverage()...exit ");

  return oldAverageValue;  
}


//------------------------------------------------------------------------------------------------------------------------------------------------------

void heaterOn() {
  Serial.println("heaterOn() ... entry.");
  digitalWrite(HEATER_OUTPUT_PIN, HEATER_ON);   // sink current
  digitalWrite(OFF_LED_PIN, LOW);
  digitalWrite(UNO_LED_PIN, HIGH);   // set the LED on
  digitalWrite(STATE_LED_PIN, HIGH);
  Serial.println("heaterOn() ... exit.");
}

void heaterOff() {
  Serial.println("heaterOff() ... entry.");
  digitalWrite(HEATER_OUTPUT_PIN, HEATER_OFF);     
  digitalWrite(UNO_LED_PIN, LOW);   // set the LED on
  digitalWrite(STATE_LED_PIN, LOW);
  digitalWrite(OFF_LED_PIN, HIGH);
  Serial.println("heaterOff() ... exit.");
}  

void alarm(int sensorValueCounts) {  //needs to be refactored to use heaterOff()
  //blink both on and off LEDs
  Serial.println("alarm()....Entry...will not exit!");

  //TURN OFF!
  digitalWrite(HEATER_OUTPUT_PIN, HEATER_OFF);   
  digitalWrite(OFF_LED_PIN, LOW);
  
  Serial.print("alarm()....sensor low. sensorValueCounts: "); Serial.println(sensorValueCounts);
  while(1) {
    //keep turning heater off!
    digitalWrite(HEATER_OUTPUT_PIN, HEATER_OFF);
    
    //show it by alternating blinking both LEDs
    digitalWrite(UNO_LED_PIN, HIGH);
    digitalWrite(STATE_LED_PIN, HIGH);
    digitalWrite(OFF_LED_PIN, LOW);
    delay(ALARM_COUNTS);

    digitalWrite(UNO_LED_PIN,LOW);
    digitalWrite(STATE_LED_PIN, LOW);
    digitalWrite(OFF_LED_PIN, HIGH);
    delay(ALARM_COUNTS);
  }
}

Wednesday, February 8, 2012

Banjo's Arduino Thermocouple Controlled Thermostat


Banjo's Arduino Thermocouple Controlled Thermostat
(Note: scroll down for circuit diagram and software listing)
Part 2 of 3 parts

Thermostat controlled by Arduino using
Thermocouple

 
Interior view


Fritzing view of Arduino Thermostat Controller using Thermocouple 


In a previous post, I presented an Arduino Timer Circuit, which was my original attempt at controlling a gas heater on my porch.  I originally thought this might work pretty well, as my porch is somewhat drafty, and I thought a time-based control might work better than a temperature-based control.  That didn't work out in practice, although I am seeing some truth to that assumption when the wind picks up!

Hardware Change
In this circuit, I have changed some of the hardware by adding a thermocouple breakout-board from AdaFruit.  This device makes use of a Maxim MAX6675, which provides you with a lot benefits, such as cold-junction thermocouple adjustments.  It also interfaces well from a software standpoint, making your the job of determining temperature much easier.

Changing from a timer-based controller to a temperature-based controller meant I would have to make a hardware change, as well as software changes.  I mentioned the hardware change above; now for insight into the software change.

Software Change
Since this is temperature-based, I no longer need to calculate time and time-changes introduced through manipulation of the front knob (potentiometer).  Instead, the front knob (potentiometer) will now change the desired temperature, or setpoint, of a thermostat.

So the first software change will be to remove the time-based calculations, and substitute in the temperature-based changes.  The first choice is what temperature span to give to the knob, from full counterclockwise to full clockwise - how many degrees of change are under the control of the knob?  I elected to give the full range a change of 4 degrees for my porch - your needs may differ.  The smaller the total change, the larger the movement of the knob.  You could in fact, make the full range be as small as 1 dF, (or as small as you desire), but the smaller you make it, the more susceptible to circuit noise, and temperature changes due to wind-drafts.  After some experimentation for my porch, I elected for a 4 dF full range change.

Knowing that I have provided a 4 dF full range temperature change, the next choice is - what is the bottom temperature?  I decided that I wouldn't want to have a setpoint below 70 dF, so I made this value my base, or bottom, value.  In my case, therefore, I have a bottom temperature setpoint capability of 70 dF, and with a 4 dF full range, my maximum setpoint will be 74 dF.

A final decision regards the deadband.  Deadband represents the range, in temperature, where no state-change is made by a thermostat.  In other words, if the thermostat is in the heating-state, then it will continue to demand heat until it reaches the setpoint plus the deadband value.  If the thermostat is in the cooling-state (or for a heater, coasting or het-not-on), then it will continue to demand no-heat until it reaches the setpoint minus the deadband value.

My first thoughts on deadband would be that I would make it a small as possible.  Well, I realized this wasn't a good idea.   Let's assume I have a setpoint of 72 dF, and the thermostat is in a heating-state.  Let's further assume I have a setpoint of 0.1 dF.  This means that the thermostat will continue to heat until the setpoint (72 dF) is reached, plus the deadband (0.1), or until 72.1 dF is reached.  Once that 72.1 dF is reached, the thermostat will change states to no-heat.  At that point, the temperature in the porch will fall, until it reaches the setpoint value (72 dF) minus the deadband value (0.1), or 69.9 dF.  If you were running a compressor style heatpump, then you may burn up the compressor or motor, as almost every gust of wind could cause the thermostat to change states.  So having a realistic value for the deadband, instead of the smallest value you can sense, is your goal.  What my goal is, for my porch, is to not feel hot when the thermostat is in the heating-state and reaches the turn-off point (setpoint plus deadband), and not feel cold when it reaches the turn-on point (setpoint minus deadband).  Also, note that the temperature, when falling, will continue to fall for a while after the thermostat changes state from no-heat to on-heat, before the heated air has enough time to circulate; the same idea applies to turning changing to no-heat too.  After some experimentation, I found that, for my porch, the deadband needs to be 0.25 dF.  This will vary for you, based on the size of the room, the size of your heater, the amount of draft, the heat loss rate of the room, your willingness to tolerate temperature swings......all of which you can adjust by making a simple software change in the program (below).


So, these are really the fine points of turning the timer-based circuit into a temperature-based thermostat.

  • change to the hardware to add a thermocouple breakout board.
  • change to the software to read the thermocouple temperature value
  • change to the software to add a deadband
  • change to the software to convert the knob adjustment from time to temperature 
  • change to the software to add a base temperature



Software Listing


//Author: Banjo 1/29/12
//
//  HARDWARE: 
//  - This program makes use of the Thermocouple breakout board from www.adafruit.com MAX6675, inserted into 
//    Arduino UNO Digital pin positions 2 - 6.  Correctly inserted, this sheild will lay over the board, not project outside of it.
//  SOFTWARE: This program makes use of a thermocouple library from: // www.ladyada.net/learn/sensors/thermocouple.html
//    you will also need to install the library and rename it, from that same page.
//  - displays 
//       RED - heater is on
//       GREEN - heater is off
//       Alternating RED and GREEN - pilot doesn't appear to be lit.
//
//For use:
//  - Wire manual heater switch to NO contacts on relay.  This relay is controlled by this Arduino.
//    These contacts are not polarized, so it doesn't matter which contact goes to which
//    leg of the switch.
//  - Wire Type K Thermocouple to screws on Thermocouple breakout board, observing correct polarity.
//      (Note: if you get the thermocouple poloratiy wrong, it won't hurt anything, it just won't work right.)
//
//for testing:
//  - If you want LED for HEATER ON, then add LED at digital pin 9, then through resistor to ground
//  - if you want LED for HEATER OFF, then add LED at digital pin 10, then through resistor to ground 
//  - add jumper from 5 vdc to pin A1 - this is the pilot light permissive
//  - add jumper from pin A0 to 3.3 vdc ref; this will supply about 66% ratio
//  - change MAX_CYCLE_TIME to something short, like 10 ONE_SECOND
//  - change IGNORE_MINIMUM to something short, like 2 ONE_SECOND
//  - remember to change back!
//
//some handy time constants
unsigned long ONE_SECOND = 1000;
unsigned long ONE_MINUTE = ONE_SECOND * 60;
int MAX_ADC_COUNTS = 1024; //ADC
unsigned long SENSOR_READ_DELAY_TIME = ONE_SECOND;


//heater controlled by temperature
#include max6675.h
//stuff for thermocouple
int thermoDO = 4;
int thermoCS = 5;
int thermoCLK = 6;
MAX6675 thermocouple(thermoCLK, thermoCS, thermoDO);
int vccPin = 3;
int gndPin = 2;
//stuff for calcs
float MAX_CONTROL_RANGE_dF = 5.0;
float DEADBAND_dF = 0.5;
float CALIBRATION = -3.25; //Testing boiling water adjusted for altitude (-1 deg for every 500 ft).  If you don't test, then set to 0.0 
float LOWEST_TEMPERATURE_SETPOINT = 70.0;  //This corresponds to 0% on temperature setpoint adjustment knob
const int TEMPERATURE_AVERAGING_ARRAY_SIZE = 10;  //Samples to be averaged.  Eg., this value set to 10 will average 10 samples
float temperatureReadings[TEMPERATURE_AVERAGING_ARRAY_SIZE];
boolean firstTime = true;

//pin assignments common to all heater contoller types
int UNO_LED_PIN = 13;  //LED on the uno board
int STATE_LED_PIN = 9;  //LED if using an external state LED
int OFF_LED_PIN = 10;   //LED heater off 
int HEATER_OUTPUT_PIN = 8;        //This controls the onboard relay that controls the heater gas valve
int HEATER_RATIO_INPUT_PIN = A0;  //this is the onboard potetiometer that controls the ratio of on to off
int HEATER_INPUT_PERMISSIVE_PIN = A1;  // Read this pin for input permissive from thermocouple pile.  

int HEATER_ON = HIGH;  //used because may use sink current at some point
int HEATER_OFF = LOW;  //used because may use sink current at some point
int ALARM_COUNTS = 250;  //blink rate

void setup() {                
  Serial.begin(9600);
  // initialize the digital pin as an output.
  // Pin 13 has an LED connected on most Arduino boards:
  pinMode(UNO_LED_PIN, OUTPUT);
  pinMode(STATE_LED_PIN, OUTPUT);
  pinMode(OFF_LED_PIN, OUTPUT);
  pinMode(HEATER_OUTPUT_PIN, OUTPUT);

  pinMode(vccPin, OUTPUT); digitalWrite(vccPin, HIGH);
  pinMode(gndPin, OUTPUT); digitalWrite(gndPin, LOW);
  // wait for MAX chip to stabilize
  delay(500);

  Serial.println("setup()...Entry & Exit"); 

}

void loop() {
  Serial.println("loop()...Entry"); 
  //digitalWrite(ONBOARD_LED_PIN, HIGH);   // set the LED on
  turnHeaterOff();
  turnHeaterOn();
  Serial.println("loop()...Exit"); 
}

float getSetpointRatio() {
  //rather than counts of 0 - 1023 (range of the onboard ADC), return the 0% - 100% of range
  float ratio = 0.0;
  int sensorValue = 0;

  sensorValue = analogRead(HEATER_RATIO_INPUT_PIN);
  ratio = float(sensorValue) / float(MAX_ADC_COUNTS);

//  Serial.print("getSetpointRatio()....sensorValue: "); Serial.println(sensorValue); 
//  Serial.print("getSetpointRatio()....ratio: "); Serial.println(ratio);
  return ratio;
}


//-------------------------------------------------------------------------------------------------------------------------------------

void turnHeaterOn () {  //needs to be refactored with turnHeaterOff
  double temperature = 0.0;
  double previousTemperature = 0.0;
  float setpointRatio = 0.0;
  float setpointValue = 0.0;  //dF
  static boolean initNeeded = true;  //needed first time we start up
  
  Serial.println();
  Serial.println("turnHeaterOn()...temperature controlled...entry.......................");
  
  do {  
    previousTemperature = temperature; //first time will be 0.0;
    temperature = thermocouple.readFarenheit();
    temperature += CALIBRATION; //Determine this by testing with boiling water for your probe.  
    temperature = getTemperatureAverage(temperature);
    setpointRatio = getSetpointRatio();
    setpointValue = LOWEST_TEMPERATURE_SETPOINT + (MAX_CONTROL_RANGE_dF * setpointRatio);
    Serial.print("turnHeaterOn()...setpointValue: "); Serial.println(setpointValue);
    Serial.print("turnHeaterOn()...Temperature avg: "); Serial.println(temperature);
    Serial.print("turnHeaterOn()...previousTemperature: ");Serial.print(previousTemperature); //useful to see how noise the data is
      Serial.print(", difference: "); Serial.println(temperature - previousTemperature);
    heaterOn();
    delay(SENSOR_READ_DELAY_TIME);
  }  while(temperature < setpointValue+DEADBAND_dF);
  Serial.println("turnHeaterOn()...exit");
}

void turnHeaterOff() {  //needs to be refactored with turnHeaterOn
  double temperature = 0.0;
  double previousTemperature = 0.0;
  float setpointRatio = 0.0;
  float setpointValue = 0.0;  //dF

  Serial.println();
  Serial.println("turnHeaterOff()...temperature controlled...entry......................");

  do {
    previousTemperature = temperature; //will be 0 for first time
    temperature = thermocouple.readFarenheit();
    temperature += CALIBRATION; //Determine this by testing with boiling water for your probe.  
    temperature = getTemperatureAverage(temperature);
    setpointRatio = getSetpointRatio();
    setpointValue = LOWEST_TEMPERATURE_SETPOINT + (MAX_CONTROL_RANGE_dF * setpointRatio);
    Serial.print("turnHeaterOff()...setpointValue: "); Serial.println(setpointValue);
    Serial.print("turnHeaterOff()...Temperature avg: "); Serial.println(temperature);
    Serial.print("turnHeaterOff()...previousTemperature: ");Serial.print(previousTemperature); //useful to see how noise the data is
      Serial.print(", difference: "); Serial.println(temperature - previousTemperature);
    heaterOff();
    delay(SENSOR_READ_DELAY_TIME);
  }  while(temperature > setpointValue-DEADBAND_dF);
  Serial.println("turnHeaterOff()...exit");
}

float getTemperatureAverage(float readValue) {
  static int element = 0;
  float oldReadingValue = 0.0;
  float newAverageValue = 0.0;
  static float oldAverageValue = 0.0;
  
//  Serial.println("getTemperatureAverage()...entry ");

  if(firstTime) {  //first time, there are no previous readings, so populate the whole thing with current reading
//    Serial.println("getTemperatureAverage()...First Time.");
    firstTime = false;
    for(int i=0;i<TEMPERATURE_AVERAGING_ARRAY_SIZE; i++) {
      temperatureReadings[i] = readValue;  //do the population with current reading
    }
    oldAverageValue = readValue;  //on first time, all are same, so this is also the average
  }
  
  //doing it this way avoids having to loop through whole array and then taking the average
  //replace the oldest reading with the newest reading, then offset the average value
  //this is a circular queue
  oldReadingValue = temperatureReadings[element];
  temperatureReadings[element] = readValue;  //replace old value with new value
  newAverageValue = (readValue - oldReadingValue)/ float(TEMPERATURE_AVERAGING_ARRAY_SIZE);  //get averaged change between old value and new value
  oldAverageValue += newAverageValue; //change average by average increase or decrease
//  Serial.print("getTemperatureAverage()...average temperature: "); Serial.println(averageTemperature);
  
  element++;
  if(element==TEMPERATURE_AVERAGING_ARRAY_SIZE)  //this makes it a circular queue
    element = 0;
//  Serial.println("getTemperatureAverage()...exit ");

  return oldAverageValue;  
}


//------------------------------------------------------------------------------------------------------------------------------------------------------

void heaterOn() {
  Serial.println("heaterOn() ... entry.");
  digitalWrite(HEATER_OUTPUT_PIN, HEATER_ON);   // sink current
  digitalWrite(OFF_LED_PIN, LOW);
  digitalWrite(UNO_LED_PIN, HIGH);   // set the LED on
  digitalWrite(STATE_LED_PIN, HIGH);
  Serial.println("heaterOn() ... exit.");
}

void heaterOff() {
  Serial.println("heaterOff() ... entry.");
  digitalWrite(HEATER_OUTPUT_PIN, HEATER_OFF);     
  digitalWrite(UNO_LED_PIN, LOW);   // set the LED on
  digitalWrite(STATE_LED_PIN, LOW);
  digitalWrite(OFF_LED_PIN, HIGH);
  Serial.println("heaterOff() ... exit.");
}  

void alarm(int sensorValueCounts) {  //needs to be refactored to use heaterOff()
  //blink both on and off LEDs
  Serial.println("alarm()....Entry...will not exit!");

  //TURN OFF!
  digitalWrite(HEATER_OUTPUT_PIN, HEATER_OFF);   
  digitalWrite(OFF_LED_PIN, LOW);
  
  Serial.print("alarm()....sensor low. sensorValueCounts: "); Serial.println(sensorValueCounts);
  while(1) {
    //keep turning heater off!
    digitalWrite(HEATER_OUTPUT_PIN, HEATER_OFF);
    
    //show it by alternating blinking both LEDs
    digitalWrite(UNO_LED_PIN, HIGH);
    digitalWrite(STATE_LED_PIN, HIGH);
    digitalWrite(OFF_LED_PIN, LOW);
    delay(ALARM_COUNTS);

    digitalWrite(UNO_LED_PIN,LOW);
    digitalWrite(STATE_LED_PIN, LOW);
    digitalWrite(OFF_LED_PIN, HIGH);
    delay(ALARM_COUNTS);
  }
}