Compare commits

...

35 Commits

Author SHA1 Message Date
ab33f423c8 chore: remove deprecated release script
Some checks failed
Create Release / build (push) Has been cancelled
2025-02-16 10:34:56 +01:00
15600ceac5 feat: enhance release script to support upstream push and improve error handling 2025-02-16 10:31:00 +01:00
7ccacec68f feat: add GitHub Actions workflow for automated release creation and update CHANGELOG.md structure 2025-02-16 10:26:43 +01:00
2f34a0ca4e feat: update HTML structure and add version display in the navbar 2025-02-16 10:26:36 +01:00
2fb2a2f183 refactor: simplify NFC tag assignment logic and enhance info message styling 2025-02-16 09:29:09 +01:00
269f54b2b3 refactor: clean up mqtt_callback function by removing commented-out code and redundant processing 2025-02-15 15:44:20 +01:00
1c1043ac75 feat: add calibration index handling to tray data and update related API and UI components 2025-02-15 13:30:25 +01:00
d1f22c78f7 feat: implement tray ID handling and add bambu_restart function for MQTT management 2025-02-15 09:05:22 +01:00
3e1490cafc fix: update calibration index check in displayAmsData function 2025-02-15 09:05:11 +01:00
dc82c04a17 feat: enhance reconnect functionality and improve UI feedback for connection status 2025-02-15 09:01:23 +01:00
c6fd5f8ad5 fix: remove unnecessary SPIFFS creation configuration from platformio.ini 2025-02-15 07:17:10 +01:00
a55cce854e refactor: remove combined binary creation script and related configurations 2025-02-15 07:14:12 +01:00
c21be92e98 fix: add calibration index to tray properties and update related logic 2025-02-15 07:14:03 +01:00
d4348944fc fix: update weight and length formatting in statistics display 2025-02-14 19:43:06 +01:00
f1bd896cf1 fix: improve reconnect logic by replacing delay with yield and vTaskDelay 2025-02-14 17:58:12 +01:00
9ace9f0567 fix: enhance Bambu spool handling by adding tray info index and improving logging 2025-02-14 17:40:13 +01:00
0379c4c45f fix: correct label for filament index in tray properties 2025-02-14 16:49:46 +01:00
49f36bb25b fix: update MQTT connection logic to ensure proper task creation and connection handling 2025-02-14 16:46:16 +01:00
a05cde8669 fix: add additional Bambu spool settings and update API response structure 2025-02-14 16:29:46 +01:00
64e5a171b6 fix: update MQTT connection logic to handle failures correctly and not try to connect random 2025-02-14 16:01:56 +01:00
bec403ec1b correct typo 2025-02-14 12:58:12 +01:00
ccc810ab7e fix: update README with German video link and correct About page link in header 2025-02-14 12:50:51 +01:00
61dadc0aa1 fix: comment out combine_binaries target in PlatformIO configuration 2025-02-14 10:50:04 +01:00
a2ee9a98a6 fix: update Bambu spool settings and connection handling logic 2025-02-14 10:35:47 +01:00
ae96c729b1 Corrected Amazon Links in Readme 2025-02-13 21:40:41 +01:00
1d337dd990 add PlatformIO configuration file for ESP32 development with custom build targets 2025-02-13 20:59:07 +01:00
cfa6c12d65 add script to combine firmware and SPIFFS binaries, and remove unused display files 2025-02-13 18:42:34 +01:00
b2aeae5815 add link to image folder and FilaMan website in README for additional resources 2025-02-13 17:39:41 +01:00
5cdbc5f2cc add new image assets for enhanced visual content 2025-02-13 17:37:17 +01:00
7a5415ad80 add FilaMan website link to README for additional resource 2025-02-13 16:57:07 +01:00
36fd271d0c remove unused image files and update scale image for clarity 2025-02-13 16:24:49 +01:00
a39e199c79 update "Buy Me A Coffee" button size in README for better visibility 2025-02-13 16:22:52 +01:00
b7004f5b71 add scale image to README for enhanced visual representation 2025-02-13 16:19:42 +01:00
e5bf05ee43 refactor header and RFID display logic, add spool out functionality, and update image assets 2025-02-13 16:13:46 +01:00
f01a42d850 implement AMS data sending every minute and remove unused CSS file 2025-02-13 13:38:15 +01:00
44 changed files with 1269 additions and 673 deletions

33
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,33 @@
name: Create Release
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Get version from tag
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\/v/}
- name: Read CHANGELOG.md
id: changelog
run: |
CHANGELOG=$(awk "/## \[${{ steps.get_version.outputs.VERSION }}\]/{p=1;print;next} /## \[/{p=0} p" CHANGELOG.md)
echo "::set-output name=CHANGES::$CHANGELOG"
- name: Create Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ steps.get_version.outputs.VERSION }}
body: ${{ steps.changelog.outputs.CHANGES }}
draft: false
prerelease: false

14
CHANGELOG.md Normal file
View File

@ -0,0 +1,14 @@
# Changelog
## [1.0.2] - 2025-02-16
### Added
- Feature 1
- Feature 2
### Changed
- Change 1
- Change 2
### Fixed
- Fix 1
- Fix 2

View File

@ -4,6 +4,13 @@ FilaMan is a filament management system for 3D printing. It uses ESP32 hardware
Users can manage filament spools, monitor the status of the Automatic Material System (AMS) and make settings via a web interface.
The system integrates seamlessly with [Bambulab](https://bambulab.com/en-us) 3D printers and [Spoolman](https://github.com/Donkie/Spoolman) filament management as well as the [Openspool](https://github.com/spuder/OpenSpool) NFC-TAG format.
![Scale](./img/scale_trans.png)
More Images can be found in the [img Folder](/img/)
or my website:[FilaMan Website](https://www.filaman.app)
german explanatory video: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaOHU)
### ESP32 Hardware Features
- **Weight Measurement:** Using a load cell with HX711 amplifier for precise weight tracking.
- **NFC Tag Reading/Writing:** PN532 module for reading and writing filament data to NFC tags.
@ -29,7 +36,7 @@ The system integrates seamlessly with [Bambulab](https://bambulab.com/en-us) 3D
- Track NFC tag assignments.
### If you want to support my work, i would be happy to get a coffe
<a href="https://www.buymeacoffee.com/manuelw" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
<a href="https://www.buymeacoffee.com/manuelw" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 30px !important;width: 108px !important;" ></a>
## Detailed Functionality
@ -46,14 +53,15 @@ The system integrates seamlessly with [Bambulab](https://bambulab.com/en-us) 3D
### Components
- **ESP32 Development Board:** Any ESP32 variant.
[https://amzn.eu/d/aXThslf](url)
[Amazon Link](https://amzn.eu/d/aXThslf)
- **HX711 Load Cell Amplifier:** For weight measurement.
[https://amzn.eu/d/1wZ4v0x](url)
[Amazon Link](https://amzn.eu/d/1wZ4v0x)
- **OLED Display:** 128x64 SSD1306.
[https://amzn.eu/d/dozAYDU](url)
[Amazon Link](https://amzn.eu/d/dozAYDU)
- **PN532 NFC Module:** For NFC tag operations.
[https://amzn.eu/d/8205DDh](url)
- **NFC-Tag:** [https://amzn.eu/d/fywy4c4](url)
[Amazon Link](https://amzn.eu/d/8205DDh)
- **NFC-Tag:** NTAG215
[Amazon Link](https://amzn.eu/d/fywy4c4)
### Pin Configuration
@ -147,4 +155,4 @@ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file
The code can be tested and the application can be downloaded from the [GitHub repository](https://github.com/ManuelW77/Filaman).
### If you want to support my work, i would be happy to get a coffe
<a href="https://www.buymeacoffee.com/manuelw" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
<a href="https://www.buymeacoffee.com/manuelw" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 30px !important;width: 108px !important;" ></a>

View File

View File

View File

@ -6,14 +6,13 @@
<title>FilaMan - Filament Management Tool</title>
<link rel="icon" type="image/png" href="/favicon.ico">
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="style2.css">
</head>
<body>
<div class="navbar">
<div style="display: flex; align-items: center; gap: 2rem;">
<img src="/logo.png" alt="FilaMan Logo" class="logo">
<div class="logo-text">
<h1>FilaMan</h1>
<h1>FilaMan<span class="version">v1.0.2</span></h1>
<h4>Filament Management Tool</h4>
</div>
</div>
@ -21,7 +20,7 @@
<a href="/">Start</a>
<a href="/waage">Scale</a>
<a href="/spoolman">Spoolman/Bambu</a>
<a href="/">About</a>
<a href="/about">About</a>
</nav>
<div class="status-container">
<div class="status-item">
@ -33,3 +32,4 @@
<div class="ram-status" id="ramStatus"></div>
</div>
</div>

View File

@ -1,4 +1,39 @@
{{header}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FilaMan - Filament Management Tool</title>
<link rel="icon" type="image/png" href="/favicon.ico">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="navbar">
<div style="display: flex; align-items: center; gap: 2rem;">
<img src="/logo.png" alt="FilaMan Logo" class="logo">
<div class="logo-text">
<h1>FilaMan<span class="version">v1.0.2</span></h1>
<h4>Filament Management Tool</h4>
</div>
</div>
<nav style="display: flex; gap: 1rem;">
<a href="/">Start</a>
<a href="/waage">Scale</a>
<a href="/spoolman">Spoolman/Bambu</a>
<a href="/about">About</a>
</nav>
<div class="status-container">
<div class="status-item">
<span class="status-dot" id="bambuDot"></span>B
</div>
<div class="status-item">
<span class="status-dot" id="spoolmanDot"></span>S
</div>
<div class="ram-status" id="ramStatus"></div>
</div>
</div>
<div class="container">
<h1>FilaMan</h1>

View File

@ -1,4 +1,39 @@
{{header}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FilaMan - Filament Management Tool</title>
<link rel="icon" type="image/png" href="/favicon.ico">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="navbar">
<div style="display: flex; align-items: center; gap: 2rem;">
<img src="/logo.png" alt="FilaMan Logo" class="logo">
<div class="logo-text">
<h1>FilaMan<span class="version">v1.0.2</span></h1>
<h4>Filament Management Tool</h4>
</div>
</div>
<nav style="display: flex; gap: 1rem;">
<a href="/">Start</a>
<a href="/waage">Scale</a>
<a href="/spoolman">Spoolman/Bambu</a>
<a href="/about">About</a>
</nav>
<div class="status-container">
<div class="status-item">
<span class="status-dot" id="bambuDot"></span>B
</div>
<div class="status-item">
<span class="status-dot" id="spoolmanDot"></span>S
</div>
<div class="ram-status" id="ramStatus"></div>
</div>
</div>
<div class="connection-status hidden">
<div class="spinner"></div>
<span>Connection lost. Trying to reconnect...</span>
@ -35,11 +70,11 @@
</li>
<li>
<span class="stat-label">Weight:</span>
<span class="stat-value"><span id="totalWeight"></span> kg</span>
<span class="stat-value"><span id="totalWeight"></span></span>
</li>
<li>
<span class="stat-label">Length:</span>
<span class="stat-value"><span id="totalLength"></span> m</span>
<span class="stat-value"><span id="totalLength"></span></span>
</li>
</ul>
</div>

View File

@ -112,9 +112,39 @@ function initWebSocket() {
if (bambuDot) {
bambuDot.className = 'status-dot ' + (data.bambu_connected ? 'online' : 'offline');
// Add click handler only when offline
if (!data.bambu_connected) {
bambuDot.style.cursor = 'pointer';
bambuDot.onclick = function() {
if (socket && socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({
type: 'reconnect',
payload: 'bambu'
}));
}
};
} else {
bambuDot.style.cursor = 'default';
bambuDot.onclick = null;
}
}
if (spoolmanDot) {
spoolmanDot.className = 'status-dot ' + (data.spoolman_connected ? 'online' : 'offline');
// Add click handler only when offline
if (!data.spoolman_connected) {
spoolmanDot.style.cursor = 'pointer';
spoolmanDot.onclick = function() {
if (socket && socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({
type: 'reconnect',
payload: 'spoolman'
}));
}
};
} else {
spoolmanDot.style.cursor = 'default';
spoolmanDot.onclick = null;
}
}
if (ramStatus) {
ramStatus.textContent = `${data.freeHeap}k`;
@ -209,16 +239,6 @@ function updateNfcInfo() {
`${spool.id} | ${spool.filament.name} (${spool.filament.material})` === selectedText
);
if (selectedSpool && selectedSpool.extra.nfc_id) {
nfcInfo.textContent = "NFC Tag assigned";
nfcInfo.classList.add("nfc-success");
nfcInfo.classList.remove("nfc-error");
} else {
nfcInfo.textContent = "No NFC-Tag assigned";
nfcInfo.classList.add("nfc-error");
nfcInfo.classList.remove("nfc-success");
}
if (selectedSpool) {
writeButton.classList.remove("hidden");
} else {
@ -236,7 +256,7 @@ function displayAmsData(amsData) {
const trayHTML = ams.tray.map(tray => {
// Prüfe ob überhaupt Daten vorhanden sind
const relevantFields = ['tray_type', 'tray_sub_brands', 'tray_info_idx', 'setting_id'];
const relevantFields = ['tray_type', 'tray_sub_brands', 'tray_info_idx', 'setting_id', 'cali_idx'];
const hasAnyContent = relevantFields.some(field =>
tray[field] !== null &&
tray[field] !== undefined &&
@ -244,23 +264,38 @@ function displayAmsData(amsData) {
tray[field] !== 'null'
);
if (!hasAnyContent) {
return `
<div class="tray">
<p class="tray-head">Tray ${tray.id}</p>
<p>Empty</p>
</div>
<hr>`;
}
// Bestimme den Anzeigenamen für das Tray
const trayDisplayName = (ams.ams_id === 255) ? 'External' : `Tray ${tray.id}`;
// Nur für nicht-leere Trays den Button-HTML erstellen
const buttonHtml = `
<button class="spool-button" onclick="handleSpoolIn(${ams.ams_id}, ${tray.id})"
style="position: absolute; top: -35px; left: -15px;
style="position: absolute; top: -30px; left: -15px;
background: none; border: none; padding: 0;
cursor: pointer; display: none;">
<img src="spool_in.png" alt="Spool In" style="width: 48px; height: 48px;">
</button>`;
// Nur für nicht-leere Trays den Button-HTML erstellen
const outButtonHtml = `
<button class="spool-button" onclick="handleSpoolOut()"
style="position: absolute; top: -35px; right: -15px;
background: none; border: none; padding: 0;
cursor: pointer; display: block;">
<img src="spool_in.png" alt="Spool In" style="width: 48px; height: 48px; transform: rotate(180deg) scaleX(-1);">
</button>`;
if (!hasAnyContent) {
return `
<div class="tray">
<p class="tray-head">${trayDisplayName}</p>
<p>
${(ams.ams_id === 255 && tray.tray_type === '') ? buttonHtml : ''}
Empty
</p>
</div>
<hr>`;
}
// Generiere den Type mit Color-Box zusammen
const typeWithColor = tray.tray_type ?
@ -277,8 +312,9 @@ function displayAmsData(amsData) {
// Array mit restlichen Tray-Eigenschaften
const trayProperties = [
{ key: 'tray_sub_brands', label: 'Sub Brands' },
{ key: 'tray_info_idx', label: 'Filament Index' },
{ key: 'setting_id', label: 'Setting ID' }
{ key: 'tray_info_idx', label: 'Filament IDX' },
{ key: 'setting_id', label: 'Setting ID' },
{ key: 'cali_idx', label: 'Calibration IDX' }
];
// Nur gültige Felder anzeigen
@ -289,7 +325,13 @@ function displayAmsData(amsData) {
tray[prop.key] !== '' &&
tray[prop.key] !== 'null'
)
.map(prop => `<p>${prop.label}: ${tray[prop.key]}</p>`)
.map(prop => {
// Spezielle Behandlung für setting_id
if (prop.key === 'cali_idx' && tray[prop.key] === '-1') {
return `<p>${prop.label}: not calibrated</p>`;
}
return `<p>${prop.label}: ${tray[prop.key]}</p>`;
})
.join('');
// Temperaturen nur anzeigen, wenn beide nicht 0 sind
@ -297,9 +339,6 @@ function displayAmsData(amsData) {
? `<p>Nozzle Temp: ${tray.nozzle_temp_min}°C - ${tray.nozzle_temp_max}°C</p>`
: '';
// Bestimme den Anzeigenamen für das Tray
const trayDisplayName = (ams.ams_id === 255) ? 'External' : `Tray ${tray.id}`;
return `
<div class="tray" ${tray.tray_color ? `style="border-left: 4px solid #${tray.tray_color};"` : 'style="border-left: 4px solid #007bff;"'}>
<div style="position: relative;">
@ -308,7 +347,9 @@ function displayAmsData(amsData) {
${typeWithColor}
${trayDetails}
${tempHTML}
${(ams.ams_id === 255 && tray.tray_type !== '') ? outButtonHtml : ''}
</div>
</div>`;
}).join('');
@ -332,6 +373,30 @@ function updateSpoolButtons(show) {
});
}
function handleSpoolOut() {
// Erstelle Payload
const payload = {
type: 'setBambuSpool',
payload: {
amsId: 255,
trayId: 254,
color: "FFFFFF",
nozzle_temp_min: 0,
nozzle_temp_max: 0,
type: "",
brand: ""
}
};
try {
socket.send(JSON.stringify(payload));
showNotification(`External Spool removed. Pls wait`, true);
} catch (error) {
console.error("Fehler beim Senden der WebSocket Nachricht:", error);
showNotification("Error while sending!", false);
}
}
// Neue Funktion zum Behandeln des Spool-In-Klicks
function handleSpoolIn(amsId, trayId) {
// Prüfe WebSocket Verbindung zuerst
@ -361,7 +426,7 @@ function handleSpoolIn(amsId, trayId) {
// Temperaturwerte extrahieren
let minTemp = "175";
let maxTemp = "275";
if (Array.isArray(selectedSpool.filament.nozzle_temperature) &&
selectedSpool.filament.nozzle_temperature.length >= 2) {
minTemp = selectedSpool.filament.nozzle_temperature[0];
@ -378,19 +443,30 @@ function handleSpoolIn(amsId, trayId) {
nozzle_temp_min: parseInt(minTemp),
nozzle_temp_max: parseInt(maxTemp),
type: selectedSpool.filament.material,
brand: selectedSpool.filament.vendor.name
brand: selectedSpool.filament.vendor.name,
tray_info_idx: selectedSpool.filament.extra.bambu_idx.replace(/['"]+/g, '').trim(),
cali_idx: "-1" // Default-Wert setzen
}
};
// Debug logging
console.log("Sende WebSocket Nachricht:", payload);
// Prüfe, ob der Key cali_idx vorhanden ist und setze ihn
if (selectedSpool.filament.extra.bambu_cali_id) {
payload.payload.cali_idx = selectedSpool.filament.extra.bambu_cali_id.replace(/['"]+/g, '').trim();
}
// Prüfe, ob der Key bambu_setting_id vorhanden ist
if (selectedSpool.filament.extra.bambu_setting_id) {
payload.payload.bambu_setting_id = selectedSpool.filament.extra.bambu_setting_id.replace(/['"]+/g, '').trim();
}
console.log("Spool-In Payload:", payload);
try {
socket.send(JSON.stringify(payload));
showNotification(`Spool set in AMS ${amsId} Tray ${trayId}. Pls wait`, true);
} catch (error) {
console.error("Fehler beim Senden der WebSocket Nachricht:", error);
showNotification("Fehler beim Senden der Daten", false);
showNotification("Error while sending", false);
}
}
@ -440,7 +516,7 @@ function updateNfcData(data) {
}
} else {
nfcDataDiv.innerHTML = '<div style="margin-top: 10px;"></div>';
nfcDataDiv.innerHTML = '<div class="info-message-inner" style="margin-top: 10px;"></div>';
}
nfcStatusContainer.appendChild(nfcDataDiv);
return;
@ -566,4 +642,4 @@ function showNotification(message, isSuccess) {
notification.remove();
}, 300);
}, 3000);
}
}

View File

@ -1,4 +1,39 @@
{{header}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FilaMan - Filament Management Tool</title>
<link rel="icon" type="image/png" href="/favicon.ico">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="navbar">
<div style="display: flex; align-items: center; gap: 2rem;">
<img src="/logo.png" alt="FilaMan Logo" class="logo">
<div class="logo-text">
<h1>FilaMan<span class="version">v1.0.2</span></h1>
<h4>Filament Management Tool</h4>
</div>
</div>
<nav style="display: flex; gap: 1rem;">
<a href="/">Start</a>
<a href="/waage">Scale</a>
<a href="/spoolman">Spoolman/Bambu</a>
<a href="/about">About</a>
</nav>
<div class="status-container">
<div class="status-item">
<span class="status-dot" id="bambuDot"></span>B
</div>
<div class="status-item">
<span class="status-dot" id="spoolmanDot"></span>S
</div>
<div class="ram-status" id="ramStatus"></div>
</div>
</div>
<script>
window.onload = function() {
if (spoolmanUrl && spoolmanUrl.trim() !== "") {

View File

@ -61,9 +61,6 @@ function populateVendorDropdown(data, selectedSmId = null) {
totalLength += spool.remaining_length;
}
console.log("Länge gesamt: " + spool.remaining_length);
console.log("Gewicht gesamt" + spool.remaining_weight);
const vendor = spool.filament.vendor;
const hasValidNfcId = spool.extra &&
@ -88,6 +85,18 @@ function populateVendorDropdown(data, selectedSmId = null) {
}
});
// Nach der Schleife: Formatierung der Gesamtlänge
console.log("Total Lenght: ", totalLength);
const formattedLength = totalLength > 1000
? (totalLength / 1000).toFixed(2) + " km"
: totalLength.toFixed(2) + " m";
// Formatierung des Gesamtgewichts (von g zu kg zu t)
const weightInKg = totalWeight / 1000; // erst in kg umrechnen
const formattedWeight = weightInKg > 1000
? (weightInKg / 1000).toFixed(2) + " t"
: weightInKg.toFixed(2) + " kg";
// Dropdown mit gefilterten Herstellern befüllen
Object.entries(filteredVendors).forEach(([id, name]) => {
const option = document.createElement("option");
@ -102,8 +111,8 @@ function populateVendorDropdown(data, selectedSmId = null) {
document.getElementById("totalVendors").textContent = Object.keys(allVendors).length;
// Neue Statistiken hinzufügen
document.getElementById("totalWeight").textContent = (totalWeight / 1000).toFixed(2);
document.getElementById("totalLength").textContent = (totalLength / 1000).toFixed(2);
document.getElementById("totalWeight").textContent = formattedWeight;
document.getElementById("totalLength").textContent = formattedLength;
// Material-Statistiken zum DOM hinzufügen
const materialsList = document.getElementById("materialsList");
@ -112,7 +121,7 @@ function populateVendorDropdown(data, selectedSmId = null) {
.sort(([,a], [,b]) => b - a) // Sortiere nach Anzahl absteigend
.forEach(([material, count]) => {
const li = document.createElement("li");
li.textContent = `${material}: ${count} ${count === 1 ? 'Spule' : 'Spulen'}`;
li.textContent = `${material}: ${count} ${count === 1 ? 'Spool' : 'Spools'}`;
materialsList.appendChild(li);
});

View File

@ -133,6 +133,15 @@ body {
background-color: #dc2626;
}
.status-dot.offline {
cursor: pointer;
}
.status-dot.offline:hover {
opacity: 0.8;
transform: scale(1.1);
}
.ram-status {
color: var(--inner-text-color);
font-size: 0.9em;
@ -471,11 +480,15 @@ a:hover {
.info-message {
padding: 10px;
background-color: #fff3f3;
background-color: var(--header-bg);
border-radius: 4px;
border-left: 4px solid #39d82e;
}
.info-message-inner {
background-color: var(--header-bg) !important;
}
.nfc-header {
display: grid;
grid-template-columns: 40px 1fr 40px;
@ -489,3 +502,515 @@ a:hover {
text-align: center;
color: var(--accent-color);
}
.nfc-header .status-circle {
grid-column: 3;
justify-self: end;
}
.content-header {
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.connection-status {
display: flex;
align-items: center;
gap: 10px;
background-color: #fff3f3;
border: 1px solid #dc3545;
border-radius: 4px;
padding: 10px 15px;
margin: 15px auto;
color: #dc3545;
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
max-width: 90%;
opacity: 0;
transition: opacity 0.3s ease;
}
.connection-status.visible {
opacity: 1;
}
.spinner {
flex-shrink: 0;
width: 16px;
height: 16px;
border: 2px solid rgba(220, 53, 69, 0.2);
border-top-color: #dc3545;
border-radius: 50%;
}
.connection-status.visible .spinner {
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.connection-status.hidden {
display: none;
}
.nfc-actions {
display: flex;
gap: 10px;
justify-content: center; /* Zentriert das div */
}
.btn {
padding: 8px 16px;
border-radius: 4px;
border: none;
cursor: pointer;
font-weight: bold;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn-primary:hover {
background-color: #0056b3;
}
.btn-danger {
background-color: #dc3545;
color: white;
}
.btn-danger:hover {
background-color: #c82333;
}
/* Filament Select Styling */
#filamentSelect {
width: 100%;
padding: 12px 15px;
font-size: 16px;
border: 2px solid #e0e0e0;
border-radius: 8px;
background-color: #fff;
cursor: pointer;
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23007bff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 15px center;
background-size: 15px;
transition: all 0.3s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
#filamentSelect:hover {
border-color: #007bff;
box-shadow: 0 3px 6px rgba(0, 123, 255, 0.1);
}
#filamentSelect:focus {
border-color: #007bff;
outline: none;
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25);
}
#filamentSelect option {
padding: 8px 15px;
font-size: 16px;
background-color: #fff;
color: #000; /* Standard Textfarbe für alles außer dem Farbblock */
}
#filamentSelect option::first-letter {
font-size: 16px;
margin-right: 5px;
}
#filamentSelect option::before {
content: '';
display: inline-block;
width: 12px;
margin-right: 8px;
}
/* Color Box im Select */
.color-box {
display: inline-block;
width: 12px;
height: 12px;
border: 1px solid #333;
border-radius: 2px;
margin-right: 5px;
vertical-align: middle;
}
#filamentSelect option span {
display: inline-block;
pointer-events: none;
}
#filamentSelect option span:first-child {
margin-right: 5px;
font-size: 16px;
}
/* Filament Select Option Styling */
#filamentSelect option span.color-circle {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 8px;
border: 1px solid #333;
vertical-align: middle;
}
/* Custom Dropdown */
.custom-dropdown {
position: relative;
width: 100%;
font-family: inherit;
cursor: default; /* Container selbst soll normalen Cursor haben */
color: black;
}
.dropdown-button {
padding: 12px 15px;
border: 2px solid #e0e0e0;
border-radius: 8px;
background-color: #fff;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.3s ease;
}
.dropdown-button:hover {
border-color: #007bff;
}
.selected-color {
width: 16px;
height: 16px;
border-radius: 50%;
border: 1px solid #333;
flex-shrink: 0;
}
.dropdown-arrow {
margin-left: auto;
color: #007bff;
font-size: 12px;
}
.dropdown-content {
display: none;
position: absolute;
top: 100%;
left: 0;
right: 0;
background-color: #fff;
border: 1px solid #e0e0e0;
border-radius: 8px;
margin-top: 4px;
max-height: 300px;
overflow-y: auto;
z-index: 1000;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.dropdown-content.show {
display: block;
}
.dropdown-option {
padding: 10px 15px;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
}
.dropdown-option:hover {
background-color: #f8f9fa;
}
.option-color {
width: 16px;
height: 16px;
border-radius: 50%;
border: 1px solid #333;
flex-shrink: 0;
}
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 25px;
border-radius: 4px;
color: white;
z-index: 1000;
animation: slideIn 0.3s ease-out;
}
.notification.success {
background-color: #28a745;
}
.notification.error {
background-color: #dc3545;
}
.notification.fade-out {
opacity: 0;
transition: opacity 0.3s ease-out;
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
/* Neue Styles für die Statistiken */
.statistics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1rem;
margin-top: 15px;
}
.statistics-column {
background: var(--inner-box-bg);
border: 1px solid rgba(99, 187, 103, 0.2);
border-radius: 0.5rem;
}
.statistics-column h3 {
color: var(--inner-text-color);
border-bottom: 1px solid rgba(99, 187, 103, 0.2);
font-size: 1.1rem;
}
.statistics-list {
list-style: none;
padding: 0;
margin: 0;
}
.statistics-list li {
display: flex;
justify-content: space-between;
padding: 0.5rem;
border-bottom: 1px solid rgba(99, 187, 103, 0.1);
}
.statistics-list li:last-child {
border-bottom: none;
}
.stat-label {
color: var(--inner-text-color);
}
.stat-value {
color: var(--stat-value-color);
font-weight: 500;
}
/* Responsive Design Anpassung */
@media (max-width: 768px) {
.statistics-grid {
grid-template-columns: 1fr;
}
}
.statistics-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 0;
border-bottom: 1px solid #e9ecef;
}
.refresh-button {
display: flex;
align-items: center;
padding: 8px 16px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s;
}
.refresh-button:hover {
background-color: #0056b3;
}
.refresh-button:active {
background-color: #004494;
}
.spools-info {
display: flex;
justify-content: flex-start;
gap: 20px;
margin-bottom: 15px;
}
.spool-stat {
display: flex;
align-items: center;
gap: 8px;
background: var(--inner-box-bg);
border: 1px solid rgba(99, 187, 103, 0.2);
border-radius: 0.5rem;
}
.spool-stat .stat-label {
color: var(--inner-text-color);
}
.spool-stat .stat-value {
color: var(--stat-value-color);
font-weight: 500;
}
/* Buttons und klickbare Elemente */
button,
input[type="submit"],
.dropdown-button,
.dropdown-option,
.refresh-button,
.btn,
.styled-select,
select,
a {
cursor: pointer !important;
}
/* Disabled Zustände */
button:disabled,
input[type="submit"]:disabled,
.btn:disabled,
.styled-select:disabled {
cursor: not-allowed !important;
opacity: 0.7;
}
/* Schreib-Button */
#writeNfcButton {
background-color: #007bff;
color: white;
transition: background-color 0.3s, color 0.3s;
width: 160px;
}
#writeNfcButton.writing {
background-color: #ffc107;
color: black;
width: 160px;
}
#writeNfcButton.success {
background-color: #28a745;
color: white;
width: 160px;
}
#writeNfcButton.error {
background-color: #dc3545;
color: white;
width: 160px;
}
@keyframes dots {
0% { content: ""; }
33% { content: "."; }
66% { content: ".."; }
100% { content: "..."; }
}
#writeNfcButton.writing::after {
content: "...";
animation: dots 1s steps(3, end) infinite;
}
/* Bambu Settings Erweiterung */
.bambu-settings {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
max-width: 400px;
margin: 20px auto;
}
.bambu-settings .input-group {
margin-bottom: 15px;
text-align: left;
}
.bambu-settings .input-group label {
display: block;
margin-bottom: 5px;
}
.bambu-settings .input-group input {
width: 95%;
}
#bambuStatusMessage {
margin-top: 15px;
display: flex;
align-items: center;
gap: 10px;
justify-content: center;
}
.amsData {
border-color: black !important;
border-width: 1px !important;
}
.tray {
position: relative;
}
.tray-head {
color: var(--stat-value-color) !important;
text-align: center !important;
font-weight: bold !important;
}
.spool-button:hover {
opacity: 0.8;
}
.version {
font-size: 0.4em;
color: #000;
vertical-align: middle;
margin-left: 0.5rem;
}

View File

@ -1,505 +0,0 @@
.nfc-header .status-circle {
grid-column: 3;
justify-self: end;
}
.content-header {
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.connection-status {
display: flex;
align-items: center;
gap: 10px;
background-color: #fff3f3;
border: 1px solid #dc3545;
border-radius: 4px;
padding: 10px 15px;
margin: 15px auto;
color: #dc3545;
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
max-width: 90%;
opacity: 0;
transition: opacity 0.3s ease;
}
.connection-status.visible {
opacity: 1;
}
.spinner {
flex-shrink: 0;
width: 16px;
height: 16px;
border: 2px solid rgba(220, 53, 69, 0.2);
border-top-color: #dc3545;
border-radius: 50%;
}
.connection-status.visible .spinner {
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.connection-status.hidden {
display: none;
}
.nfc-actions {
display: flex;
gap: 10px;
justify-content: center; /* Zentriert das div */
}
.btn {
padding: 8px 16px;
border-radius: 4px;
border: none;
cursor: pointer;
font-weight: bold;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn-primary:hover {
background-color: #0056b3;
}
.btn-danger {
background-color: #dc3545;
color: white;
}
.btn-danger:hover {
background-color: #c82333;
}
/* Filament Select Styling */
#filamentSelect {
width: 100%;
padding: 12px 15px;
font-size: 16px;
border: 2px solid #e0e0e0;
border-radius: 8px;
background-color: #fff;
cursor: pointer;
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23007bff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 15px center;
background-size: 15px;
transition: all 0.3s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
#filamentSelect:hover {
border-color: #007bff;
box-shadow: 0 3px 6px rgba(0, 123, 255, 0.1);
}
#filamentSelect:focus {
border-color: #007bff;
outline: none;
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25);
}
#filamentSelect option {
padding: 8px 15px;
font-size: 16px;
background-color: #fff;
color: #000; /* Standard Textfarbe für alles außer dem Farbblock */
}
#filamentSelect option::first-letter {
font-size: 16px;
margin-right: 5px;
}
#filamentSelect option::before {
content: '';
display: inline-block;
width: 12px;
margin-right: 8px;
}
/* Color Box im Select */
.color-box {
display: inline-block;
width: 12px;
height: 12px;
border: 1px solid #333;
border-radius: 2px;
margin-right: 5px;
vertical-align: middle;
}
#filamentSelect option span {
display: inline-block;
pointer-events: none;
}
#filamentSelect option span:first-child {
margin-right: 5px;
font-size: 16px;
}
/* Filament Select Option Styling */
#filamentSelect option span.color-circle {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 8px;
border: 1px solid #333;
vertical-align: middle;
}
/* Custom Dropdown */
.custom-dropdown {
position: relative;
width: 100%;
font-family: inherit;
cursor: default; /* Container selbst soll normalen Cursor haben */
color: black;
}
.dropdown-button {
padding: 12px 15px;
border: 2px solid #e0e0e0;
border-radius: 8px;
background-color: #fff;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.3s ease;
}
.dropdown-button:hover {
border-color: #007bff;
}
.selected-color {
width: 16px;
height: 16px;
border-radius: 50%;
border: 1px solid #333;
flex-shrink: 0;
}
.dropdown-arrow {
margin-left: auto;
color: #007bff;
font-size: 12px;
}
.dropdown-content {
display: none;
position: absolute;
top: 100%;
left: 0;
right: 0;
background-color: #fff;
border: 1px solid #e0e0e0;
border-radius: 8px;
margin-top: 4px;
max-height: 300px;
overflow-y: auto;
z-index: 1000;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.dropdown-content.show {
display: block;
}
.dropdown-option {
padding: 10px 15px;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
}
.dropdown-option:hover {
background-color: #f8f9fa;
}
.option-color {
width: 16px;
height: 16px;
border-radius: 50%;
border: 1px solid #333;
flex-shrink: 0;
}
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 25px;
border-radius: 4px;
color: white;
z-index: 1000;
animation: slideIn 0.3s ease-out;
}
.notification.success {
background-color: #28a745;
}
.notification.error {
background-color: #dc3545;
}
.notification.fade-out {
opacity: 0;
transition: opacity 0.3s ease-out;
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
/* Neue Styles für die Statistiken */
.statistics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1rem;
margin-top: 15px;
}
.statistics-column {
background: var(--inner-box-bg);
border: 1px solid rgba(99, 187, 103, 0.2);
border-radius: 0.5rem;
}
.statistics-column h3 {
color: var(--inner-text-color);
border-bottom: 1px solid rgba(99, 187, 103, 0.2);
font-size: 1.1rem;
}
.statistics-list {
list-style: none;
padding: 0;
margin: 0;
}
.statistics-list li {
display: flex;
justify-content: space-between;
padding: 0.5rem;
border-bottom: 1px solid rgba(99, 187, 103, 0.1);
}
.statistics-list li:last-child {
border-bottom: none;
}
.stat-label {
color: var(--inner-text-color);
}
.stat-value {
color: var(--stat-value-color);
font-weight: 500;
}
/* Responsive Design Anpassung */
@media (max-width: 768px) {
.statistics-grid {
grid-template-columns: 1fr;
}
}
.statistics-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 0;
border-bottom: 1px solid #e9ecef;
}
.refresh-button {
display: flex;
align-items: center;
padding: 8px 16px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s;
}
.refresh-button:hover {
background-color: #0056b3;
}
.refresh-button:active {
background-color: #004494;
}
.spools-info {
display: flex;
justify-content: flex-start;
gap: 20px;
margin-bottom: 15px;
}
.spool-stat {
display: flex;
align-items: center;
gap: 8px;
background: var(--inner-box-bg);
border: 1px solid rgba(99, 187, 103, 0.2);
border-radius: 0.5rem;
}
.spool-stat .stat-label {
color: var(--inner-text-color);
}
.spool-stat .stat-value {
color: var(--stat-value-color);
font-weight: 500;
}
/* Buttons und klickbare Elemente */
button,
input[type="submit"],
.dropdown-button,
.dropdown-option,
.refresh-button,
.btn,
.styled-select,
select,
a {
cursor: pointer !important;
}
/* Disabled Zustände */
button:disabled,
input[type="submit"]:disabled,
.btn:disabled,
.styled-select:disabled {
cursor: not-allowed !important;
opacity: 0.7;
}
/* Schreib-Button */
#writeNfcButton {
background-color: #007bff;
color: white;
transition: background-color 0.3s, color 0.3s;
width: 160px;
}
#writeNfcButton.writing {
background-color: #ffc107;
color: black;
width: 160px;
}
#writeNfcButton.success {
background-color: #28a745;
color: white;
width: 160px;
}
#writeNfcButton.error {
background-color: #dc3545;
color: white;
width: 160px;
}
@keyframes dots {
0% { content: ""; }
33% { content: "."; }
66% { content: ".."; }
100% { content: "..."; }
}
#writeNfcButton.writing::after {
content: "...";
animation: dots 1s steps(3, end) infinite;
}
/* Bambu Settings Erweiterung */
.bambu-settings {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
max-width: 400px;
margin: 20px auto;
}
.bambu-settings .input-group {
margin-bottom: 15px;
text-align: left;
}
.bambu-settings .input-group label {
display: block;
margin-bottom: 5px;
}
.bambu-settings .input-group input {
width: 95%;
}
#bambuStatusMessage {
margin-top: 15px;
display: flex;
align-items: center;
gap: 10px;
justify-content: center;
}
.amsData {
border-color: black !important;
border-width: 1px !important;
}
.tray {
position: relative;
}
.tray-head {
color: var(--stat-value-color) !important;
text-align: center !important;
font-weight: bold !important;
}
.spool-button:hover {
opacity: 0.8;
}

View File

@ -1,4 +1,39 @@
{{header}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FilaMan - Filament Management Tool</title>
<link rel="icon" type="image/png" href="/favicon.ico">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="navbar">
<div style="display: flex; align-items: center; gap: 2rem;">
<img src="/logo.png" alt="FilaMan Logo" class="logo">
<div class="logo-text">
<h1>FilaMan<span class="version">v1.0.2</span></h1>
<h4>Filament Management Tool</h4>
</div>
</div>
<nav style="display: flex; gap: 1rem;">
<a href="/">Start</a>
<a href="/waage">Scale</a>
<a href="/spoolman">Spoolman/Bambu</a>
<a href="/about">About</a>
</nav>
<div class="status-container">
<div class="status-item">
<span class="status-dot" id="bambuDot"></span>B
</div>
<div class="status-item">
<span class="status-dot" id="spoolmanDot"></span>S
</div>
<div class="ram-status" id="ramStatus"></div>
</div>
</div>
<div class="content">
<h1>Scale Configuration Page</h1>

View File

@ -1,4 +1,39 @@
{{header}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FilaMan - Filament Management Tool</title>
<link rel="icon" type="image/png" href="/favicon.ico">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="navbar">
<div style="display: flex; align-items: center; gap: 2rem;">
<img src="/logo.png" alt="FilaMan Logo" class="logo">
<div class="logo-text">
<h1>FilaMan<span class="version">v1.0.2</span></h1>
<h4>Filament Management Tool</h4>
</div>
</div>
<nav style="display: flex; gap: 1rem;">
<a href="/">Start</a>
<a href="/waage">Scale</a>
<a href="/spoolman">Spoolman/Bambu</a>
<a href="/about">About</a>
</nav>
<div class="status-container">
<div class="status-item">
<span class="status-dot" id="bambuDot"></span>B
</div>
<div class="status-item">
<span class="status-dot" id="spoolmanDot"></span>S
</div>
<div class="ram-status" id="ramStatus"></div>
</div>
</div>
<div class="content">
<h1>WiFi Configuration Page</h1>
<form action="/setToken" method="post">

BIN
img/display_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 KiB

BIN
img/display_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 KiB

BIN
img/display_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 KiB

BIN
img/display_4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 255 KiB

BIN
img/scale_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 735 KiB

BIN
img/scale_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 KiB

BIN
img/scale_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 KiB

BIN
img/scale_4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 KiB

BIN
img/scale_side.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 KiB

BIN
img/scale_trans.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 KiB

BIN
img/web_1.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 KiB

BIN
img/web_ams.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

BIN
img/web_nfc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

BIN
img/web_spoolm_to_ams.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

BIN
img/web_spoolman.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

BIN
img/web_statistics.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

51
platformio.ini Normal file
View File

@ -0,0 +1,51 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[common]
version = "1.0.2"
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
lib_deps =
tzapu/WiFiManager @ ^2.0.17
me-no-dev/ESP Async WebServer @ ^1.2.4
me-no-dev/AsyncTCP @ ^1.1.1
bogde/HX711 @ ^0.7.5
adafruit/Adafruit SSD1306 @ ^2.5.13
adafruit/Adafruit GFX Library @ ^1.11.11
adafruit/Adafruit PN532 @ ^1.3.3
bblanchon/ArduinoJson @ ^7.3.0
knolleary/PubSubClient @ ^2.8
digitaldragon/SSLClient @ ^1.3.2
; Enable SPIFFS upload
board_build.filesystem = spiffs
board_build.spiffs.partition = 2M
board_build.spiffs.upload_size = 2M
build_flags =
-Os
-ffunction-sections
-fdata-sections
-DNDEBUG
-mtext-section-literals
'-D VERSION="${common.version}"'
extra_scripts =
pre:scripts/combine_html.py
pre:scripts/pre_build.py
pre:scripts/pre_spiffs.py
pre:scripts/gzip_files.py
pre:scripts/extra_script.py
pre:scripts/update_changelog.py

31
scripts/combine_html.py Normal file
View File

@ -0,0 +1,31 @@
Import("env")
import os
def combine_html_files(source, target, env):
html_dir = "./html"
header_file = os.path.join(html_dir, "header.html")
# Read header content
with open(header_file, 'r') as f:
header_content = f.read()
# Process all HTML files except header.html
for filename in os.listdir(html_dir):
if filename.endswith('.html') and filename != 'header.html':
file_path = os.path.join(html_dir, filename)
# Read content
with open(file_path, 'r') as f:
content = f.read()
# Replace placeholder with header content
if '{{header}}' in content:
new_content = content.replace('{{header}}', header_content)
# Write back combined content
with open(file_path, 'w') as f:
f.write(new_content)
print(f"Combined header with {filename}")
# Register the script to run before building SPIFFS
env.AddPreAction("buildfs", combine_html_files)

View File

@ -14,7 +14,7 @@ def copy_file(input_file, output_file):
def should_compress(file):
# Komprimiere nur bestimmte Dateitypen
return file.endswith(('.js', '.png', '.css'))
return file.endswith(('.js', '.png', '.css', '.html'))
def main(source_dir, target_dir):
for root, dirs, files in os.walk(source_dir):

25
scripts/pre_build.py Normal file
View File

@ -0,0 +1,25 @@
Import("env")
import os
def replace_version(source, target, env):
# Get version from common section
version = env.GetProjectConfig().get("common", "version").strip('"')
header_file = "./html/header.html"
with open(header_file, 'r') as file:
content = file.read()
# Replace version in header.html using string manipulation instead of regex
search = '<h1>FilaMan<span class="version">v'
end = '</span>'
start_pos = content.find(search)
if start_pos != -1:
start_pos += len(search)
end_pos = content.find(end, start_pos)
if end_pos != -1:
content = content[:start_pos] + version + content[end_pos:]
with open(header_file, 'w') as file:
file.write(content)
env.AddPreAction("buildfs", replace_version)

7
scripts/pre_spiffs.py Normal file
View File

@ -0,0 +1,7 @@
Import("env")
# Wiederverwendung der replace_version Funktion
exec(open("./scripts/pre_build.py").read())
# Bind to SPIFFS build
env.AddPreAction("buildfs", replace_version)

View File

@ -0,0 +1,36 @@
import os
import re
from datetime import datetime
def get_version():
with open('../platformio.ini', 'r') as f:
content = f.read()
version_match = re.search(r'version\s*=\s*"([^"]+)"', content)
return version_match.group(1) if version_match else None
def update_changelog():
version = get_version()
today = datetime.now().strftime('%Y-%m-%d')
changelog_template = f"""## [{version}] - {today}
### Added
-
### Changed
-
### Fixed
-
"""
with open('../CHANGELOG.md', 'r') as f:
content = f.read()
# Insert new version template after the header
updated_content = content.replace("# Changelog\n", f"# Changelog\n\n{changelog_template}")
with open('../CHANGELOG.md', 'w') as f:
f.write(updated_content)
if __name__ == "__main__":
update_changelog()

View File

@ -279,7 +279,11 @@ bool checkSpoolmanExtraFields() {
"price_meter",
"price_gramm",
"bambu_setting_id",
"bambu_idx"
"bambu_cali_id",
"bambu_idx",
"bambu_k",
"bambu_flow_ratio",
"bambu_max_volspeed"
};
String spoolExtraFields[] = {
@ -309,9 +313,27 @@ bool checkSpoolmanExtraFields() {
"\"field_type\": \"text\","
"\"key\": \"bambu_setting_id\"}",
"{\"name\": \"Bambu IDX\","
"{\"name\": \"Bambu Cali ID\","
"\"field_type\": \"text\","
"\"key\": \"bambu_idx\"}"
"\"key\": \"bambu_cali_id\"}",
"{\"name\": \"Bambu Filament IDX\","
"\"field_type\": \"text\","
"\"key\": \"bambu_idx\"}",
"{\"name\": \"Bambu k\","
"\"field_type\": \"float\","
"\"key\": \"bambu_k\"}",
"{\"name\": \"Bambu Flow Ratio\","
"\"field_type\": \"float\","
"\"key\": \"bambu_flow_ratio\"}",
"{\"name\": \"Bambu Max Vol. Speed\","
"\"unit\": \"mm3/s\","
"\"field_type\": \"integer\","
"\"default_value\": \"12\","
"\"key\": \"bambu_max_volspeed\"}"
};
Serial.println("Überprüfe Extrafelder...");
@ -374,8 +396,10 @@ bool checkSpoolmanExtraFields() {
Serial.println("Fehler beim Senden der Anfrage: " + String(http.errorToString(httpCode)));
return false;
}
http.end();
//http.end();
}
yield();
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
}

View File

@ -137,43 +137,8 @@ bool sendMqttMessage(String payload) {
}
bool setBambuSpool(String payload) {
/* payload
//// set Spool
{
"print": {
"sequence_id": 0,
"command": "ams_filament_setting",
"ams_id": 0, // AMS ID 0-3 oder externe Spule 255
"tray_id": 0, // Tray ID 0-3 oder externe Spule 254
"tray_color": "000000FF",
"nozzle_temp_min": 170,
"nozzle_temp_max": 200,
"tray_type": "PETG",
"setting_id": "",
"tray_info_idx": "GFG99"
}
}
//// Remove Spool
{
"print":{
"ams_id":255,
"command":"ams_filament_setting",
"nozzle_temp_max": 0,
"nozzle_temp_min": 0,
"sequence_id": 0,
"setting_id": "",
"tray_color": "FFFFFFFF",
"tray_id": 254,
"tray_info_idx": "",
"tray_type": "",
}
}
*/
Serial.println("Setting spool");
Serial.println("Spool settings in");
Serial.println(payload);
// Parse the JSON
JsonDocument doc;
@ -192,25 +157,29 @@ bool setBambuSpool(String payload) {
int maxTemp = doc["nozzle_temp_max"];
String type = doc["type"].as<String>();
String brand = doc["brand"].as<String>();
String tray_info_idx = findFilamentIdx(brand, type);
String tray_info_idx = (doc["tray_info_idx"].as<String>() != "-1") ? doc["tray_info_idx"].as<String>() : "";
if (tray_info_idx == "") tray_info_idx = (brand != "" && type != "") ? findFilamentIdx(brand, type) : "";
String setting_id = doc["bambu_setting_id"].as<String>();
String cali_idx = doc["cali_idx"].as<String>();
doc.clear();
doc["print"]["sequence_id"] = 0;
doc["print"]["command"] = "ams_filament_setting";
doc["print"]["ams_id"] = amsId < 200 ? amsId-1 : 255;
doc["print"]["tray_id"] = trayId < 200 ? trayId-1 : 254;
doc["print"]["ams_id"] = amsId < 200 ? amsId : 255;
doc["print"]["tray_id"] = trayId < 200 ? trayId : 254;
doc["print"]["tray_color"] = color.length() == 8 ? color : color+"FF";
doc["print"]["nozzle_temp_min"] = minTemp;
doc["print"]["nozzle_temp_max"] = maxTemp;
doc["print"]["tray_type"] = type;
doc["print"]["setting_id"] = "";
doc["print"]["cali_idx"] = (cali_idx != "") ? cali_idx : "";
doc["print"]["tray_info_idx"] = tray_info_idx;
doc["print"]["setting_id"] = setting_id;
// Serialize the JSON
String output;
serializeJson(doc, output);
if (sendMqttMessage(output)) {
Serial.println("Spool successfully set");
}
@ -220,6 +189,67 @@ bool setBambuSpool(String payload) {
return false;
}
doc.clear();
yield();
if (cali_idx != "") {
yield();
doc["print"]["sequence_id"] = 0;
doc["print"]["command"] = "extrusion_cali_sel";
doc["print"]["filament_id"] = tray_info_idx;
doc["print"]["nozzle_diameter"] = "0.4";
doc["print"]["cali_idx"] = cali_idx.toInt();
doc["print"]["tray_id"] = trayId < 200 ? trayId : 254;
doc["print"]["ams_id"] = amsId < 200 ? amsId : 255;
// Serialize the JSON
String output;
serializeJson(doc, output);
if (sendMqttMessage(output)) {
Serial.println("Extrusion calibration successfully set");
}
else
{
Serial.println("Failed to set extrusion calibration");
return false;
}
doc.clear();
yield();
}
/*
if (setting_id != "") {
yield();
doc["print"]["sequence_id"] = 0;
doc["print"]["command"] = "ams_filament_setting";
doc["print"]["nozzle_temp_min"] = minTemp;
doc["print"]["nozzle_temp_max"] = maxTemp;
doc["print"]["setting_id"] = setting_id;
doc["print"]["tray_color"] = color.length() == 8 ? color : color+"FF";
doc["print"]["ams_id"] = amsId < 200 ? amsId : 255;
doc["print"]["tray_id"] = trayId < 200 ? trayId : 254;
doc["print"]["tray_info_idx"] = tray_info_idx;
doc["print"]["tray_type"] = type;
// Serialize the JSON
String output;
serializeJson(doc, output);
if (sendMqttMessage(output)) {
Serial.println("Filament Setting successfully set");
}
else
{
Serial.println("Failed to set Filament setting");
return false;
}
doc.clear();
yield();
}
*/
return true;
}
@ -276,7 +306,8 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
JsonObject trayObj = trayArray[j];
if (trayObj["tray_info_idx"].as<String>() != ams_data[storedIndex].trays[j].tray_info_idx ||
trayObj["tray_type"].as<String>() != ams_data[storedIndex].trays[j].tray_type ||
trayObj["tray_color"].as<String>() != ams_data[storedIndex].trays[j].tray_color) {
trayObj["tray_color"].as<String>() != ams_data[storedIndex].trays[j].tray_color ||
trayObj["cali_idx"].as<String>() != ams_data[storedIndex].trays[j].cali_idx) {
hasChanges = true;
break;
}
@ -293,7 +324,8 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
foundExternal = true;
if (vtTray["tray_info_idx"].as<String>() != ams_data[i].trays[0].tray_info_idx ||
vtTray["tray_type"].as<String>() != ams_data[i].trays[0].tray_type ||
vtTray["tray_color"].as<String>() != ams_data[i].trays[0].tray_color) {
vtTray["tray_color"].as<String>() != ams_data[i].trays[0].tray_color ||
vtTray["cali_idx"].as<String>() != ams_data[i].trays[0].cali_idx) {
hasChanges = true;
}
break;
@ -307,7 +339,6 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
// Fortfahren mit der bestehenden Verarbeitung, da Änderungen gefunden wurden
ams_count = amsArray.size();
// Restlicher bestehender Code...
for (int i = 0; i < ams_count && i < 16; i++) {
JsonObject amsObj = amsArray[i];
JsonArray trayArray = amsObj["tray"].as<JsonArray>();
@ -324,25 +355,7 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
ams_data[i].trays[j].nozzle_temp_min = trayObj["nozzle_temp_min"].as<int>();
ams_data[i].trays[j].nozzle_temp_max = trayObj["nozzle_temp_max"].as<int>();
ams_data[i].trays[j].setting_id = trayObj["setting_id"].as<String>();
}
}
//Serial.println("----------------");
//Serial.println();
// Sende die aktualisierten AMS-Daten an alle WebSocket-Clients
sendAmsData(nullptr);
// Verarbeite erst die normalen AMS-Daten
for (int i = 0; i < amsArray.size() && i < 16; i++) {
JsonObject amsObj = amsArray[i];
JsonArray trayArray = amsObj["tray"].as<JsonArray>();
ams_data[i].ams_id = amsObj["id"].as<uint8_t>();
for (int j = 0; j < trayArray.size() && j < 4; j++) {
JsonObject trayObj = trayArray[j];
ams_data[i].trays[j].id = trayObj["id"].as<uint8_t>();
ams_data[i].trays[j].tray_info_idx = trayObj["tray_info_idx"].as<String>();
// ... weitere Tray-Daten ...
ams_data[i].trays[j].cali_idx = trayObj["cali_idx"].as<String>();
}
}
@ -362,11 +375,12 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
ams_data[extIdx].trays[0].nozzle_temp_min = vtTray["nozzle_temp_min"].as<int>();
ams_data[extIdx].trays[0].nozzle_temp_max = vtTray["nozzle_temp_max"].as<int>();
ams_data[extIdx].trays[0].setting_id = vtTray["setting_id"].as<String>();
ams_data[extIdx].trays[0].cali_idx = vtTray["cali_idx"].as<String>();
ams_count++; // Erhöhe ams_count für die externe Spule
}
// Sende die aktualisierten AMS-Daten
sendAmsData(nullptr);
//sendAmsData(nullptr);
// Erstelle JSON für WebSocket-Clients
JsonDocument wsDoc;
@ -389,18 +403,68 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
trayObj["nozzle_temp_min"] = ams_data[i].trays[j].nozzle_temp_min;
trayObj["nozzle_temp_max"] = ams_data[i].trays[j].nozzle_temp_max;
trayObj["setting_id"] = ams_data[i].trays[j].setting_id;
trayObj["cali_idx"] = ams_data[i].trays[j].cali_idx;
}
}
serializeJson(wsArray, amsJsonData);
sendAmsData(nullptr);
}
// Neue Bedingung für ams_filament_setting
else if (doc["print"]["command"] == "ams_filament_setting") {
int amsId = doc["print"]["ams_id"].as<int>();
int trayId = doc["print"]["tray_id"].as<int>();
String settingId = doc["print"]["setting_id"].as<String>();
// Finde das entsprechende AMS und Tray
for (int i = 0; i < ams_count; i++) {
if (ams_data[i].ams_id == amsId) {
// Update setting_id im entsprechenden Tray
ams_data[i].trays[trayId].setting_id = settingId;
// Erstelle neues JSON für WebSocket-Clients
JsonDocument wsDoc;
JsonArray wsArray = wsDoc.to<JsonArray>();
for (int j = 0; j < ams_count; j++) {
JsonObject amsObj = wsArray.createNestedObject();
amsObj["ams_id"] = ams_data[j].ams_id;
JsonArray trays = amsObj.createNestedArray("tray");
int maxTrays = (ams_data[j].ams_id == 255) ? 1 : 4;
for (int k = 0; k < maxTrays; k++) {
JsonObject trayObj = trays.createNestedObject();
trayObj["id"] = ams_data[j].trays[k].id;
trayObj["tray_info_idx"] = ams_data[j].trays[k].tray_info_idx;
trayObj["tray_type"] = ams_data[j].trays[k].tray_type;
trayObj["tray_sub_brands"] = ams_data[j].trays[k].tray_sub_brands;
trayObj["tray_color"] = ams_data[j].trays[k].tray_color;
trayObj["nozzle_temp_min"] = ams_data[j].trays[k].nozzle_temp_min;
trayObj["nozzle_temp_max"] = ams_data[j].trays[k].nozzle_temp_max;
trayObj["setting_id"] = ams_data[j].trays[k].setting_id;
trayObj["cali_idx"] = ams_data[j].trays[k].cali_idx;
}
}
// Aktualisiere das globale amsJsonData
amsJsonData = "";
serializeJson(wsArray, amsJsonData);
// Sende an WebSocket Clients
sendAmsData(nullptr);
break;
}
}
}
}
void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
bambu_connected = false;
oledShowTopRow();
// Attempt to connect
if (client.connect(bambu_serialnr, bambu_username, bambu_accesscode)) {
@ -416,7 +480,8 @@ void reconnect() {
bambu_connected = false;
oledShowTopRow();
// Wait 5 seconds before retrying
delay(5000);
yield();
vTaskDelay(5000 / portTICK_PERIOD_MS);
}
}
}
@ -434,6 +499,8 @@ void mqtt_loop(void * parameter) {
vTaskDelay(100);
}
client.loop();
yield();
vTaskDelay(100);
}
}
@ -466,28 +533,28 @@ bool setupMqtt() {
//client.subscribe(request_topic.c_str());
Serial.println("MQTT-Client initialisiert");
oledShowTopRow();
oledShowMessage("Bambu Connected");
bambu_connected = true;
oledShowTopRow();
xTaskCreatePinnedToCore(
mqtt_loop, /* Function to implement the task */
"BambuMqtt", /* Name of the task */
10000, /* Stack size in words */
NULL, /* Task input parameter */
mqttTaskPrio, /* Priority of the task */
&BambuMqttTask, /* Task handle. */
mqttTaskCore); /* Core where the task should run */
}
else
{
Serial.println("Fehler: Konnte sich nicht beim MQTT-Server anmelden");
oledShowMessage("Bambu Connection Failed");
oledShowTopRow();
vTaskDelay(2000 / portTICK_PERIOD_MS);
connected = false;
oledShowTopRow();
}
xTaskCreatePinnedToCore(
mqtt_loop, /* Function to implement the task */
"BambuMqtt", /* Name of the task */
10000, /* Stack size in words */
NULL, /* Task input parameter */
mqttTaskPrio, /* Priority of the task */
&BambuMqttTask, /* Task handle. */
mqttTaskCore); /* Core where the task should run */
if (!connected) return false;
}
else
@ -499,4 +566,11 @@ bool setupMqtt() {
return false;
}
return true;
}
void bambu_restart() {
if (BambuMqttTask) {
vTaskDelete(BambuMqttTask);
}
setupMqtt();
}

View File

@ -13,6 +13,7 @@ struct TrayData {
int nozzle_temp_min;
int nozzle_temp_max;
String setting_id;
String cali_idx;
};
#define MAX_AMS 17 // 16 normale AMS + 1 externe Spule
@ -33,5 +34,6 @@ bool saveBambuCredentials(const String& bambu_ip, const String& bambu_serialnr,
bool setupMqtt();
void mqtt_loop(void * parameter);
bool setBambuSpool(String payload);
void bambu_restart();
#endif

View File

@ -74,6 +74,10 @@ void setup() {
unsigned long lastWeightReadTime = 0;
const unsigned long weightReadInterval = 1000; // 1 second
unsigned long lastAmsSendTime = 0;
const unsigned long amsSendInterval = 60000; // 1 minute
uint8_t weightSend = 0;
int16_t lastWeight = 0;
uint8_t wifiErrorCounter = 0;
@ -95,6 +99,12 @@ void loop() {
// Falls WifiManager im nicht blockenden Modus ist
//if(wm_nonblocking) wm.process();
// Send AMS Data min every Minute
if (currentMillis - lastAmsSendTime >= amsSendInterval) {
lastAmsSendTime = currentMillis;
sendAmsData(nullptr);
}
// Ausgabe der Waage auf Display
if (pauseMainTask == 0 && weight != lastWeight && hasReadRfidTag == 0)
{

View File

@ -69,6 +69,16 @@ void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventTyp
}
}
else if (doc["type"] == "reconnect") {
if (doc["payload"] == "bambu") {
bambu_restart();
}
if (doc["payload"] == "spoolman") {
initSpoolman();
}
}
else if (doc["type"] == "setBambuSpool") {
Serial.println(doc["payload"].as<String>());
setBambuSpool(doc["payload"]);
@ -82,24 +92,16 @@ void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventTyp
// Funktion zum Laden und Ersetzen des Headers in einer HTML-Datei
String loadHtmlWithHeader(const char* filename) {
if (!SPIFFS.exists(filename) || !SPIFFS.exists("/header.html")) {
Serial.println("Lade HTML-Datei: " + String(filename));
if (!SPIFFS.exists(filename)) {
Serial.println("Fehler: Datei nicht gefunden!");
return "Fehler: Datei nicht gefunden!";
}
// Lade den Header
File headerFile = SPIFFS.open("/header.html", "r");
String header = headerFile.readString();
headerFile.close();
// Lade die Hauptdatei
File file = SPIFFS.open(filename, "r");
String html = file.readString();
file.close();
// Ersetze den Platzhalter mit dem Header
html.replace("{{header}}", header);
return html;
}
@ -159,25 +161,31 @@ void setupWebserver(AsyncWebServer &server) {
Serial.print("Geladene Spoolman-URL: ");
Serial.println(spoolmanUrl);
// Route für die Startseite
// Route für about
server.on("/about", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für / erhalten");
String html = loadHtmlWithHeader("/index.html");
request->send(200, "text/html", html);
Serial.println("Anfrage für /about erhalten");
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/about.html.gz", "text/html");
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
});
// Route für Waage
server.on("/waage", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /waage erhalten");
String html = loadHtmlWithHeader("/waage.html");
request->send(200, "text/html", html);
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/waage.html.gz", "text/html");
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
});
// Route für RFID
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /rfid erhalten");
String html = loadHtmlWithHeader("/rfid.html");
request->send(200, "text/html", html);
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/rfid.html.gz", "text/html");
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
Serial.println("RFID-Seite gesendet");
});
@ -201,8 +209,10 @@ void setupWebserver(AsyncWebServer &server) {
// Route für WiFi
server.on("/wifi", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /wifi erhalten");
String html = loadHtmlWithHeader("/wifi.html");
request->send(200, "text/html", html);
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/wifi.html.gz", "text/html");
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
});
// Route für Spoolman Setting
@ -283,15 +293,6 @@ void setupWebserver(AsyncWebServer &server) {
Serial.println("style.css gesendet");
});
server.on("/style2.css", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Lade style2.css");
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/style2.css.gz", "text/css");
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
Serial.println("style2.css gesendet");
});
// Route für das Logo
server.on("/logo.png", HTTP_GET, [](AsyncWebServerRequest *request){
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/logo.png.gz", "image/png");