Compare commits

...

18 Commits

Author SHA1 Message Date
84617ebc53 feat: add German translation of README and link to it from the English version
Some checks failed
Create Release / build (push) Has been cancelled
2025-02-16 11:18:43 +01:00
8ac6ba57a4 docs: update changelog for version 1.0.5 2025-02-16 10:47:26 +01:00
cc17140dc4 feat: update version to 1.0.5 and enhance changelog update process with automatic git push 2025-02-16 10:47:18 +01:00
8741216374 feat: update changelog script to categorize commits and handle new changelog creation 2025-02-16 10:45:12 +01:00
7bdebeab03 feat: improve changelog update script to handle absolute paths and create new changelog if missing 2025-02-16 10:38:12 +01:00
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
26 changed files with 857 additions and 203 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

36
CHANGELOG.md Normal file
View File

@ -0,0 +1,36 @@
# Changelog
## [1.0.5] - 2025-02-16
### Added
- update version to 1.0.5 and enhance changelog update process with automatic git push
- update changelog script to categorize commits and handle new changelog creation
- improve changelog update script to handle absolute paths and create new changelog if missing
## [1.0.4] - 2025-02-16
### Added
- improve changelog update script to handle absolute paths and create new changelog if missing
## [1.0.3] - 2025-02-16
### Added
-
### Changed
-
### Fixed
-
## [1.0.2] - 2025-02-16
### Added
- Feature 1
- Feature 2
### Changed
- Change 1
- Change 2
### Fixed
- Fix 1
- Fix 2

155
README.de.md Normal file
View File

@ -0,0 +1,155 @@
# FilaMan - Filament Management System
FilaMan ist ein Filament-Managementsystem für den 3D-Druck. Es verwendet ESP32-Hardware für Gewichtsmessungen und NFC-Tag-Management.
Benutzer können Filamentspulen verwalten, den Status des Automatic Material System (AMS) von Bablulab Druckern überwachen und Einstellungen über eine Weboberfläche vornehmen.
Das System integriert sich nahtlos mit der [Spoolman](https://github.com/Donkie/Spoolman) Filamentverwaltung, zusätzlich mit [Bambulab](https://bambulab.com/en-us) 3D-Druckern und sowie dem [Openspool](https://github.com/spuder/OpenSpool) NFC-TAG Format.
![Scale](./img/scale_trans.png)
Weitere Bilder finden Sie im [img Ordner](/img/)
oder auf meiner Website: [FilaMan Website](https://www.filaman.app)
Deutsches Erklärvideo: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaOHU)
### ESP32 Hardware-Funktionen
- **Gewichtsmessung:** Verwendung einer Wägezelle mit HX711-Verstärker für präzise Gewichtsverfolgung.
- **NFC-Tag Lesen/Schreiben:** PN532-Modul zum Lesen und Schreiben von Filamentdaten auf NFC-Tags.
- **OLED-Display:** Zeigt aktuelles Gewicht, Verbindungsstatus (WiFi, Bambu Lab, Spoolman).
- **WLAN-Konnektivität:** WiFiManager für einfache Netzwerkkonfiguration.
- **MQTT-Integration:** Verbindet sich mit Bambu Lab Drucker für AMS-Steuerung.
- **NFC-Tag NTAG215:** Verwendung von NTAG215 wegen ausreichendem Speicherplatz auf dem Tag
### Weboberflächen-Funktionen
- **Echtzeit-Updates:** WebSocket-Verbindung für Live-Daten-Updates.
- **NFC-Tag-Verwaltung:**
- Filamentdaten auf NFC-Tags schreiben.
- Verwendet das NFC-Tag-Format von [Openspool](https://github.com/spuder/OpenSpool)
- Ermöglicht automatische Spulenerkennung im AMS
- **Bambulab AMS-Integration:**
- Anzeige der aktuellen AMS-Fachbelegung.
- Zuordnung von Filamenten zu AMS-Slots.
- Unterstützung für externe Spulenhalter.
- **Spoolman-Integration:**
- Auflistung verfügbarer Filamentspulen.
- Filtern und Auswählen von Filamenten.
- Automatische Aktualisierung der Spulengewichte.
- Verfolgung von NFC-Tag-Zuweisungen.
### Wenn Sie meine Arbeit unterstützen möchten, freue ich mich über einen Kaffee
<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>
## Detaillierte Funktionalität
### ESP32-Funktionalität
- **Druckaufträge steuern und überwachen:** Der ESP32 kommuniziert mit dem Bambu Lab Drucker.
- **Drucker-Kommunikation:** Nutzt MQTT für Echtzeit-Kommunikation mit dem Drucker.
- **Benutzerinteraktionen:** Das OLED-Display bietet sofortiges Feedback zum Systemstatus.
### Weboberflächen-Funktionalität
- **Benutzerinteraktionen:** Die Weboberfläche ermöglicht Benutzern die Interaktion mit dem System.
- **UI-Elemente:** Enthält Dropdown-Menüs für Hersteller und Filamente, Buttons zum Beschreiben von NFC-Tags und Echtzeit-Statusanzeigen.
## Hardware-Anforderungen
### Komponenten
- **ESP32 Entwicklungsboard:** Jede ESP32-Variante.
[Amazon Link](https://amzn.eu/d/aXThslf)
- **HX711 Wägezellen-Verstärker:** Für Gewichtsmessung.
[Amazon Link](https://amzn.eu/d/1wZ4v0x)
- **OLED Display:** 128x64 SSD1306.
[Amazon Link](https://amzn.eu/d/dozAYDU)
- **PN532 NFC Modul:** Für NFC-Tag-Operationen.
[Amazon Link](https://amzn.eu/d/8205DDh)
- **NFC-Tag:** NTAG215
[Amazon Link](https://amzn.eu/d/fywy4c4)
### Pin-Konfiguration
| Komponente | ESP32 Pin |
|-------------------|-----------|
| HX711 DOUT | 16 |
| HX711 SCK | 17 |
| OLED SDA | 21 |
| OLED SCL | 22 |
| PN532 IRQ | 32 |
| PN532 RESET | 33 |
| PN532 SCK | 14 |
| PN532 MOSI | 13 |
| PN532 MISO | 12 |
| PN532 CS/SS | 15 |
## Software-Abhängigkeiten
### ESP32-Bibliotheken
- `WiFiManager`: Netzwerkkonfiguration
- `ESPAsyncWebServer`: Webserver-Funktionalität
- `ArduinoJson`: JSON-Verarbeitung
- `PubSubClient`: MQTT-Kommunikation
- `Adafruit_PN532`: NFC-Funktionalität
- `Adafruit_SSD1306`: OLED-Display-Steuerung
- `HX711`: Wägezellen-Kommunikation
## Installation
### Voraussetzungen
- **Software:**
- [PlatformIO](https://platformio.org/) in VS Code
- [Spoolman](https://github.com/Donkie/Spoolman) Instanz
- **Hardware:**
- ESP32 Entwicklungsboard
- HX711 Wägezellen-Verstärker
- Wägezelle (Gewichtssensor)
- OLED Display (128x64 SSD1306)
- PN532 NFC Modul
- Verbindungskabel
### Schritt-für-Schritt Installation
1. **Repository klonen:**
```bash
git clone https://github.com/ManuelW77/Filaman.git
cd FilaMan
```
2. **Abhängigkeiten installieren:**
```bash
pio lib install
```
3. **ESP32 flashen:**
```bash
pio run --target upload
```
4. **Ersteinrichtung:**
- Mit dem "FilaMan" WLAN-Zugangspunkt verbinden.
- WLAN-Einstellungen über das Konfigurationsportal vornehmen.
- Weboberfläche unter `http://filaman.local` oder der IP-Adresse aufrufen.
## Dokumentation
### Relevante Links
- [PlatformIO Dokumentation](https://docs.platformio.org/)
- [Spoolman Dokumentation](https://github.com/Donkie/Spoolman)
- [Bambu Lab Drucker Dokumentation](https://www.bambulab.com/)
### Tutorials und Beispiele
- [PlatformIO erste Schritte](https://docs.platformio.org/en/latest/tutorials/espressif32/arduino_debugging_unit_testing.html)
- [ESP32 Webserver Tutorial](https://randomnerdtutorials.com/esp32-web-server-arduino-ide/)
## Lizenz
Dieses Projekt ist unter der MIT-Lizenz lizenziert. Siehe [LICENSE](LICENSE) Datei für Details.
## Materialien
### Nützliche Ressourcen
- [ESP32 Offizielle Dokumentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/)
- [Arduino Bibliotheken](https://www.arduino.cc/en/Reference/Libraries)
- [NFC Tag Informationen](https://learn.adafruit.com/adafruit-pn532-rfid-nfc/overview)
### Community und Support
- [PlatformIO Community](https://community.platformio.org/)
- [Arduino Forum](https://forum.arduino.cc/)
- [ESP32 Forum](https://www.esp32.com/)
## Verfügbarkeit
Der Code kann getestet und die Anwendung kann vom [GitHub Repository](https://github.com/ManuelW77/Filaman) heruntergeladen werden.
### Wenn Sie meine Arbeit unterstützen möchten, freue ich mich über einen Kaffee
<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

@ -1,5 +1,7 @@
# FilaMan - Filament Management System # FilaMan - Filament Management System
[Deutsche Version](README.de.md)
FilaMan is a filament management system for 3D printing. It uses ESP32 hardware for weight measurement and NFC tag management. FilaMan is a filament management system for 3D printing. It uses ESP32 hardware for weight measurement and NFC tag management.
Users can manage filament spools, monitor the status of the Automatic Material System (AMS) and make settings via a web interface. 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. 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.

View File

@ -1,68 +0,0 @@
Import("env")
from os.path import join, exists
import shutil
import os
def combine_binaries(source, target, env):
# Define paths for binary files
build_dir = env.subst("$BUILD_DIR")
project_dir = env.subst("$PROJECT_DIR")
combined_bin = join(build_dir, "combined.bin")
firmware_bin = join(build_dir, "firmware.bin")
spiffs_bin = join(build_dir, "spiffs.bin")
# Define target firmware path
firmware_dir = join(project_dir, "firmware")
target_firmware = join(firmware_dir, "filaman.bin")
# Build firmware if it doesn't exist
if not exists(firmware_bin):
print("Building firmware...")
env.Execute("pio run -t buildprog")
# Build SPIFFS if it doesn't exist
if not exists(spiffs_bin):
print("Building SPIFFS image...")
env.Execute("pio run -t buildfs")
# Check if files exist after build attempts
if not exists(firmware_bin):
raise Exception("Firmware binary not found at: " + firmware_bin)
if not exists(spiffs_bin):
raise Exception("SPIFFS binary not found at: " + spiffs_bin)
print("Found firmware at:", firmware_bin)
print("Found SPIFFS at:", spiffs_bin)
# Command to merge firmware and SPIFFS
merge_command = (
"esptool.py --chip esp32 merge_bin -o {combined_bin} "
"--flash_mode dio --flash_freq 40m --flash_size 4MB "
"0x10000 {firmware_bin} 0x310000 {spiffs_bin}"
).format(
combined_bin=combined_bin,
firmware_bin=firmware_bin,
spiffs_bin=spiffs_bin
)
print("Executing merge command:", merge_command)
env.Execute(merge_command)
# Create firmware directory if it doesn't exist
if not exists(firmware_dir):
os.makedirs(firmware_dir)
# Move combined binary to target location
print(f"Moving combined binary to {target_firmware}")
shutil.copy2(combined_bin, target_firmware)
print(f"Binary successfully moved to {target_firmware}")
# Register the custom target with explicit dependencies
env.AddCustomTarget(
name="combine_binaries",
dependencies=["buildfs", "buildprog"],
actions=[combine_binaries],
title="Combine Firmware & SPIFFS",
description="Combines firmware.bin and spiffs.bin into a single binary"
)

View File

@ -12,7 +12,7 @@
<div style="display: flex; align-items: center; gap: 2rem;"> <div style="display: flex; align-items: center; gap: 2rem;">
<img src="/logo.png" alt="FilaMan Logo" class="logo"> <img src="/logo.png" alt="FilaMan Logo" class="logo">
<div class="logo-text"> <div class="logo-text">
<h1>FilaMan</h1> <h1>FilaMan<span class="version">v1.0.2</span></h1>
<h4>Filament Management Tool</h4> <h4>Filament Management Tool</h4>
</div> </div>
</div> </div>
@ -32,3 +32,4 @@
<div class="ram-status" id="ramStatus"></div> <div class="ram-status" id="ramStatus"></div>
</div> </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"> <div class="container">
<h1>FilaMan</h1> <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="connection-status hidden">
<div class="spinner"></div> <div class="spinner"></div>
<span>Connection lost. Trying to reconnect...</span> <span>Connection lost. Trying to reconnect...</span>

View File

@ -112,9 +112,39 @@ function initWebSocket() {
if (bambuDot) { if (bambuDot) {
bambuDot.className = 'status-dot ' + (data.bambu_connected ? 'online' : 'offline'); 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) { if (spoolmanDot) {
spoolmanDot.className = 'status-dot ' + (data.spoolman_connected ? 'online' : 'offline'); 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) { if (ramStatus) {
ramStatus.textContent = `${data.freeHeap}k`; ramStatus.textContent = `${data.freeHeap}k`;
@ -209,16 +239,6 @@ function updateNfcInfo() {
`${spool.id} | ${spool.filament.name} (${spool.filament.material})` === selectedText `${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) { if (selectedSpool) {
writeButton.classList.remove("hidden"); writeButton.classList.remove("hidden");
} else { } else {
@ -236,7 +256,7 @@ function displayAmsData(amsData) {
const trayHTML = ams.tray.map(tray => { const trayHTML = ams.tray.map(tray => {
// Prüfe ob überhaupt Daten vorhanden sind // 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 => const hasAnyContent = relevantFields.some(field =>
tray[field] !== null && tray[field] !== null &&
tray[field] !== undefined && tray[field] !== undefined &&
@ -293,7 +313,8 @@ function displayAmsData(amsData) {
const trayProperties = [ const trayProperties = [
{ key: 'tray_sub_brands', label: 'Sub Brands' }, { key: 'tray_sub_brands', label: 'Sub Brands' },
{ key: 'tray_info_idx', label: 'Filament IDX' }, { key: 'tray_info_idx', label: 'Filament IDX' },
{ key: 'setting_id', label: 'Setting ID' } { key: 'setting_id', label: 'Setting ID' },
{ key: 'cali_idx', label: 'Calibration IDX' }
]; ];
// Nur gültige Felder anzeigen // Nur gültige Felder anzeigen
@ -304,7 +325,13 @@ function displayAmsData(amsData) {
tray[prop.key] !== '' && tray[prop.key] !== '' &&
tray[prop.key] !== 'null' 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(''); .join('');
// Temperaturen nur anzeigen, wenn beide nicht 0 sind // Temperaturen nur anzeigen, wenn beide nicht 0 sind
@ -417,12 +444,22 @@ function handleSpoolIn(amsId, trayId) {
nozzle_temp_max: parseInt(maxTemp), nozzle_temp_max: parseInt(maxTemp),
type: selectedSpool.filament.material, 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() tray_info_idx: selectedSpool.filament.extra.bambu_idx.replace(/['"]+/g, '').trim(),
cali_idx: "-1" // Default-Wert setzen
} }
}; };
// Debug logging // Prüfe, ob der Key cali_idx vorhanden ist und setze ihn
console.log("Sende WebSocket Nachricht:", payload); 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 { try {
socket.send(JSON.stringify(payload)); socket.send(JSON.stringify(payload));
@ -479,7 +516,7 @@ function updateNfcData(data) {
} }
} else { } else {
nfcDataDiv.innerHTML = '<div style="margin-top: 10px;"></div>'; nfcDataDiv.innerHTML = '<div class="info-message-inner" style="margin-top: 10px;"></div>';
} }
nfcStatusContainer.appendChild(nfcDataDiv); nfcStatusContainer.appendChild(nfcDataDiv);
return; return;

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> <script>
window.onload = function() { window.onload = function() {
if (spoolmanUrl && spoolmanUrl.trim() !== "") { if (spoolmanUrl && spoolmanUrl.trim() !== "") {

View File

@ -86,6 +86,7 @@ function populateVendorDropdown(data, selectedSmId = null) {
}); });
// Nach der Schleife: Formatierung der Gesamtlänge // Nach der Schleife: Formatierung der Gesamtlänge
console.log("Total Lenght: ", totalLength);
const formattedLength = totalLength > 1000 const formattedLength = totalLength > 1000
? (totalLength / 1000).toFixed(2) + " km" ? (totalLength / 1000).toFixed(2) + " km"
: totalLength.toFixed(2) + " m"; : totalLength.toFixed(2) + " m";

View File

@ -133,6 +133,15 @@ body {
background-color: #dc2626; background-color: #dc2626;
} }
.status-dot.offline {
cursor: pointer;
}
.status-dot.offline:hover {
opacity: 0.8;
transform: scale(1.1);
}
.ram-status { .ram-status {
color: var(--inner-text-color); color: var(--inner-text-color);
font-size: 0.9em; font-size: 0.9em;
@ -471,11 +480,15 @@ a:hover {
.info-message { .info-message {
padding: 10px; padding: 10px;
background-color: #fff3f3; background-color: var(--header-bg);
border-radius: 4px; border-radius: 4px;
border-left: 4px solid #39d82e; border-left: 4px solid #39d82e;
} }
.info-message-inner {
background-color: var(--header-bg) !important;
}
.nfc-header { .nfc-header {
display: grid; display: grid;
grid-template-columns: 40px 1fr 40px; grid-template-columns: 40px 1fr 40px;
@ -994,3 +1007,10 @@ input[type="submit"]:disabled,
.spool-button:hover { .spool-button:hover {
opacity: 0.8; opacity: 0.8;
} }
.version {
font-size: 0.4em;
color: #000;
vertical-align: middle;
margin-left: 0.5rem;
}

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"> <div class="content">
<h1>Scale Configuration Page</h1> <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"> <div class="content">
<h1>WiFi Configuration Page</h1> <h1>WiFi Configuration Page</h1>
<form action="/setToken" method="post"> <form action="/setToken" method="post">

View File

@ -1,6 +0,0 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x4000,
otadata, data, ota, 0xd000, 0x2000,
app0, app, ota_0, 0x10000, 0x180000,
app1, app, ota_1, 0x190000,0x180000,
spiffs, data, spiffs, 0x310000,0x0F0000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x4000
3 otadata data ota 0xd000 0x2000
4 app0 app ota_0 0x10000 0x180000
5 app1 app ota_1 0x190000 0x180000
6 spiffs data spiffs 0x310000 0x0F0000

View File

@ -8,6 +8,9 @@
; Please visit documentation for the other options and examples ; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html ; https://docs.platformio.org/page/projectconf.html
[common]
version = "1.0.5"
[env:esp32dev] [env:esp32dev]
platform = espressif32 platform = espressif32
board = esp32dev board = esp32dev
@ -27,10 +30,7 @@ lib_deps =
digitaldragon/SSLClient @ ^1.3.2 digitaldragon/SSLClient @ ^1.3.2
; Enable SPIFFS upload ; Enable SPIFFS upload
board_build.partitions = partitions.csv
board_build.filesystem = spiffs board_build.filesystem = spiffs
board_upload.flash_size = 4MB
board_build.spiffs_create = yes
board_build.spiffs.partition = 2M board_build.spiffs.partition = 2M
board_build.spiffs.upload_size = 2M board_build.spiffs.upload_size = 2M
@ -40,18 +40,12 @@ build_flags =
-fdata-sections -fdata-sections
-DNDEBUG -DNDEBUG
-mtext-section-literals -mtext-section-literals
'-D VERSION="${common.version}"'
extra_scripts = extra_scripts =
pre:gzip_files.py pre:scripts/combine_html.py
pre:extra_script.py pre:scripts/pre_build.py
post:$PROJECT_DIR/create_combined_binary.py pre:scripts/pre_spiffs.py
pre:scripts/gzip_files.py
# Add custom target for combining binaries pre:scripts/extra_script.py
custom_targets = pre:scripts/update_changelog.py
combine_binaries
# Define the build sequence
targets =
buildfs # Build SPIFFS image
buildprog # Build firmware
#combine_binaries # Combine both binaries

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): def should_compress(file):
# Komprimiere nur bestimmte Dateitypen # Komprimiere nur bestimmte Dateitypen
return file.endswith(('.js', '.png', '.css')) return file.endswith(('.js', '.png', '.css', '.html'))
def main(source_dir, target_dir): def main(source_dir, target_dir):
for root, dirs, files in os.walk(source_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)

132
scripts/update_changelog.py Normal file
View File

@ -0,0 +1,132 @@
import os
import re
import subprocess
from datetime import datetime
def get_version():
script_dir = os.path.dirname(os.path.abspath(__file__))
project_dir = os.path.dirname(script_dir)
platformio_path = os.path.join(project_dir, 'platformio.ini')
with open(platformio_path, '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 get_last_tag():
try:
result = subprocess.run(['git', 'describe', '--tags', '--abbrev=0'],
capture_output=True, text=True)
return result.stdout.strip()
except subprocess.CalledProcessError:
return None
def categorize_commit(commit_msg):
"""Categorize commit messages based on conventional commits"""
lower_msg = commit_msg.lower()
if any(x in lower_msg for x in ['feat', 'add', 'new']):
return 'Added'
elif any(x in lower_msg for x in ['fix', 'bug']):
return 'Fixed'
else:
return 'Changed'
def get_changes_from_git():
"""Get changes from git commits since last tag"""
changes = {
'Added': [],
'Changed': [],
'Fixed': []
}
last_tag = get_last_tag()
# Get commits since last tag
git_log_command = ['git', 'log', '--pretty=format:%s']
if last_tag:
git_log_command.append(f'{last_tag}..HEAD')
try:
result = subprocess.run(git_log_command, capture_output=True, text=True)
commits = result.stdout.strip().split('\n')
# Filter out empty commits and categorize
for commit in commits:
if commit:
category = categorize_commit(commit)
# Clean up commit message
clean_msg = re.sub(r'^(feat|fix|chore|docs|style|refactor|perf|test)(\(.*\))?:', '', commit).strip()
changes[category].append(clean_msg)
except subprocess.CalledProcessError:
print("Error: Failed to get git commits")
return None
return changes
def push_changes(version):
"""Push changes to upstream"""
try:
# Stage the CHANGELOG.md
subprocess.run(['git', 'add', 'CHANGELOG.md'], check=True)
# Commit the changelog
commit_msg = f"docs: update changelog for version {version}"
subprocess.run(['git', 'commit', '-m', commit_msg], check=True)
# Push to origin (local)
subprocess.run(['git', 'push', 'origin'], check=True)
print("Successfully pushed to origin")
# Ask for upstream push
response = input("Do you want to push to GitHub (upstream)? (y/n): ").lower()
if response == 'y':
subprocess.run(['git', 'push', 'upstream'], check=True)
print("Successfully pushed to upstream")
except subprocess.CalledProcessError as e:
print(f"Error during git operations: {e}")
return False
return True
def update_changelog():
version = get_version()
today = datetime.now().strftime('%Y-%m-%d')
script_dir = os.path.dirname(os.path.abspath(__file__))
project_dir = os.path.dirname(script_dir)
changelog_path = os.path.join(project_dir, 'CHANGELOG.md')
# Get changes from git
changes = get_changes_from_git()
if not changes:
print("No changes found or error occurred")
return
# Create changelog entry
changelog_entry = f"## [{version}] - {today}\n"
for section, entries in changes.items():
if entries: # Only add sections that have entries
changelog_entry += f"### {section}\n"
for entry in entries:
changelog_entry += f"- {entry}\n"
changelog_entry += "\n"
if not os.path.exists(changelog_path):
with open(changelog_path, 'w') as f:
f.write(f"# Changelog\n\n{changelog_entry}")
push_changes(version)
else:
with open(changelog_path, 'r') as f:
content = f.read()
if f"[{version}]" not in content:
updated_content = content.replace("# Changelog\n", f"# Changelog\n\n{changelog_entry}")
with open(changelog_path, 'w') as f:
f.write(updated_content)
push_changes(version)
else:
print(f"Version {version} already exists in changelog")
if __name__ == "__main__":
update_changelog()

View File

@ -279,6 +279,7 @@ bool checkSpoolmanExtraFields() {
"price_meter", "price_meter",
"price_gramm", "price_gramm",
"bambu_setting_id", "bambu_setting_id",
"bambu_cali_id",
"bambu_idx", "bambu_idx",
"bambu_k", "bambu_k",
"bambu_flow_ratio", "bambu_flow_ratio",
@ -312,7 +313,11 @@ bool checkSpoolmanExtraFields() {
"\"field_type\": \"text\"," "\"field_type\": \"text\","
"\"key\": \"bambu_setting_id\"}", "\"key\": \"bambu_setting_id\"}",
"{\"name\": \"Bambu IDX\"," "{\"name\": \"Bambu Cali ID\","
"\"field_type\": \"text\","
"\"key\": \"bambu_cali_id\"}",
"{\"name\": \"Bambu Filament IDX\","
"\"field_type\": \"text\"," "\"field_type\": \"text\","
"\"key\": \"bambu_idx\"}", "\"key\": \"bambu_idx\"}",

View File

@ -137,43 +137,7 @@ bool sendMqttMessage(String payload) {
} }
bool setBambuSpool(String payload) { bool setBambuSpool(String payload) {
/* payload Serial.println("Spool settings in");
//// 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(payload); Serial.println(payload);
// Parse the JSON // Parse the JSON
@ -193,8 +157,10 @@ bool setBambuSpool(String payload) {
int maxTemp = doc["nozzle_temp_max"]; int maxTemp = doc["nozzle_temp_max"];
String type = doc["type"].as<String>(); String type = doc["type"].as<String>();
String brand = doc["brand"].as<String>(); String brand = doc["brand"].as<String>();
String tray_info_idx = doc["tray_info_idx"].as<String>(); 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) : ""; 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.clear();
@ -206,8 +172,9 @@ bool setBambuSpool(String payload) {
doc["print"]["nozzle_temp_min"] = minTemp; doc["print"]["nozzle_temp_min"] = minTemp;
doc["print"]["nozzle_temp_max"] = maxTemp; doc["print"]["nozzle_temp_max"] = maxTemp;
doc["print"]["tray_type"] = type; 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"]["tray_info_idx"] = tray_info_idx;
doc["print"]["setting_id"] = setting_id;
// Serialize the JSON // Serialize the JSON
String output; String output;
@ -222,6 +189,67 @@ bool setBambuSpool(String payload) {
return false; 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; return true;
} }
@ -278,7 +306,8 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
JsonObject trayObj = trayArray[j]; JsonObject trayObj = trayArray[j];
if (trayObj["tray_info_idx"].as<String>() != ams_data[storedIndex].trays[j].tray_info_idx || 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_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; hasChanges = true;
break; break;
} }
@ -295,7 +324,8 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
foundExternal = true; foundExternal = true;
if (vtTray["tray_info_idx"].as<String>() != ams_data[i].trays[0].tray_info_idx || 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_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; hasChanges = true;
} }
break; break;
@ -304,17 +334,11 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
if (!foundExternal) hasChanges = true; if (!foundExternal) hasChanges = true;
} }
// Wenn Bambu connection changed
if (bambu_connected != doc["print"]["bambu_connected"].as<bool>()) {
hasChanges = true;
}
if (!hasChanges) return; if (!hasChanges) return;
// Fortfahren mit der bestehenden Verarbeitung, da Änderungen gefunden wurden // Fortfahren mit der bestehenden Verarbeitung, da Änderungen gefunden wurden
ams_count = amsArray.size(); ams_count = amsArray.size();
// Restlicher bestehender Code...
for (int i = 0; i < ams_count && i < 16; i++) { for (int i = 0; i < ams_count && i < 16; i++) {
JsonObject amsObj = amsArray[i]; JsonObject amsObj = amsArray[i];
JsonArray trayArray = amsObj["tray"].as<JsonArray>(); JsonArray trayArray = amsObj["tray"].as<JsonArray>();
@ -331,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_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].nozzle_temp_max = trayObj["nozzle_temp_max"].as<int>();
ams_data[i].trays[j].setting_id = trayObj["setting_id"].as<String>(); ams_data[i].trays[j].setting_id = trayObj["setting_id"].as<String>();
} ams_data[i].trays[j].cali_idx = trayObj["cali_idx"].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 ...
} }
} }
@ -369,6 +375,7 @@ 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_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].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].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 ams_count++; // Erhöhe ams_count für die externe Spule
} }
@ -396,12 +403,60 @@ 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_min"] = ams_data[i].trays[j].nozzle_temp_min;
trayObj["nozzle_temp_max"] = ams_data[i].trays[j].nozzle_temp_max; trayObj["nozzle_temp_max"] = ams_data[i].trays[j].nozzle_temp_max;
trayObj["setting_id"] = ams_data[i].trays[j].setting_id; trayObj["setting_id"] = ams_data[i].trays[j].setting_id;
trayObj["cali_idx"] = ams_data[i].trays[j].cali_idx;
} }
} }
serializeJson(wsArray, amsJsonData); serializeJson(wsArray, amsJsonData);
sendAmsData(nullptr); 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() { void reconnect() {
@ -512,3 +567,10 @@ bool setupMqtt() {
} }
return true; 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_min;
int nozzle_temp_max; int nozzle_temp_max;
String setting_id; String setting_id;
String cali_idx;
}; };
#define MAX_AMS 17 // 16 normale AMS + 1 externe Spule #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(); bool setupMqtt();
void mqtt_loop(void * parameter); void mqtt_loop(void * parameter);
bool setBambuSpool(String payload); bool setBambuSpool(String payload);
void bambu_restart();
#endif #endif

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") { else if (doc["type"] == "setBambuSpool") {
Serial.println(doc["payload"].as<String>()); Serial.println(doc["payload"].as<String>());
setBambuSpool(doc["payload"]); 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 // Funktion zum Laden und Ersetzen des Headers in einer HTML-Datei
String loadHtmlWithHeader(const char* filename) { 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!"); Serial.println("Fehler: Datei nicht gefunden!");
return "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"); File file = SPIFFS.open(filename, "r");
String html = file.readString(); String html = file.readString();
file.close(); file.close();
// Ersetze den Platzhalter mit dem Header
html.replace("{{header}}", header);
return html; return html;
} }
@ -159,25 +161,31 @@ void setupWebserver(AsyncWebServer &server) {
Serial.print("Geladene Spoolman-URL: "); Serial.print("Geladene Spoolman-URL: ");
Serial.println(spoolmanUrl); Serial.println(spoolmanUrl);
// Route für die Startseite // Route für about
server.on("/about", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/about", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für / erhalten"); Serial.println("Anfrage für /about erhalten");
String html = loadHtmlWithHeader("/index.html"); AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/about.html.gz", "text/html");
request->send(200, "text/html", html); response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
}); });
// Route für Waage // Route für Waage
server.on("/waage", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/waage", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /waage erhalten"); Serial.println("Anfrage für /waage erhalten");
String html = loadHtmlWithHeader("/waage.html"); AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/waage.html.gz", "text/html");
request->send(200, "text/html", html); response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
}); });
// Route für RFID // Route für RFID
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /rfid erhalten"); Serial.println("Anfrage für /rfid erhalten");
String html = loadHtmlWithHeader("/rfid.html"); AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/rfid.html.gz", "text/html");
request->send(200, "text/html", html); response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
Serial.println("RFID-Seite gesendet"); Serial.println("RFID-Seite gesendet");
}); });
@ -201,8 +209,10 @@ void setupWebserver(AsyncWebServer &server) {
// Route für WiFi // Route für WiFi
server.on("/wifi", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/wifi", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /wifi erhalten"); Serial.println("Anfrage für /wifi erhalten");
String html = loadHtmlWithHeader("/wifi.html"); AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/wifi.html.gz", "text/html");
request->send(200, "text/html", html); response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
}); });
// Route für Spoolman Setting // Route für Spoolman Setting