/*
BotulisMaster9000000
pins: LCD pins
1 - heating element / LED 1 - ground 9 -
2 2 - +5v 10 -
3 - LCD RS 3 - pot 11 - DB4 (6)
4 - ethernet/sd 4 - RS (3) 12 - DB5 (7)
5 - LCD EN 5 - ground 13 - DB6 (8)
6 - LCD DB4 6 - EN (5) 14 - DB7 (9)
7 - LCD DB5 7 - 15 - +5v
8 - LCD DB6 8 - 16 - ground
9 - LCD DB7
10 - ethernet/sd
11 - ethernet/sd
12 - ethernet/sd
13 - ethernet/sd
*/
#include <SPI.h>
#include <LiquidCrystal.h>
#include <SdFat.h>
#include <SdFatUtil.h>
#include <Ethernet.h>
#include <Time.h>
#include <Udp.h>
#include <Flash.h>
#define SERIAL
#define USE_LCD
#define USE_LOG
// ETHERNET SETUP
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192, 168, 0, 101 };
byte gateway[] = { 192, 168, 0, 1 }; //your router's IP address
byte subnet[] = { 255, 255, 255, 0 }; //subnet mask of the network
Server server(580);
// NTP TIME SYNC SETUP
unsigned int localPort = 8888; // local port to listen for UDP packets
byte timeServer[] = {192, 43, 244, 18}; // time.nist.gov NTP server
const int NTP_PACKET_SIZE= 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
const long timeZoneOffset = 18000L; // 60 seconds * 60 minutes * 5 hours
// SD CARD SETUP
Sd2Card card;
SdVolume volume;
SdFile root;
SdFile file;
#ifdef USE_LCD // LCD SETUP
LiquidCrystal lcd(3, 5, 6, 7, 8, 9);
#endif // USE_LCD
// setup variables to track interval for logging/temp adjustment
long previousMillis = 0; // will store last time logging/temp adjustment was called
unsigned int SECOND = 1000;
long MINUTE = SECOND*60;
long HOUR = MINUTE*60;
int interval = 2*SECOND; // interval at which to log data/adjust temp
//int tempProgram[] = {75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75}; // bacon
int tempProgram[] = {110, 120, 130, 140, 150, 160, 165, 165, 165, 165, 165, 165, 165, 165, 165}; // sausage
//int tempProgram[] = {125, 135, 145, 155, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165}; // hot dogs
//int tempProgram[] = {130, 145, 145, 145, 160, 170, 170, 170, 170};
//int tempProgram[] = {100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100}; // jerky
// setup variables to hold temp readings/targets
byte pitTemp = 0;
byte pitTarget = 0;
byte meatTemp = 0;
byte meatTarget = 152;
// heating element
byte elementStatus = HIGH;
unsigned int elementPin = A5;
// html
FLASH_STRING(http_ok, "HTTP/1.1 200 OK");
FLASH_STRING(content_html, "Content-Type: text/html");
FLASH_STRING(html_open, "<form method=get><table border=1>");
FLASH_STRING(tr_time, "<tr><td>%2.2d:%2.2d</td><td colspan=2>%2.2d:%2.2d:%2.2d</td></tr>");
FLASH_STRING(tr_header, "<tr><td> </td><td><b>Temp</b></td><td><b>Target</b></td></tr>");
FLASH_STRING(tr_pit, "<tr><td><b>Pit</b></td><td>%2.2d</td><td><input type=text size=3 name=pit value=\"%2.2d\"></td></tr>");
FLASH_STRING(tr_meat, "<tr><td><b>Meat</b></td><td>%2.2d</td><td><input type=text size=3 name=meat value=\"%2.2d\"></td></tr>");
FLASH_STRING(tr_button, "<tr><td colspan=99 align=center><input type=submit value=Update></td></tr></table></form>");
FLASH_STRING(html_close, "");
FLASH_STRING(http_404, "HTTP/1.1 404 Not Found");
FLASH_STRING(file_not_found, "404!");
FLASH_STRING(open_ul, "<ul>");
FLASH_STRING(open_li, "<li><a href=\"");
FLASH_STRING(end_a, "\">");
FLASH_STRING(close_a, "</a>");
FLASH_STRING(close_li, "</li>");
FLASH_STRING(close_ul, "</ul>");
FLASH_STRING(content_csv, "Content-Type: text/csv");
FLASH_STRING(h2_files, "<br><h2>Files:</h2><br>");
FLASH_STRING(attachment, "Content-disposition: attachment; filename=%s");
FLASH_STRING(open_del, "<a href=\"del=");
FLASH_STRING(close_del, "\">X</a>");
FLASH_STRING(temp_scale, "%2.2d:00 - %3d<br>");
// lcd messages
FLASH_STRING(welcome_to, "welcome to");
FLASH_STRING(botul9m, "botulismaster9MM");
FLASH_STRING(web_req, "request");
FLASH_STRING(update_ln1, "%2.2d:%2.2d %2.2d:%2.2d:%2.2d");
FLASH_STRING(update_ln2a, "pit %3d%c/%3d%c");
FLASH_STRING(update_ln2b, "meat %3d%c/%3d%c");
FLASH_STRING(sync_time, "syncing time");
FLASH_STRING(attmpt, "attempt");
FLASH_STRING(blank, " ");
FLASH_STRING(udp_unavail, "UDP unavailable");
FLASH_STRING(time_set, "time set");
//store error strings in flash to save RAM
#define error(s) error_P(PSTR(s))
void error_P(const char* str) {
PgmPrint("error: ");
SerialPrintln_P(str);
if (card.errorCode()) {
PgmPrint("SD error: ");
Serial.print(card.errorCode(), HEX);
Serial.print(',');
Serial.println(card.errorData(), HEX);
}
}
#define BUFSIZ 120
void setup() {
Serial.begin(9600);
Serial.print(welcome_to);
Serial.println(botul9m);
// lcd setup
#ifdef USE_LCD
lcd.begin(16, 2);
lcd.clear();
lcd.home();
lcd.print(welcome_to);
lcd.setCursor(0, 1);
lcd.print(botul9m);
delay(1000);
#endif // USE_LCD
// initialize the SD card at SPI_HALF_SPEED to avoid bus errors with breadboards.
pinMode(10, OUTPUT); // set the SS pin as an output (necessary!)
digitalWrite(10, HIGH); // but turn off the W5100 chip!
pinMode(elementPin, OUTPUT);
// start ethernet
Ethernet.begin(mac, ip, gateway, subnet);
Udp.begin(localPort);
server.begin();
// initialize time
setupTime();
initializeLog();
}
void loop() {
unsigned long currentMillis = millis();
if(currentMillis - previousMillis > interval) {
// save the last time you log/adjust temp
previousMillis = currentMillis;
// read temp data
pitTemp = thermister_temp(analogRead(4));
meatTemp = thermister_temp(analogRead(3));
#ifdef USE_LOG
logData();
#endif // USE_LOG
adjustTargetTemp();
adjustHeat();
#ifdef USE_LCD
updateLCD();
#endif // USE_LCD
}
listenForClients();
}
void listenForClients() {
Client client = server.available();
if (client) {
char buffer[BUFSIZ];
Serial.println(web_req);
int index = 0;
while (client.connected()) {
if (client.available()) {
char c = client.read();
if (c != '\n' && c != '\r') {
buffer[index] = c;
index++;
// are we too big for the buffer? start tossing out data
if (index >= BUFSIZ)
index = BUFSIZ -1;
continue; // continue to read more data!
}
buffer[index] = 0;
if (strstr(buffer, "?") != 0) { // update pit/meat targets if needed
String s = String(buffer);
String p = s.substring(s.indexOf("pit=")+4, s.indexOf("meat=")-1);
char p_char[p.length()+1];
p.toCharArray(p_char, sizeof(p_char));
pitTarget = atoi(p_char);
String m = s.substring(s.indexOf("meat=")+5, s.indexOf("HTTP")-1);
char m_char[m.length()+1];
m.toCharArray(m_char, sizeof(m_char));
meatTarget = atoi(m_char);
}
#ifdef USE_LOG
#else
if (strstr(buffer, "GET /del=") !=0 ) { // handle delete
char filename[13];
for(int i=0; i<=11; i++){
filename = buffer[i+9];
}
filename[12] = 0;
if (!file.open(&root, filename, O_WRITE)) {
//Serial.print("Can't open ");
//Serial.println(filename);
//error("file.open failed");
}
if (!file.remove()) {
//Serial.print("file.remove failed");
}
}else if (strstr(buffer, ".CSV") != 0) { // serve up file
char filename[13];
for(int i=0; i<=11; i++){
filename = buffer[i+5];
}
filename[12] = 0;
if (! file.open(&root, filename, O_READ)) { // can't read file
client.println(http_404);
client.println(content_html);
client.println();
client.println(file_not_found);
} else { // can read file
client.println(http_ok);
client.println(content_csv);
attachment.copy(buffer);
char buf[55];
sprintf(buf, buffer, filename);
client.println(buf);
client.println();
int16_t c;
while ((c = file.read()) > 0) {
//Serial.print((char)c); // uncomment the serial to debug (slow!)
client.print((char)c);
}
file.close();
}
break;
}
#endif //USE_LOG
client.println(http_ok);
client.println(content_html);
client.println("");
client.println(html_open);
tr_time.copy(buffer);
long et = millis();
int hr = et/HOUR;
int min = et%HOUR/MINUTE;
int sec = (et%MINUTE)/1000;
sprintf(buffer, buffer, hour(), minute(), hr, min, sec);
client.println(buffer);
client.print(tr_header);
tr_pit.copy(buffer);
sprintf(buffer, buffer, pitTemp, pitTarget);
client.println(buffer);
tr_meat.copy(buffer);
sprintf(buffer, buffer, meatTemp, meatTarget);
client.println(buffer);
client.println(tr_button);
#ifdef USE_LOG
#else
client.println(h2_files);
ListFiles(client, 0);
#endif
for(int i =0; i < 10; i++) {
temp_scale.copy(buffer);
sprintf(buffer, buffer, i, tempProgram);
client.println(buffer);
}
client.println(html_close);
break;
}
}
// give the web browser time to receive the data
delay(1);
client.stop();
}
}
int thermister_temp(int aval) {
double R, T;
// These were calculated from the thermister data sheet
// A = 2.3067434E-4;
// B = 2.3696596E-4;
// C = 1.2636414E-7;
//
// This is the value of the other half of the voltage divider
// Rknown = 22200;
// Do the log once so as not to do it 4 times in the equation
// R = log(((1024/(double)aval)-1)*(double)22200);
R = log((1 / ((1024 / (double) aval) - 1)) * (double) 22200);
// Compute degrees C
T = (1 / ((2.3067434E-4) + (2.3696596E-4) * R + (1.2636414E-7) * R * R * R)) - 273.25;
// return degrees F
return ((int) ((T * 9.0) / 5.0 + 32.0));
}
void initializeLog() {
if (!card.init(SPI_HALF_SPEED, 4)) error("card.init failed!");
// initialize a FAT volume
if (!volume.init(&card)) error("volume.init failed");
// open root directory
if (!root.openRoot(&volume)) error("openRoot failed");
#ifdef USE_LOG
// create a new file
char name[] = "LOGGER00.CSV";
for (uint8_t i = 0; i < 100; i++) {
name[6] = i/10 + '0';
name[7] = i%10 + '0';
if (file.open(&root, name, O_CREAT | O_EXCL | O_WRITE)) break;
}
file.open(&root, name, O_CREAT | O_EXCL | O_WRITE);
if (!file.isOpen()) error ("file.create");
//Serial.print(F("Logging to: "));
lcd.setCursor(0, 1);
lcd.print(name);
delay(1000);
// write header
file.writeError = 0;
file.print(F("time,pitTemp,pitTarget,meatTemp,meatTarget,elementStatus"));
file.println();
if (file.writeError || !file.sync()) {
error("write header failed");
}
#endif // USE_LOG
}
// log data to the SD card
void logData() {
char line[60];
file.writeError = 0;
sprintf(line, "%2.2d:%2.2d:%2.2d,%3d,%3d,%3d,%3d,%3d", hour(), minute(), second(),pitTemp, pitTarget, meatTemp, meatTarget, elementStatus);
file.print(line);
file.println();
if (!file.sync()) error("sync failed");
}
void updateLCD() {
#ifdef USE_LCD
char line[20];
lcd.clear();
lcd.home();
long et = millis();
int hr = et/HOUR;
int min = et%HOUR/MINUTE;
int sec = (et%MINUTE)/1000;
update_ln1.copy(line);
sprintf(line, line, hour(), minute(), hr, min, sec);
lcd.print(line);
lcd.setCursor(0, 1);
if(second() % 6 < 3) {
update_ln2a.copy(line);
sprintf(line, line, pitTemp, 0xDF, pitTarget, 0xDF);
} else {
update_ln2b.copy(line);
sprintf(line, line, meatTemp, 0xDF, meatTarget, 0xDF);
}
lcd.print(line);
#endif // USE_LCD
}
// adjustHeat
void adjustHeat() {
if ((pitTarget) > pitTemp) {
elementStatus = HIGH;
} else {
elementStatus = LOW;
}
if(pitTemp < 0) {
elementStatus = LOW; // if something is wrong turn off the element
}
digitalWrite(elementPin, elementStatus);
}
// adjustHeat
void adjustTargetTemp() {
int hr = millis()/HOUR;
//int hr = millis()%HOUR/MINUTE;
if(hr > sizeof(tempProgram))
hr = sizeof(tempProgram)-1;
pitTarget = tempProgram
;
//pitTarget = 75;
}
void setupTime() {
char buffer[20];
Serial.println(sync_time);
#ifdef USE_LCD
lcd.clear();
lcd.home();
lcd.print(sync_time);
lcd.setCursor(0,1);
lcd.print(attmpt);
#endif // USE_LCD
for(int i=1; i <=5; i++) {
Serial.println(i);
#ifdef USE_LCD
lcd.setCursor(1, 8);
lcd.print(i);
// attmpt.copy(buffer);
// sprintf(buffer, buffer, i);
// lcd.print(buffer);
#endif // USE_LCD
sendNTPpacket(timeServer); // send an NTP packet to a time server
// wait to see if a reply is available
delay(1000);
if ( Udp.available() ) {
Udp.readPacket(packetBuffer,NTP_PACKET_SIZE); // read the packet into the buffer
//the timestamp starts at byte 40 of the received packet and is four bytes,
// or two words, long. First, extract the two words:
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
// combine the four bytes (two words) into a long integer
// this is NTP time (seconds since Jan 1 1900):
unsigned long secsSince1900 = highWord << 16 | lowWord;
// Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
const unsigned long seventyYears = 2208988800UL;
// subtract seventy years:
unsigned long epoch = secsSince1900 - seventyYears - timeZoneOffset;
setTime(epoch);
Serial.println(time_set);
break;
} else {
Serial.println(udp_unavail);
#ifdef USE_LCD
lcd.setCursor(0, 1);
lcd.print(udp_unavail);
#endif // USE_LCD
delay(2000);
}
}
if (timeStatus()== timeNotSet) {
Serial.println("time not set... defaulting");
#ifdef USE_LCD
lcd.setCursor(0, 1);
lcd.print("fail. defaulting");
#endif // USE_LCD
setTime(1262347900);
}
}
// send an NTP request to the time server at the given address
unsigned long sendNTPpacket(byte *address)
{
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
Udp.sendPacket( packetBuffer,NTP_PACKET_SIZE, address, 123); //NTP requests are to port 123
}
#ifdef USE_LOG
#else
void ListFiles(Client client, uint8_t flags) {
// This code is just copied from SdFile.cpp in the SDFat library
// and tweaked to print to the client output in html!
dir_t p;
char buffer[15];
root.rewind();
client.println(open_ul);
while (root.readDir(p) > 0) {
// done if past last used entry
if (p.name[0] == DIR_NAME_FREE) break;
// skip deleted entry and entries for . and ..
if (p.name[0] == DIR_NAME_DELETED || p.name[0] == '.') continue;
// only list subdirectories and files
if (!DIR_IS_FILE_OR_SUBDIR(&p)) continue;
// print any indent spaces
client.println(open_li);
for (uint8_t i = 0; i < 11; i++) {
if (p.name == ' ') continue;
if (i == 8) {
client.print('.');
}
client.print(p.name);
}
client.println(end_a);
// print file name with possible blank fill
for (uint8_t i = 0; i < 11; i++) {
if (p.name == ' ') continue;
if (i == 8) {
client.print('.');
}
client.print(p.name);
}
client.println(close_a);
if (DIR_IS_SUBDIR(&p)) {
client.print('/');
}
// print modify date/time if requested
if (flags & LS_DATE) {
root.printFatDate(p.lastWriteDate);
client.print(' ');
root.printFatTime(p.lastWriteTime);
}
// print size if requested
if (!DIR_IS_SUBDIR(&p) && (flags & LS_SIZE)) {
client.print(' ');
client.print(p.fileSize);
}
// print file name with possible blank fill
client.print(open_del);
for (uint8_t i = 0; i < 11; i++) {
if (p.name == ' ') continue;
if (i == 8) {
client.print('.');
}
client.print(p.name);
}
client.print(close_del);
client.println(close_li);
}
client.println(close_ul);
}
#endif