// Luke Miller July 2011 // This software is released "as is", with no warranty or support. It may not // even work, that is for you to determine. See the bottom of this // file for license information. // Written under Arduino-0022 and WiShield software v1.3.0 // for an Arduino Uno and the LinkSprite CuHead Wifi Shield // http://linksprite.com/product/showproduct.php?id=73&lang=en // See http://asynclabs.com/wiki/index.php?title=AsyncLabsWiki for the WiShield library // that is used to run the CuHead Wifi Shield. Follow the instructions there for the // initial setup of the Wifi shield. //Note that you must first alter the apps_conf.h file in the WiShield library folder //so that #define APP_WEBSERVER is commented out and #define APP_WISERVER is uncommented //You must then close and reopen the Arduino software so that the library is //recompiled and the WiServer.h header file loads properly. #include #include //***************************************************************** // initialize the Liquid Crystal library with the numbers of the interface pins LiquidCrystal lcd(3, 4, 5, 6, 7, 8); //(RS,EN,DB4,DB5,DB6,DB7) //***************************************************************** //*************************************************************************** //Wireless configuration #define WIRELESS_MODE_INFRA 1 #define WIRELESS_MODE_ADHOC 2 // Wireless configuration parameters ---------------------------------------- //Settings for network unsigned char local_ip[] = {192,168,1,103}; // IP address of WiShield unsigned char gateway_ip[] = {192,168,1,1}; // router or gateway IP address unsigned char subnet_mask[] = {255,255,255,0}; // subnet mask for the local network const prog_char ssid[] PROGMEM = {"Your SSID here"}; // max 32 bytes unsigned char security_type = 3; // 0 - open; 1 - WEP; 2 - WPA; 3 - WPA2 // WPA/WPA2 passphrase const prog_char security_passphrase[] PROGMEM = {"Your password here"}; // max 64 characters // WEP 128-bit keys. If using WEP security, you must convert your human-readable password to hex and // insert it in one of the lines below. // sample HEX keys prog_uchar wep_keys[] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Key 0 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Key 1 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Key 2 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Key 3 }; // setup the wireless mode // infrastructure - connect to AP // adhoc - connect to another WiFi device unsigned char wireless_mode = WIRELESS_MODE_INFRA; unsigned char ssid_len; unsigned char security_passphrase_len; //-------------------------------------------------------------------------- //Give the analog input pins more sensible names #define airTempPin 0 //analog 0 #define meatTemp1 1 //analog 1 #define meatTemp2 2 //analog 2 #define meatTemp3 3 //analog 3 #define button1 A4 //analog 4, used as digital in for button input #define ssrPin A5 //analog 5, used as digital out for SSR volatile int lowTemp = 180; //initial lowTemp limit for heating element control volatile int highTemp = 190; //initial highTemp limit for heating element control volatile boolean pwrState = false; //initial power on state volatile boolean runwifi = false; //user chooses whether to run wifi feature, default is false //Temperature conversion constants const float x3 = 0.000000535; //3rd order constant const float x2 = 0.0007; //2nd order constant const float x1 = 0.4849; //1st order constant const float inter = 12.61; //intercept for thermistor temperature conversion const int lowlim = 20; //lower temperature limit, values below this are probably due to a //disconnected temperature probe //----------------------------------------- //Setup array for holding temperatures const int chs = 4; // # of channels to input const int numReadings = 10; const int reps = 9; //number of temp readings to average per channel (count from zero) long tempInArray[numReadings][chs]; //create array to hold analog values int index = 0; //index value to iterate through in main loop long total[chs]; //array of totals, one value per channel float averages[chs]; //array of averages, one value per channel //------------------------------------------- //************************************************************************** //Stripped-down webpage to speed loading. Every additional character you send //to the webpage extends the loading time when the page is requested. boolean sendMyPage(char* URL) { //Check if requested URL matches ''/'' if (strcmp(URL, "/") == 0) { //Use print and println functions to write out webpage WiServer.print(""); WiServer.println("Run time: "); WiServer.println(float(float(millis()/1000)/60)); WiServer.println(" minutes"); WiServer.println("
"); WiServer.print("Air: "); if (int(averages[0]) > lowlim) { WiServer.print(int(averages[0])); } else { WiServer.print("NA "); //values below 20F are probably due to disconnected probe } WiServer.println("
"); WiServer.print("Meat 1: "); if (int(averages[1]) > lowlim) { WiServer.print(int(averages[1])); } else { WiServer.print("NA "); //values below 20F are probably due to disconnected probe } WiServer.println("
"); WiServer.print("Meat 2: "); if (int(averages[2]) > lowlim) { WiServer.print(int(averages[2])); } else { WiServer.print("NA "); //values below 20F are probably due to disconnected probe } WiServer.println("
"); WiServer.print("Meat 3: "); if (int(averages[2]) > lowlim) { WiServer.print(int(averages[3])); } else { WiServer.print("NA "); //values below 20F are probably due to disconnected probe } WiServer.print(""); return true; } //URL not found return false; } //end of sendMyPage function //************************************************************************** //************************************************************************** void setup() { analogReference(DEFAULT); //use default 5V reference pinMode(button1, INPUT); //button for selecting options pinMode(ssrPin, OUTPUT); //pin to toggle solid state relay (SSR) digitalWrite(ssrPin, LOW); //turn off power to SSR initially pwrState = false; // set up the LCD's number of columns and rows: lcd.begin(16,2); //initialize LCD output runwifi = runWiFi(); //call runWiFi button reading function lcd.clear(); if (runwifi) { lcd.home(); lcd.print("Associating with"); lcd.setCursor(0,1); lcd.print("WiFi router"); WiServer.init(sendMyPage); //initialize WiServer, connect to wireless router (may take 30 sec) // Print a message to the LCD. lcd.clear(); lcd.home(); lcd.print("WiFi IP:"); lcd.setCursor(0,1); //2nd row lcd.print(int(local_ip[0])); lcd.print("."); lcd.print(int(local_ip[1])); lcd.print("."); lcd.print(int(local_ip[2])); lcd.print("."); lcd.print(int(local_ip[3])); delay(4000); } lowTemp = lowTempFunc(); //call lowTempFunc sub-function and return value highTemp = highTempFunc(lowTemp); //call highTempFunc sub-function and return value //send lowTemp value to give a starting point //remind users once again what the IP is if WiFi is enabled if (runwifi) { // Print a message to the LCD. lcd.clear(); lcd.home(); lcd.print("WiFi IP:"); lcd.setCursor(0,1); //2nd row lcd.print(int(local_ip[0])); lcd.print("."); lcd.print(int(local_ip[1])); lcd.print("."); lcd.print(int(local_ip[2])); lcd.print("."); lcd.print(int(local_ip[3])); delay(2000); } // initialize analog temperature value array for (int channel = 0; channel < chs; channel++) { for (int i = 0; i < numReadings; i++) { tempInArray[i][channel] = 0; //initialize all values to 0 } } lcd.clear(); } //end of setup loop, move on to main loop() //**************************************************************************** //Main Loop void loop() { //************************************************************************** //Start by reading temperature values on each analog input //first remove the old value in this row of tempInArray from the running total for (int channel = 0; channel < chs; channel++) { total[channel] = total[channel] - tempInArray[index][channel]; } //read temperature on each channel airTempPin, meatTemp1, meatTemp2, meatTemp3 int channel = 0; analogRead(airTempPin); //take 1 reading to throw away delay(20); tempInArray[index][channel] = 1023 - analogRead(airTempPin); delay(20); channel = 1; analogRead(meatTemp1); delay(20); tempInArray[index][channel] = 1023 - analogRead(meatTemp1); delay(20); channel = 2; analogRead(meatTemp2); delay(20); tempInArray[index][channel] = 1023 - analogRead(meatTemp2); channel = 3; analogRead(meatTemp3); delay(20); tempInArray[index][channel] = 1023 - analogRead(meatTemp3); for (int channel = 0; channel < chs; channel++) { total[channel] = total[channel] + tempInArray[index][channel]; averages[channel] = total[channel] / numReadings; //calculate each average //convert average ADC value to temperature in Fahrenheit using pre-determined conversion equation averages[channel] = (x3 * pow(averages[channel],3)) - (x2 * pow(averages[channel],2)) + (x1 * averages[channel]) + inter; } index = index + 1; if (index >= numReadings) { index = 0; } //Regulate power to bbq by checking air temperature against //low and high temperature limits. if (averages[0] < lowTemp) { //if air temp is below lower limit digitalWrite(ssrPin, 1); //turn on solid state relay pwrState = true; } else if (averages[0] > highTemp) { digitalWrite(ssrPin, 0); //turn off solid state relay pwrState = false; } //Note that when the air temp is between lowTemp and highTemp nothing is done, //so as to let the air temperature heat or cool through the temperature window //before changing the state of the solid state relay. This provides some hystersis //to avoid rapid switching of the heating element. //********************************* //Output temperatures to LCD screen //The various delays in here seem to be necessary for keeping output //properly formatted when hooked up to the electrically noisy smoker lcd.home(); lcd.print("Air:"); delay(10); if (int(averages[0]) < lowlim) { lcd.print("NA "); } else if (int(averages[0]) > lowlim & int(averages[0]) < 100) { lcd.print(" "); //pad with a space lcd.print(int(averages[0])); } else { lcd.print(int(averages[0])); } lcd.print("F "); delay(10); /////////////////////////// lcd.setCursor(9,0); lcd.print("1:"); delay(10); if (int(averages[1]) < lowlim) { lcd.print("NA "); } else if (int(averages[1]) > lowlim & int(averages[1]) < 100) { lcd.print(" "); //pad with a space lcd.print(int(averages[1])); } else { lcd.print(int(averages[1])); } lcd.print("F"); delay(10); /////////////////////// lcd.setCursor(0,1); //start of 2nd row lcd.print(" 2:"); delay(10); if (int(averages[2]) < lowlim) { lcd.print("NA "); } else if (int(averages[2]) > lowlim & int(averages[2]) < 100) { lcd.print(" "); //pad with a space lcd.print(int(averages[2])); } else { lcd.print(int(averages[2])); } delay(10); /////////////////////// lcd.setCursor(7,1); delay(10); lcd.print("F 3:"); if (int(averages[3]) < lowlim) { lcd.print("NA "); } else if (int(averages[3]) > lowlim & int(averages[3]) < 100) { lcd.print(" "); //pad with a space lcd.print(int(averages[3])); } else { lcd.print(int(averages[3])); } delay(10); lcd.print("F"); delay(10); lcd.setCursor(15,1); if (pwrState) { //if pwrState is true lcd.print("x"); } else { lcd.print(" "); } delay(150); //******************************************************** //Run WiServer to handle any http requests for the webpage if (runwifi) { //Run WiServer if user set it up. WiServer.server_task(); delay(10); } } //end of main loop //********************************************************************************** //********************************************************************************** // lowTempFunc subroutine. This lets the user choose the low temperature limit for // the heating element of the bbq. This uses input from button1 and returns a value // 'lowTemp' to the setup loop. int lowTempFunc() { //lowTempFunc will return an integer when called int buttonValue1; int buttonValue2; int buttonState; long startTime = millis(); //get starting time for this loop lcd.clear(); lcd.print("Choose low temp"); delay(500); lcd.setCursor(0,1); lcd.print(lowTemp); lcd.setCursor(3,1); lcd.print("F"); buttonState = digitalRead(button1); //get current state of button (should be HIGH) while (millis() <= startTime + 3000) //while current millis is less than 3sec from startTime { buttonValue1 = digitalRead(button1); delay(10); //perform a crude debounce by checking button twice over 10ms buttonValue2 = digitalRead(button1); if (buttonValue1 == buttonValue2) { if (buttonValue1 != buttonState) { //make sure button state has changed if (buttonValue1 == LOW) { //if button is pressed if (lowTemp >= 160 & lowTemp < 230) { lowTemp = lowTemp + 10; } else if(lowTemp >= 230) { lowTemp = 160; } lcd.setCursor(0,1); lcd.print(" "); lcd.setCursor(0,1); lcd.print(lowTemp); lcd.setCursor(3,1); lcd.print("F"); startTime = millis(); //update startTime to give user more time //to choose another value } } buttonState = buttonValue1; //update buttonState so that only changes //in button status are registered } } lcd.setCursor(6,1); lcd.print("Storing"); delay(400); for (int i = 0; i <= 2; i++) { lcd.print("."); delay(350); } return lowTemp; } //********************************************************************************* // highTempFunc subroutine. This lets the user choose the low temperature limit for // the heating element of the bbq. This uses input from button1 and returns a value // 'lowTemp' to the setup loop. int highTempFunc(int lowT) { //highTempFunc will return an integer when called lowT = lowT + 10; //increment low Temp limit value highTemp = lowT; //start highTemp at value of lowT int buttonValue1; int buttonValue2; int buttonState; long startTime = millis(); //get starting time for this loop lcd.clear(); lcd.print("Choose high temp"); delay(500); lcd.setCursor(0,1); lcd.print(lowT); lcd.setCursor(3,1); lcd.print("F"); buttonState = digitalRead(button1); //get current state of button (should be HIGH) while (millis() <= startTime + 3000) //while current millis is less than 3sec from startTime { buttonValue1 = digitalRead(button1); delay(10); //perform a crude debounce by checking button twice over 10ms buttonValue2 = digitalRead(button1); if (buttonValue1 == buttonValue2) { if (buttonValue1 != buttonState) { //make sure button state has changed if (buttonValue1 == LOW) { //if button is pressed if (highTemp >= lowT & highTemp < 250) { highTemp = highTemp + 10; //raise upper limit } else if(highTemp >= 250) { highTemp = lowT; //recycle back to lowest limit } lcd.setCursor(0,1); //set cursor at start of 2nd row lcd.print(" "); //clear current temp lcd.setCursor(0,1); //reset cursor position lcd.print(highTemp); //display current highTemp value lcd.setCursor(3,1); //move cursor lcd.print("F"); startTime = millis(); //update startTime to give user more time //to choose another value } } buttonState = buttonValue1; //update buttonState so that only changes //in button status are registered } } lcd.setCursor(6,1); lcd.print("Storing"); delay(400); for (int i = 0; i <= 2; i++) { lcd.print("."); delay(350); } return highTemp; } //**************************************************************** //runWiFi function // This lets the user choose whether to enable the WiFi connection // If WiFi was set up incorrectly, the whole program would hang while // trying to connect to a router, so the user has the option to // simply bypass the WiFi feature and just read out BBQ status on // the LCD screen. boolean runWiFi() { int buttonValue1; int buttonValue2; int buttonState; //int minutes; long startTime = millis(); //get starting time for this loop lcd.clear(); lcd.print("Run WiFi?"); lcd.setCursor(0,1); if (runwifi) { lcd.print("YES"); } else { lcd.print("NO"); } buttonState = digitalRead(button1); //get current state of button (should be HIGH) while (millis() <= startTime + 4000) //while current millis is less than 4sec from startTime { buttonValue1 = digitalRead(button1); delay(10); //perform a crude debounce by checking button twice over 10ms buttonValue2 = digitalRead(button1); if (buttonValue1 == buttonValue2) { if (buttonValue1 != buttonState) { //make sure button state has changed if (buttonValue1 == LOW) { //if button is pressed runwifi = !runwifi; //invert runwifi value if (runwifi) { //if runwifi is currently true lcd.setCursor(0,1); lcd.print("YES"); } else if (!runwifi) { //if runwifi is currently false lcd.setCursor(0,1); lcd.print("NO "); } startTime = millis(); } } buttonState = buttonValue1; //update buttonState so that only changes //in button status are registered } } return runwifi; } /* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */