Not sure if this is the best spot to post, if not please let me know where is better.
I have a thermocouple (k type - is fine for my needseven if not the best)
the program i have works well upto 1024 deg C but then the program slows down and stops refreshing web.
it is for a gas kiln, to track the rate of change in temp, over 5 min and 30 min. (takes about 10 hours to fire kiln uptp 1230deg c.)
i assume it is because of the precision in floating point?
my programing skills are very basic,
code as follows,
Code: Select all
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>
#include <SPI.h>
#include <time.h>
#include <Adafruit_MAX31855.h>
// Hardware Configuration
#define MAXDO 19 // Data out
#define MAXCS 5 // Chip Select
#define MAXCLK 18 // Clock
#define LED_LOW 25 //blue
#define LED_MID 26 //green
#define LED_HIGH 27 //red
const int shortBlinkDuration = 250; // milliseconds
const int longBlinkDuration = 750;
const int pauseDuration = 500;
float avgTCCelsius =0;
float icnt=0;
float TCCelsius=0;
// Data Logging
#define LOG_INTERVAL 30000 // 30 seconds ( 30000)
#define TEMP_INTERVAL 5000 // 5 seconds ( 5000)
#define BUFFER_SIZE 1400 // 12 hours at 30s intervals
// Default Settings
const char *AP_SSID = "ThermoConfig";
float LOW_THRESH = 100.0;
float HIGH_THRESH = 170.0;
// NTP Configuration for Australia (AEST/AEDT)
const char *ntpServer = "au.pool.ntp.org"; // Australian NTP pool
const char *tz = "AEST-10AEDT-11,M10.1.0,M4.1.0/3"; // Auto DST adjustment
struct TempReading
{
time_t timestamp; // Unix timestamp
float temperature;
float rate5min;
float rate30min;
float TCCelsius;
float temp_avgTCCelsius;
};
//MAX6675 thermocouple(MAX6675_SCK, MAX6675_CS, MAX6675_MISO);
Adafruit_MAX31855 thermocouple(MAXCLK, MAXCS, MAXDO);
WebServer server(80);
Preferences prefs;
TempReading tempBuffer[BUFFER_SIZE];
int bufferIndex = 0;
int readingsCount = 0;
unsigned long lastReadingTime = 0;
unsigned long lastTEMPTime = 0;
float currentTemp = 0.0;
float rate5min = 0.0;
float rate30min = 0.0;
float currentRate5min = 0.0;
float currentRate30min = 0.0;
bool restartFlag = false;
unsigned long restartTime = 0;
bool timeSet = false;
void initSPI()
{
pinMode(MAXCS, OUTPUT);
digitalWrite(MAXCS, HIGH);
}
void updateRates() {
currentRate5min = NAN;
currentRate30min = NAN;
if (readingsCount >= 10)
{
int idx = (bufferIndex - 10 + BUFFER_SIZE) % BUFFER_SIZE;
rate5min = (currentTemp - tempBuffer[idx].temperature) * 12.0f; //f float
currentRate5min = rate5min;
}
if (readingsCount >= 60)
{
int idx = (bufferIndex - 60 + BUFFER_SIZE) % BUFFER_SIZE;
rate30min = (currentTemp - tempBuffer[idx].temperature) * 2.0f; //f float
currentRate30min = rate30min;
}
}
void updateLEDs() {
float rate = !isnan(currentRate5min) ? currentRate5min : currentRate30min;
digitalWrite(LED_LOW, LOW);
digitalWrite(LED_MID, LOW);
digitalWrite(LED_HIGH, LOW);
if (isnan(rate)) return;
if (rate < LOW_THRESH) digitalWrite(LED_LOW, HIGH);
else if (rate > HIGH_THRESH) digitalWrite(LED_HIGH, HIGH);
else digitalWrite(LED_MID, HIGH);
}
// Initialize NTP time synchronization with Australian settings
void initTime()
{
configTzTime(tz, ntpServer);
Serial.println("Waiting for NTP time sync...");
// Wait for time to be set
time_t now;
int attempts = 0;
while ((now = time(nullptr)) < 1000000000 && attempts < 20)
{
Serial.print(".");
delay(500);
attempts++;
}
if (now < 1000000000)
{
Serial.println("\nFailed to get NTP time");
timeSet = false;
}
else
{
Serial.println("\nTime synchronized!");
timeSet = true;
}
}
// Get current timestamp
time_t getCurrentTime()
{
return time(nullptr);
}
// Format timestamp as compact Australian time string (HH:MM:SS)
String formatTime(time_t timestamp)
{
if (timestamp == 0) return "N/A";
struct tm *timeinfo;
timeinfo = localtime(×tamp);
char buffer[10]; // HH:MM:SS format
strftime(buffer, sizeof(buffer), "%H:%M:%S", timeinfo);
return String(buffer);
}
// Format date for display (DD/MM/YYYY)
String formatDate(time_t timestamp)
{
if (timestamp == 0) return "N/A";
struct tm *timeinfo;
timeinfo = localtime(×tamp);
char buffer[11]; // DD/MM/YYYY format
strftime(buffer, sizeof(buffer), "%d/%m/%Y", timeinfo);
return String(buffer);
}
void handleRoot()
{
String html = R"rawhtml(
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Thermocouple Monitor</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body { font-family: Arial, sans-serif; }
.container { max-width: 1200px; margin: 0 auto; }
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; text-align:center;}
.status-card { background: #29cc00; padding: 15px; border-radius: 5px; }
button { padding: 12px; background: #29cc00; color: white; border: none; border-radius: 4px; cursor: pointer; }
canvas { margin-top: 20px; } // extra
.alert { padding: 10px; margin: 10px 0; border-radius: 4px; }
.success { background: #d4edda; color: #155724; }
.error { background: #f8d7da; color: #721c24; }
</style>
</head>
<body>
<div class="container">
<h2 text-align:center;>Thermocouple Monitoring</h2>
<div id="message" class="alert" style="display:none;"></div>
<div class="grid">
<div class="status-card"; >
<p>Current Temp</p>
<h1 style="font-size:3rem;" id="currentTemp">0.0 °C</h1>
</div>
<div class="status-card">
<p>5-min Rate</p>
<h1 style="font-size:3rem;" id="rate5min">0.0 °C/h</h1>
</div>
<div class="status-card">
<p>30-min Rate</p>
<h1 style="font-size:3rem;" id="rate30min">0.0 °C/h</h1>
</div>
</div>
<canvas id="tempChart"></canvas>
<div style="margin-top:20px;">
<h3>Configuration</h3>
<div>
<label>Low Threshold: </label>
<input type="number" id="lowThresh" step="0.1" value=")rawhtml" + String(LOW_THRESH) + R"rawhtml(">
<label>High Threshold: </label>
<input type="number" id="highThresh" step="0.1" value=")rawhtml" + String(HIGH_THRESH) + R"rawhtml(">
<button onclick="updateThresholds()">Update</button>
</div>
<div style="margin-top:10px;">
<h3>Download CSV</h3>
<button onclick="downloadCSV()">Download</button>
</div>
<div style="margin-top:20px;">
<h3>WiFi Settings</h3>
<input type="text" id="ssid" placeholder="New SSID" value=")rawhtml" + String(WiFi.SSID()) + R"rawhtml(">
<input type="password" id="password" placeholder="New Password">
<button onclick="updateWiFi()">Update WiFi</button>
</div>
<div style="margin-top:20px;">
<h3>Time Sync</h3>
<button onclick="syncTime()">Sync Time</button>
</div>
</div>
</div>
<script>
const ctx = document.getElementById('tempChart').getContext('2d');
const chart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Temperature (C)',
data: [],
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
}]
},
options: { scales: { y: { beginAtZero: false } } }
});
function showMessage(text, isError) {
const msgDiv = document.getElementById('message');
msgDiv.textContent = text;
msgDiv.className = isError ? 'alert error' : 'alert success';
msgDiv.style.display = 'block';
setTimeout(() => msgDiv.style.display = 'none', 5000);
}
function updateData() {
fetch('/data')
.then(r => r.json())
.then(data => {
document.getElementById('currentTemp').textContent = data.currentTemp.toFixed(1) + ' C';
document.getElementById('rate5min').textContent = data.rate5min.toFixed(1) + ' C/h';
document.getElementById('rate30min').textContent = data.rate30min.toFixed(1) + ' C/h';
chart.data.labels = data.timestamps;
chart.data.datasets[0].data = data.temperature;
chart.update();
});
}
function updateThresholds() {
const low = document.getElementById('lowThresh').value;
const high = document.getElementById('highThresh').value;
fetch(`/config?low=${low}&high=${high}`)
.then(r => r.text())
.then(text => showMessage(text, false))
.catch(e => showMessage('Error: ' + e, true));
}
function downloadCSV() {
window.location.href = '/csv';
}
function updateWiFi() {
const ssid = document.getElementById('ssid').value;
const password = document.getElementById('password').value;
if(!ssid) {
showMessage('SSID cannot be empty!', true);
return;
}
fetch(`/wifi?ssid=${encodeURIComponent(ssid)}&pass=${encodeURIComponent(password)}`)
.then(r => r.text())
.then(text => {
showMessage(text, false);
setTimeout(() => showMessage('Reconnecting in 5 seconds...', false), 1000);
setTimeout(() => location.reload(), 6000);
})
.catch(e => showMessage('Error: ' + e, true));
}
function syncTime() {
fetch('/synctime')
.then(r => r.text())
.then(text => {
showMessage(text, false);
location.reload();
})
.catch(e => showMessage('Error: ' + e, true));
}
setInterval(updateData, 2000);
updateData();
</script>
</body>
</html>
)rawhtml";
server.send(200, "text/html; charset=utf-8", html);
}
void handleData()
{
String json = "{";
json += "\"currentTemp\":" + String(currentTemp) + ",";
json += "\"rate5min\":" + String(rate5min) + ",";
json += "\"rate30min\":" + String(rate30min) + ",";
json += "\"timestamps\":[";
for(int i = 0; i < min(readingsCount, 61); i++)
{
if(i > 0) json += ",";
json += "\"" + String(i*0.5f) + "m\"";
}
json += "],";
json += "\"temperature\":[";
for(int i = 0; i < min(readingsCount, 61); i++) {
if(i > 0) json += ",";
int idx = (bufferIndex - min(readingsCount, 61) + i + BUFFER_SIZE) % BUFFER_SIZE;
json += String(tempBuffer[idx].temperature);
}
json += "]}";
server.send(200, "application/json; charset=utf-8", json);
}
void handleCSV()
{
// Use "C" instead of "amp deg C" to avoid encoding issues in CSV
// String csv = "Time (s),Temperature (C)\n";
String csv = formatDate(getCurrentTime()) + "\n" + "Time,Temperature (C),5min Rate (C/h),30min Rate (C/h)\n";
for(int i = 0; i < readingsCount; i++) {
int idx = (bufferIndex - readingsCount + i + BUFFER_SIZE) % BUFFER_SIZE;
// Format timestamp to HH:MM:SS
csv += formatTime(tempBuffer[idx].timestamp) + ",";
csv += String(tempBuffer[idx].temperature, 1) + ",";
csv += String(tempBuffer[idx].rate5min, 1) + ",";
csv += String(tempBuffer[idx].rate30min, 1) + "\n";
//csv += String(i*10) + "," + String(tempBuffer[idx].temperature, 1) + "\n";
}
server.send(200, "text/csv", csv);
}
void handleConfig()
{
if(server.hasArg("low") && server.hasArg("high"))
{
LOW_THRESH = server.arg("low").toFloat();
HIGH_THRESH = server.arg("high").toFloat();
prefs.putFloat("lowThresh", LOW_THRESH);
prefs.putFloat("highThresh", HIGH_THRESH);
server.send(200, "text/plain", "Thresholds updated");
}
else
{
server.send(400, "text/plain", "Invalid parameters");
}
}
void handleWiFi()
{
if(server.hasArg("ssid") && server.hasArg("pass")) {
String ssid = server.arg("ssid");
String pass = server.arg("pass");
if(ssid.length() == 0) {
server.send(400, "text/plain", "SSID cannot be empty");
return;
}
prefs.putString("ssid", ssid);
prefs.putString("pass", pass);
prefs.end(); // Ensure data is committed
server.send(200, "text/plain", "WiFi credentials updated. Reconnecting...");
// Set flag to trigger reconnect in main loop
restartFlag = true;
restartTime = millis() + 2000; // Reconnect after 2 seconds
}
else
{
server.send(400, "text/plain", "Missing parameters");
}
}
void handleSyncTime()
{
initTime();
server.send(200, "text/plain", "Time synchronized: " + formatTime(getCurrentTime()));
}
void blink_octet(int octet)
{
if (octet ==10)
{
digitalWrite(LED_MID, HIGH);
delay(1000);
digitalWrite(LED_MID, LOW);
delay(pauseDuration);
}
else
{
for (int i = 0; i < octet; i++)
{
digitalWrite(LED_MID, HIGH);
delay(shortBlinkDuration);
digitalWrite(LED_MID, LOW);
if (i < octet - 1)
{
delay(pauseDuration);
}
}
}
}
void initWiFi()
{
prefs.begin("thermoConfig", false);
String ssid = prefs.getString("ssid", "");
String pass = prefs.getString("pass", "");
if(ssid == "")
{
WiFi.softAP(AP_SSID);
Serial.println("AP Mode: " + String(AP_SSID));
return;
}
WiFi.begin(ssid.c_str(), pass.c_str());
Serial.print("Connecting to WiFi...");
int attempts = 0;
while(WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(".");
attempts++;
}
if(WiFi.status() == WL_CONNECTED) {
Serial.println("\nConnected! IP: " + WiFi.localIP().toString());
// Initialize time after WiFi connection
initTime();
}
else
{
WiFi.softAP(AP_SSID);
Serial.println("\nFailed! Starting AP Mode");
}
}
void reconnectWiFi()
{
Serial.println("Reconnecting to WiFi...");
WiFi.disconnect();
delay(1000);
initWiFi();
restartFlag = false;
}
void setup()
{
Serial.begin(115200);
// Initialize hardware
pinMode(LED_LOW, OUTPUT);
pinMode(LED_MID, OUTPUT);
pinMode(LED_HIGH, OUTPUT);
initSPI();
// Load saved settings
prefs.begin("thermoConfig", false);
LOW_THRESH = prefs.getFloat("lowThresh", 100.0);
HIGH_THRESH = prefs.getFloat("highThresh", 175.0);
prefs.end();
// Start WiFi
initWiFi();
IPAddress ipAddress = WiFi.localIP();
int octet = ipAddress[3];
int val = octet;
int hundreds = val / 100;
val -= hundreds * 100;
int tens = val / 10;
val -= tens * 10;
int ones = val;
if (hundreds < 1) hundreds = 10;
if (tens < 1) tens = 10;
if (ones < 1) ones = 10;
blink_octet(hundreds);
delay(1000);
blink_octet(tens);
delay(1000);
blink_octet(ones);
delay(1000);
// Setup web server
server.on("/", handleRoot);
server.on("/data", handleData);
server.on("/csv", handleCSV);
server.on("/config", handleConfig);
server.on("/wifi", handleWiFi);
server.begin();
Serial.println("HTTP server started");
}
void loop()
{
server.handleClient();
delay(490);
// Handle WiFi reconnect if needed
if (restartFlag && millis() >= restartTime) {
reconnectWiFi();
}
// Handle temperature readings
unsigned long currentTime = millis();
TCCelsius = thermocouple.readCelsius();
//float avgTCCelsius1 = avgTCCelsius + TCCelsius;
//avgTCCelsius=avgTCCelsius1;
avgTCCelsius=avgTCCelsius + TCCelsius;
++icnt;
Serial.print(" temp ");
Serial.print(TCCelsius);
Serial.print('\n');
if (currentTime - lastTEMPTime >= TEMP_INTERVAL) {
lastTEMPTime = currentTime;
currentTemp = avgTCCelsius/icnt;
avgTCCelsius = 0 ; //james
icnt = 0 ;
Serial.print(" avtemp ");
Serial.print(currentTemp);
Serial.print('\n');
}
if (currentTime - lastReadingTime >= LOG_INTERVAL) {
lastReadingTime = currentTime;
if (!isnan(currentTemp))
{
// Calculate rates
updateRates();
// Store reading with rates and timestamp
tempBuffer[bufferIndex] = {
.timestamp = getCurrentTime(),
.temperature = currentTemp,
.rate5min = currentRate5min,
.rate30min = currentRate30min
};
// Update buffer position
bufferIndex = (bufferIndex + 1) % BUFFER_SIZE;
if (readingsCount < BUFFER_SIZE) readingsCount++;
// Update LEDs
updateLEDs();
}
}
}
[/code