Filament dryer page

This commit is contained in:
Hickmeister
2025-01-13 21:35:48 +00:00
parent bcb7d7ea7d
commit 9424f1857f
2 changed files with 521 additions and 0 deletions

512
public/filamentDryer.php Normal file
View File

@@ -0,0 +1,512 @@
<?php include '../src/session_check.php';
checkUserRole(['admin']);
?>
<html lang="en" data-bs-theme="light">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--favicon-->
<link rel="icon" href="../assets/images/favicon-32x32.png" type="image/png">
<!--plugins-->
<link href="../assets/plugins/vectormap/jquery-jvectormap-2.0.2.css" rel="stylesheet">
<link href="../assets/plugins/simplebar/css/simplebar.css" rel="stylesheet">
<link href="../assets/plugins/perfect-scrollbar/css/perfect-scrollbar.css" rel="stylesheet">
<link href="../assets/plugins/metismenu/css/metisMenu.min.css" rel="stylesheet">
<!-- loader-->
<link href="../assets/css/pace.min.css" rel="stylesheet"/>
<script src="../assets/js/pace.min.js"></script>
<!-- Bootstrap CSS -->
<link href="../assets/css/bootstrap.min.css" rel="stylesheet">
<link href="../assets/css/bootstrap-extended.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
<link href="../assets/sass/app.css" rel="stylesheet">
<link href="../assets/css/icons.css" rel="stylesheet">
<link href='https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css' rel='stylesheet'>
<!-- Theme Style CSS -->
<link rel="stylesheet" href="../assets/sass/dark-theme.css">
<link rel="stylesheet" href="../assets/sass/semi-dark.css">
<link rel="stylesheet" href="../assets/sass/bordered-theme.css">
<title>TOD Dashboard</title>
</head>
<body>
<!--wrapper-->
<div class="wrapper">
<!--sidebar wrapper -->
<?php include '../src/nav.php'; ?>
<!--end sidebar wrapper -->
<!--start header -->
<?php include '../src/header.php'; ?>
<!--end header -->
<!--start page wrapper -->
<div class="page-wrapper">
<div class="page-content">
<!--start page content -->
<div class="container mt-4">
<div class="row">
<div class="col-lg-4">
<div class="card bg-danger radius-10 overflow-hidden">
<div class="card-body">
<div class="d-flex align-items-center">
<div>
<p class="mb-0">Dryer Status</p>
<h5 class="mb-0">STATUS</h5>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card bg-danger radius-10 overflow-hidden" id="heaterRelayStatus">
<div class="card-body">
<div class="d-flex align-items-center">
<div>
<p class="mb-0">Heater Status</p>
<h5 class="mb-0">STATUS</h5>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card bg-danger radius-10 overflow-hidden" id="fanRelayStatus">
<div class="card-body">
<div class="d-flex align-items-center">
<div>
<p class="mb-0">Fan Status</p>
<h5 class="mb-0">STATUS</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<!-- Left Column - Charts -->
<div class="col-lg-7">
<div class="row">
<!-- Temperature Chart -->
<div class="col-lg-12 mb-4">
<div class="card radius-10 overflow-hidden">
<div class="card-body d-flex align-items-center justify-content-between">
<div>
<p class="mb-0 filament-name">Temp:</p>
<h5 class="mb-0" id="temperatureValue">--°C</h5>
</div>
</div>
<div id="tempChart"></div>
</div>
</div>
<!-- Humidity Chart -->
<div class="col-lg-12 mb-4">
<div class="card radius-10 overflow-hidden">
<div class="card-body d-flex align-items-center justify-content-between">
<div>
<p class="mb-0 filament-name">Humidity:</p>
<h5 class="mb-0" id="humidityValue">--%</h5>
</div>
</div>
<div id="humidityChart"></div>
</div>
</div>
</div>
</div>
<!-- Right Column - Dryer Control Form -->
<div class="col-lg-5">
<div class="card radius-10 overflow-hidden">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">Filament Dryer Control</h5>
</div>
<div class="card-body">
<form id="dryerControlForm">
<div class="mb-3">
<label for="filamentPreset" class="form-label">Filament Preset</label>
<select class="form-select" id="filamentPreset" name="filamentPreset" required>
<option value="">Select Filament Type</option>
<option value="PLA" data-temp="50">PLA</option>
<option value="ABS" data-temp="70">ABS</option>
<option value="PETG" data-temp="65">PETG</option>
<option value="Nylon" data-temp="80">Nylon</option>
<option value="TPU" data-temp="45">TPU</option>
<option value="Custom" data-temp="">Custom</option>
</select>
</div>
<div class="mb-3">
<label for="dryingTime" class="form-label">Drying Time (Hours)</label>
<input type="number" class="form-control" id="dryingTime" name="dryingTime" min="1" max="24" required>
</div>
<div class="mb-3">
<label for="temperature" class="form-label">Drying Temperature (°C)</label>
<input type="number" class="form-control" id="temperature" name="temperature" min="30" max="100" required>
</div>
<div class="d-flex justify-content-between">
<button type="submit" class="btn btn-success">Start Drying</button>
<button type="button" class="btn btn-danger" id="dryerOff">Turn Off Dryer</button>
</div>
</form>
</div>
</div>
<!-- Progress Bar Section -->
<div class="card mt-4 radius-10 overflow-hidden">
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
<h5 class="mb-0">Drying Progress</h5>
</div>
<div class="card-body">
<div class="progress mb-2">
<div class="progress-bar bg-success" id="dryingProgress" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<p class="mb-0">Time Set: <span id="dryingTimeDisplay">--</span></p>
<p class="mb-0">Remaining Time: <span id="remainingTimeDisplay">--</span></p>
</div>
</div>
</div>
</div>
</div>
<!--end page content -->
</div>
</div>
<!--end page wrapper -->
<!-- search modal -->
<div class="modal" id="SearchModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-fullscreen-md-down">
<div class="modal-content">
<div class="modal-header gap-2">
<div class="position-relative popup-search w-100">
<input class="form-control form-control-lg ps-5 border border-3 border-primary" type="search" placeholder="Search">
<span class="position-absolute top-50 search-show ms-3 translate-middle-y start-0 top-50 fs-4"><i class='bx bx-search'></i></span>
</div>
<button type="button" class="btn-close d-md-none" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
</div>
</div>
</div>
<!-- end search modal -->
<!-- Bootstrap JS -->
<script src="../assets/js/bootstrap.bundle.min.js"></script>
<!--plugins-->
<script src="../assets/js/jquery.min.js"></script>
<script src="../assets/plugins/simplebar/js/simplebar.min.js"></script>
<script src="../assets/plugins/metismenu/js/metisMenu.min.js"></script>
<script src="../assets/plugins/perfect-scrollbar/js/perfect-scrollbar.js"></script>
<script src="../assets/plugins/apexcharts-bundle/js/apexcharts.min.js"></script>
<!--app JS-->
<script src="../assets/js/app.js"></script>
<script src="../assets/js/index.js"></script>
<script src="../assets/plugins/peity/jquery.peity.min.js"></script>
<script>
$(".data-attributes span").peity("donut")
</script>
<script>
document.addEventListener("DOMContentLoaded", function () {
const ESP32_IP = 'https://filamentdry.hickmeister.uk'; // Replace with your ESP32 IP
let tempChart, humidityChart;
// Fetch ESP32 Status Data
function fetchDryerStatus() {
fetch(`${ESP32_IP}/status`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
if (data.dryerOn !== undefined) {
updateTemperatureCard(data);
updateHumidityCard(data);
updateStatusCards(data);
updateProgressBar(data); // Update progress bar and time displays
} else {
console.error("Dryer offline or no data available");
showOfflineStatus();
}
})
.catch(error => {
console.error("Failed to fetch dryer status:", error);
showOfflineStatus();
});
}
// Control Dryer
function controlDryer(dryerOn, targetTemperature, dryingTimeInMinutes) {
const formData = new URLSearchParams();
formData.append("dryerOn", dryerOn);
formData.append("targetTemperature", targetTemperature);
formData.append("dryingTime", dryingTimeInMinutes);
fetch(`${ESP32_IP}/control`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: formData.toString(),
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log('Dryer control updated:', data);
fetchDryerStatus(); // Refresh status after control update
})
.catch(error => {
console.error('Failed to update dryer control:', error);
});
}
// Prevent Form Submission Refresh
const dryerControlForm = document.getElementById("dryerControlForm");
if (dryerControlForm) {
dryerControlForm.addEventListener("submit", function (event) {
event.preventDefault();
// Collect form data
const formData = new FormData(dryerControlForm);
const dryerOn = true; // Always true when starting the dryer
const filamentPreset = formData.get("filamentPreset");
const dryingTimeInHours = parseFloat(formData.get("dryingTime"));
const targetTemperature = parseFloat(formData.get("temperature"));
// Validate Input
if (!filamentPreset || isNaN(dryingTimeInHours) || isNaN(targetTemperature)) {
alert("Please complete all required fields.");
return;
}
// Convert drying time to minutes
const dryingTimeInMinutes = Math.round(dryingTimeInHours * 60);
// Control Dryer
controlDryer(dryerOn, targetTemperature, dryingTimeInMinutes);
});
}
// Handle Turn Off Dryer
const dryerOffButton = document.getElementById("dryerOff");
if (dryerOffButton) {
dryerOffButton.addEventListener("click", function () {
controlDryer(false, 0, 0); // Turn off the dryer
});
}
// Auto-Fill Temperature Input Based on Filament Preset
const filamentPresetSelect = document.getElementById("filamentPreset");
const temperatureInput = document.getElementById("temperature");
if (filamentPresetSelect && temperatureInput) {
filamentPresetSelect.addEventListener("change", function () {
const selectedOption = filamentPresetSelect.options[filamentPresetSelect.selectedIndex];
const presetTemperature = selectedOption.getAttribute("data-temp");
if (presetTemperature) {
temperatureInput.value = presetTemperature; // Auto-fill temperature input
} else {
temperatureInput.value = ""; // Clear the temperature input for "Custom"
}
});
}
// Update Progress Bar and Time Display
function updateProgressBar(data) {
const progressBar = document.getElementById("dryingProgress");
const dryingTimeEl = document.getElementById("dryingTimeDisplay"); // Total drying time display
const remainingTimeDisplay = document.getElementById("remainingTimeDisplay"); // Remaining time display
const dryingTime = data.dryingTime || 0; // Total drying time in minutes
const remainingTime = data.remainingTime || 0; // Remaining time in minutes
// Reset progress bar if the dryer is turned off
if (!data.dryerOn) {
if (progressBar) {
progressBar.style.width = "0%";
progressBar.setAttribute("aria-valuenow", 0);
}
if (dryingTimeEl) {
dryingTimeEl.innerText = "Total: 0h 0m";
}
if (remainingTimeDisplay) {
remainingTimeDisplay.innerText = "Remaining: 0h 0m";
}
return; // Exit early as no further updates are needed
}
// Update progress bar when the dryer is on
if (progressBar && dryingTime > 0) {
const progressPercent = ((dryingTime - remainingTime) / dryingTime) * 100;
progressBar.style.width = `${progressPercent}%`;
progressBar.setAttribute("aria-valuenow", progressPercent.toFixed(0));
}
// Update time displays
if (dryingTimeEl) {
dryingTimeEl.innerText = `Total: ${convertToHoursAndMinutes(dryingTime)}`;
}
if (remainingTimeDisplay) {
remainingTimeDisplay.innerText = `Remaining: ${convertToHoursAndMinutes(remainingTime)}`;
}
}
// Convert minutes to hours and minutes
function convertToHoursAndMinutes(minutes) {
const hours = Math.floor(minutes / 60);
const mins = minutes % 60;
return `${hours}h ${mins}m`;
}
// Update Temperature Card and Chart
function updateTemperatureCard(data) {
const temperature = data.temperature.toFixed(1); // Limit temperature to 1 decimal place
const tempHistory = data.tempHistory.map(temp => parseFloat(temp.toFixed(1))); // Limit history data
const timestamps = Array.from({ length: tempHistory.length }, (_, i) => `${i + 1}`);
document.getElementById('temperatureValue').innerText = `${temperature}°C`;
const options = {
series: [{ name: "Temperature", data: tempHistory }],
chart: {
type: 'area',
height: 150,
toolbar: { show: false },
sparkline: { enabled: true },
id: 'tempChart',
},
stroke: { curve: 'smooth' },
xaxis: { categories: timestamps, labels: { show: false } },
tooltip: {
theme: 'dark',
x: { show: false },
y: { formatter: value => `${value.toFixed(1)}°C` },
},
};
if (!tempChart) {
tempChart = new ApexCharts(document.querySelector("#tempChart"), options);
tempChart.render();
} else {
tempChart.updateSeries([{ data: tempHistory }]);
}
}
// Update Humidity Card and Chart
function updateHumidityCard(data) {
const humidity = data.humidity.toFixed(1);
const humidityHistory = data.humidityHistory || [];
const timestamps = Array.from({ length: humidityHistory.length }, (_, i) => `${i + 1}`);
document.getElementById('humidityValue').innerText = `${humidity}%`;
const options = {
series: [{ name: "Humidity", data: humidityHistory }],
chart: {
type: 'area',
height: 150,
toolbar: { show: false },
sparkline: { enabled: true },
id: 'humidityChart',
},
stroke: { curve: 'smooth' },
xaxis: { categories: timestamps, labels: { show: false } },
tooltip: {
theme: 'dark',
x: { show: false },
y: { formatter: value => `${value.toFixed(1)}%` },
},
};
if (!humidityChart) {
humidityChart = new ApexCharts(document.querySelector("#humidityChart"), options);
humidityChart.render();
} else {
humidityChart.updateSeries([{ data: humidityHistory }]);
}
}
// Update Status Cards
function updateStatusCards(data) {
const dryerStatusCard = document.querySelector('.card:nth-child(1)');
const heaterStatusCard = document.getElementById('heaterRelayStatus');
const fanStatusCard = document.getElementById('fanRelayStatus');
const dryerState = data.dryerOn ? "On" : "Off";
const fanRelayState = data.fanRelayState ? "On" : "Off";
const heaterRelayState = data.heaterRelayState ? "On" : "Off";
if (dryerStatusCard) {
dryerStatusCard.classList.toggle("bg-success", dryerState === "On");
dryerStatusCard.classList.toggle("bg-danger", dryerState === "Off");
dryerStatusCard.querySelector("h5").innerText = dryerState;
}
if (heaterStatusCard) {
heaterStatusCard.classList.toggle("bg-success", heaterRelayState === "On");
heaterStatusCard.classList.toggle("bg-danger", heaterRelayState === "Off");
heaterStatusCard.querySelector("h5").innerText = heaterRelayState;
}
if (fanStatusCard) {
fanStatusCard.classList.toggle("bg-success", fanRelayState === "On");
fanStatusCard.classList.toggle("bg-danger", fanRelayState === "Off");
fanStatusCard.querySelector("h5").innerText = fanRelayState;
}
}
// Show Offline Status
function showOfflineStatus() {
const dryerStatusCard = document.querySelector('.card:nth-child(1)');
const heaterStatusCard = document.getElementById('heaterRelayStatus');
const fanStatusCard = document.getElementById('fanRelayStatus');
const progressBar = document.getElementById("dryingProgress");
if (dryerStatusCard) {
dryerStatusCard.classList.remove("bg-success");
dryerStatusCard.classList.add("bg-danger");
dryerStatusCard.querySelector("h5").innerText = "Offline";
}
if (heaterStatusCard) {
heaterStatusCard.classList.remove("bg-success");
heaterStatusCard.classList.add("bg-danger");
heaterStatusCard.querySelector("h5").innerText = "Offline";
}
if (fanStatusCard) {
fanStatusCard.classList.remove("bg-success");
fanStatusCard.classList.add("bg-danger");
fanStatusCard.querySelector("h5").innerText = "Offline";
}
if (progressBar) {
progressBar.style.width = "0%";
progressBar.setAttribute("aria-valuenow", 0);
}
}
// Fetch data every 10 seconds
fetchDryerStatus();
setInterval(fetchDryerStatus, 10000);
});
</script>
</body>
</html>

View File

@@ -21,6 +21,8 @@
</a>
</li>
<!-- Printing Section -->
<li class="menu-label">Printing Control</li>
<li>
<a href="viewPrinters.php">
<div class="parent-icon"><i class='bx bx-code-alt'></i>
@@ -28,6 +30,13 @@
<div class="menu-title">Printer Dashboard</div>
</a>
</li>
<li>
<a href="filamentDryer.php">
<div class="parent-icon"><i class='bx bx-plus-circle'></i>
</div>
<div class="menu-title">Dryer Control</div>
</a>
</li>
<!-- Filament Tracking Section -->
<li class="menu-label">Filament Tracker</li>