Compare commits
	
		
			49 Commits
		
	
	
		
			v2.0.0-bet
			...
			v2.0.2-bet
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b80184bf23 | |||
| 0f63880d1f | |||
| 0baa1d286e | |||
| 100328b1d6 | |||
| 9ec5bca652 | |||
| 1dba2b2f23 | |||
| cca0bd9dbe | |||
| 818094c36e | |||
| 4cf3858d0a | |||
| 66eef2242b | |||
| 87288e606b | |||
| 9ae9e80dcd | |||
| f2b38a5a99 | |||
| ab005b3dd1 | |||
| e537c6ec07 | |||
| bec769e95a | |||
| 5cc58927a6 | |||
| afde3f5f81 | |||
| 6800c88bb2 | |||
| 6172242f24 | |||
| 7f4b3b8d90 | |||
| 7a15424bc7 | |||
| 039a29fa3c | |||
| 6cccf3d603 | |||
| 693ee839e5 | |||
| 0bf383ecd9 | |||
| 6451d91c59 | |||
| 8d82e221b5 | |||
| bf63ecd594 | |||
| 0daa3a148b | |||
| 602642c203 | |||
| 458bd2e67b | |||
| e6a5cb29a9 | |||
| 6502bb7185 | |||
| 63fafa2463 | |||
| f664e85933 | |||
| 7bf9868d79 | |||
| b9e488d675 | |||
| 2e3fc19741 | |||
| 4d84169b29 | |||
| 10aeb9bc52 | |||
| 00b9bc08af | |||
| dfe9e4dbe9 | |||
| 79eacae225 | |||
| d5d7358f58 | |||
| 9b362b3c73 | |||
| bc51956793 | |||
| 5666a58da2 | |||
| a35f15eca5 | 
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -39,4 +39,5 @@ _local/* | ||||
| website/* | ||||
| release.sh | ||||
| .github/copilot-instructions.md | ||||
| data | ||||
| data | ||||
| wiki | ||||
							
								
								
									
										1160
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										1160
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										468
									
								
								WIKI_DE.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										468
									
								
								WIKI_DE.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,468 @@ | ||||
| # FilaMan Wiki - Deutsch | ||||
|  | ||||
| ## Inhaltsverzeichnis | ||||
|  | ||||
| 1. [Überblick](#überblick) | ||||
| 2. [Installation](#installation) | ||||
| 3. [Hardware-Anforderungen](#hardware-anforderungen) | ||||
| 4. [Ersteinrichtung](#ersteinrichtung) | ||||
| 5. [Konfiguration](#konfiguration) | ||||
| 6. [Benutzung](#benutzung) | ||||
| 7. [NFC-Tags](#nfc-tags) | ||||
| 8. [Bambu Lab Integration](#bambu-lab-integration) | ||||
| 9. [Spoolman Integration](#spoolman-integration) | ||||
| 10. [Octoprint Integration](#octoprint-integration) | ||||
| 11. [Hersteller Tags](#hersteller-tags) | ||||
| 12. [Fehlerbehebung](#fehlerbehebung) | ||||
| 13. [Support](#support) | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Überblick | ||||
|  | ||||
| FilaMan ist ein umfassendes Filament-Managementsystem für 3D-Drucker, das auf ESP32-Hardware basiert. Es bietet Gewichtsmessung, NFC-Tag-Management und nahtlose Integration mit Spoolman und Bambu Lab 3D-Druckern. | ||||
|  | ||||
| ### Hauptfunktionen | ||||
|  | ||||
| - **Präzise Gewichtsmessung** mit HX711 Wägezellen-Verstärker | ||||
| - **NFC-Tag Lesen und Schreiben** für Filament-Identifikation | ||||
| - **OLED-Display** für Status-Anzeigen | ||||
| - **WiFi-Konnektivität** mit einfacher Konfiguration | ||||
| - **Webbasierte Benutzeroberfläche** mit Echtzeit-Updates | ||||
| - **Spoolman-Integration** für Lagerverwaltung | ||||
| - **Bambu Lab AMS-Steuerung** via MQTT | ||||
| - **Openspool NFC-Format** Kompatibilität | ||||
| - **Hersteller Tag Unterstützung** für automatische Einrichtung | ||||
|  | ||||
| ### Systemvoraussetzungen | ||||
|  | ||||
| - **ESP32 Development Board** | ||||
| - **Spoolman Instanz** (erforderlich für volle Funktionalität) | ||||
| - **WiFi-Netzwerk** | ||||
| - **Webbrowser** (Chrome/Firefox/Safari) | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
| ### Einfache Installation (Empfohlen) | ||||
|  | ||||
| 1. **Öffnen Sie den [FilaMan Web-Installer](https://www.filaman.app/installer.html)** | ||||
|    - Verwenden Sie einen Chrome-basierten Browser | ||||
|  | ||||
| 2. **ESP32 vorbereiten** | ||||
|    - Verbinden Sie den ESP32 über USB mit Ihrem Computer | ||||
|    - Klicken Sie auf "Connect" | ||||
|  | ||||
| 3. **Port auswählen** | ||||
|    - Wählen Sie den entsprechenden USB-Port aus | ||||
|    - Bestätigen Sie die Auswahl | ||||
|  | ||||
| 4. **Installation starten** | ||||
|    - Klicken Sie auf "FilaMan installieren" | ||||
|    - Warten Sie, bis der Installationsvorgang abgeschlossen ist | ||||
|  | ||||
| ### Manuelle Kompilierung | ||||
|  | ||||
| Für erfahrene Benutzer mit PlatformIO: | ||||
|  | ||||
| ```bash | ||||
| git clone https://github.com/ManuelW77/Filaman.git | ||||
| cd FilaMan/esp32 | ||||
| pio lib install | ||||
| pio run --target upload | ||||
| ``` | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Hardware-Anforderungen | ||||
|  | ||||
| ### Erforderliche Komponenten | ||||
|  | ||||
| | Komponente | Beschreibung | Amazon Link (Affiliate) | | ||||
| |------------|--------------|-------------------------| | ||||
| | ESP32 Development Board | Jede ESP32-Variante | [Amazon](https://amzn.to/3FHea6D) | | ||||
| | HX711 + Wägezelle | 5kg Load Cell Amplifier | [Amazon](https://amzn.to/4ja1KTe) | | ||||
| | OLED Display | 0.96" I2C 128x64 SSD1306 | [Amazon](https://amzn.to/445aaa9) | | ||||
| | PN532 NFC Modul | V3 RFID-Modul | [Amazon](https://amzn.eu/d/gy9vaBX) | | ||||
| | NFC Tags | NTAG213/NTAG215 | [Amazon](https://amzn.to/3E071xO) | | ||||
| | TTP223 Touch Sensor | Optional für Tara-Funktion | [Amazon](https://amzn.to/4hTChMK) | | ||||
|  | ||||
| ### Pin-Konfiguration | ||||
|  | ||||
| | Komponente | ESP32 Pin | Funktion | | ||||
| |------------|-----------|----------| | ||||
| | HX711 DOUT | 16 | Datenausgang Wägezelle | | ||||
| | HX711 SCK | 17 | Takt Wägezelle | | ||||
| | OLED SDA | 21 | I2C Daten | | ||||
| | OLED SCL | 22 | I2C Takt | | ||||
| | PN532 IRQ | 32 | Interrupt | | ||||
| | PN532 RESET | 33 | Reset | | ||||
| | PN532 SDA | 21 | I2C Daten (geteilt) | | ||||
| | PN532 SCL | 22 | I2C Takt (geteilt) | | ||||
| | TTP223 I/O | 25 | Touch-Sensor (optional) | | ||||
|  | ||||
| ### Wichtige Hinweise | ||||
|  | ||||
| - **PN532 DIP-Schalter** müssen auf I2C-Modus eingestellt sein | ||||
| - **3V Pin** vom ESP32 für Touch-Sensor verwenden | ||||
| - **Wägezellen-Verkabelung**: E+ (rot), E- (schwarz), A- (weiß), A+ (grün) | ||||
|  | ||||
|  | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Ersteinrichtung | ||||
|  | ||||
| ### Nach der Installation | ||||
|  | ||||
| 1. **ESP32 Neustart** | ||||
|    - Das System erstellt automatisch einen WiFi-Hotspot "FilaMan" | ||||
|  | ||||
| 2. **WiFi-Konfiguration** | ||||
|    - Verbinden Sie sich mit dem "FilaMan" Netzwerk | ||||
|    - Öffnen Sie einen Browser (automatisches Portal oder http://192.168.4.1) | ||||
|    - Konfigurieren Sie Ihre WiFi-Zugangsdaten | ||||
|  | ||||
| 3. **Erster Zugriff** | ||||
|    - Nach erfolgreicher WiFi-Verbindung ist das System unter http://filaman.local erreichbar | ||||
|    - Alternativ über die vom Router zugewiesene IP-Adresse | ||||
|  | ||||
| ### Spoolman Vorbereitung | ||||
|  | ||||
| **Wichtiger Hinweis**: Spoolman muss im Debug-Modus laufen: | ||||
|  | ||||
| ```env | ||||
| # In der .env Datei von Spoolman auskommentieren: | ||||
| SPOOLMAN_DEBUG_MODE=TRUE | ||||
| ``` | ||||
|  | ||||
| Dies ist erforderlich, da Spoolman noch keine CORS-Domain-Konfiguration unterstützt. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Konfiguration | ||||
|  | ||||
| ### Waagen-Kalibrierung | ||||
|  | ||||
| 1. **Kalibrierung starten** | ||||
|    - Gehen Sie zur "Scale" (Waage) Seite | ||||
|    - Bereiten Sie ein 500g Referenzgewicht vor (z.B. Wasserglas) | ||||
|  | ||||
| 2. **Kalibrierungsschritte** | ||||
|    - Folgen Sie den Anweisungen auf dem Display | ||||
|    - Legen Sie das Gewicht auf, wenn gefordert | ||||
|    - Warten Sie, bis die Kalibrierung abgeschlossen ist | ||||
|  | ||||
| 3. **Validierung** | ||||
|    - Testen Sie die Genauigkeit mit bekannten Gewichten | ||||
|    - Bei Bedarf "Tare Scale" für Nullstellung verwenden | ||||
|  | ||||
| ### Spoolman-Verbindung | ||||
|  | ||||
| 1. **Spoolman-URL eingeben** | ||||
|    - Gehen Sie zur "Spoolman/Bambu" Seite | ||||
|    - Geben Sie die vollständige URL Ihrer Spoolman-Instanz ein | ||||
|    - Format: `http://spoolman-server:7912` | ||||
|  | ||||
| 2. **Verbindung testen** | ||||
|    - Das System prüft automatisch die Verbindung | ||||
|    - Erfolgreiche Verbindung wird durch grünen Status angezeigt | ||||
|  | ||||
| ### Bambu Lab Drucker (optional) | ||||
|  | ||||
| 1. **Drucker-Einstellungen** | ||||
|    - Öffnen Sie das Einstellungsmenü auf Ihrem Bambu-Drucker | ||||
|    - Notieren Sie sich die folgenden Daten: | ||||
|      - IP-Adresse des Druckers | ||||
|      - Access Code | ||||
|      - Serial Number | ||||
|  | ||||
| 2. **FilaMan Konfiguration** | ||||
|    - Geben Sie die Drucker-Daten in der "Spoolman/Bambu" Seite ein | ||||
|    - Aktivieren Sie "Auto Send to Bambu" für automatische AMS-Zuordnung | ||||
|  | ||||
| 3. **Auto-Send Timeout** | ||||
|    - Konfigurieren Sie die Wartezeit für automatische Spulen-Erkennung | ||||
|    - Empfohlener Wert: 10-30 Sekunden | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Benutzung | ||||
|  | ||||
| ### Grundlegende Bedienung | ||||
|  | ||||
| 1. **Filament wiegen** | ||||
|    - Platzieren Sie die Spule auf der Waage | ||||
|    - Das Gewicht wird automatisch auf dem Display und in der Weboberfläche angezeigt | ||||
|  | ||||
| 2. **NFC-Tag scannen** | ||||
|    - Halten Sie den Tag in die Nähe des PN532-Moduls | ||||
|    - Bei erkannten Tags wird die Spulen-Information angezeigt | ||||
|    - Das Gewicht wird automatisch in Spoolman aktualisiert | ||||
|  | ||||
| 3. **Status-Überwachung** | ||||
|    - **OLED-Display** zeigt aktuelles Gewicht und Verbindungsstatus | ||||
|    - **Weboberfläche** bietet detaillierte Informationen und Steuerung | ||||
|  | ||||
| ### Weboberfläche Navigation | ||||
|  | ||||
| - **Startseite**: Hauptfunktionen und aktueller Status | ||||
| - **Scale**: Waagen-Kalibrierung und -Einstellungen | ||||
| - **Spoolman/Bambu**: System-Konfiguration | ||||
| - **Statistics**: Nutzungsstatistiken (falls aktiviert) | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## NFC-Tags | ||||
|  | ||||
| ### Unterstützte Tag-Typen | ||||
|  | ||||
| - **NTAG213**: 144 Bytes (grundlegende Funktionen) | ||||
| - **NTAG215**: 504 Bytes (empfohlen) | ||||
| - **NTAG216**: 888 Bytes (erweiterte Funktionen) | ||||
|  | ||||
| ### Tag beschreiben | ||||
|  | ||||
| 1. **Spule in Spoolman vorbereiten** | ||||
|    - Erstellen Sie eine neue Spule in Spoolman | ||||
|    - Stellen Sie sicher, dass alle erforderlichen Daten eingegeben sind | ||||
|  | ||||
| 2. **Tag-Beschreibung starten** | ||||
|    - Wählen Sie die Spule aus der Liste | ||||
|    - Klicken Sie auf "Write Tag" | ||||
|    - Das Display zeigt "Waiting for Tag" | ||||
|  | ||||
| 3. **Tag auflegen** | ||||
|    - Platzieren Sie den NFC-Tag auf dem PN532-Modul | ||||
|    - Warten Sie auf die Bestätigung | ||||
|  | ||||
| 4. **Erfolgsmeldung** | ||||
|    - Bei erfolgreichem Beschreiben wird ein Häkchen angezeigt | ||||
|    - Der Tag ist nun mit der Spoolman-Spule verknüpft | ||||
|  | ||||
| ### Tag lesen | ||||
|  | ||||
| 1. **Tag scannen** | ||||
|    - Platzieren Sie die Spule mit dem NFC-Tag auf die Waage über dem NFC-Reader | ||||
|    - Bei Problemen beim Lesen: Spule etwas anders positionieren (nicht ganz an den Rand) | ||||
|    - Die Spulen-Information wird automatisch geladen | ||||
|  | ||||
| 2. **Automatische Updates** | ||||
|    - Das aktuelle Gewicht wird in Spoolman übertragen | ||||
|    - Die Spule wird in der Weboberfläche automatisch ausgewählt | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Bambu Lab Integration | ||||
|  | ||||
| ### AMS (Automatic Material System) | ||||
|  | ||||
| 1. **AMS-Status anzeigen** | ||||
|    - Die Weboberfläche zeigt den aktuellen Zustand aller AMS-Fächer | ||||
|    - Beladene Fächer werden mit Filament-Informationen angezeigt | ||||
|  | ||||
| 2. **Filament manuell zuordnen** | ||||
|    - Wählen Sie eine Spule aus der Spoolman-Liste | ||||
|    - Klicken Sie auf das entsprechende AMS-Fach-Symbol | ||||
|    - Das Filament wird dem Fach zugeordnet | ||||
|  | ||||
| 3. **Automatische Zuordnung** | ||||
|    - Nach dem Wiegen mit aktiviertem "Auto Send to Bambu" | ||||
|    - Das System wartet auf neue Spulen im AMS | ||||
|    - Kalibrierte Filamente werden automatisch zugeordnet | ||||
|  | ||||
| ### Bambu Studio Integration | ||||
|  | ||||
| 1. **Filament-Profile synchronisieren** | ||||
|    - Kalibrieren Sie Filamente in Bambu Studio | ||||
|    - Verwenden Sie Device → AMS → Bleistift-Symbol → Auswählen | ||||
|  | ||||
| 2. **Setting-IDs speichern** | ||||
|    - FilaMan erkennt verfügbare Setting-IDs automatisch | ||||
|    - Klicken Sie auf "Settings in Spoolman speichern" | ||||
|    - Die Profile werden für zukünftige Drucke verwendet | ||||
|  | ||||
| ### Verbindung wiederherstellen | ||||
|  | ||||
| - Bei Verbindungsproblemen klicken Sie den roten Punkt in der Menüleiste | ||||
| - Das System stellt automatisch eine neue Verbindung her | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Spoolman Integration | ||||
|  | ||||
| ### Automatische Funktionen | ||||
|  | ||||
| 1. **Spulen-Synchronisation** | ||||
|    - Automatische Übertragung von Gewichtsänderungen | ||||
|    - Echtzeit-Updates der Spulen-Daten | ||||
|  | ||||
| 2. **Extra-Felder** | ||||
|    - FilaMan erstellt automatisch erforderliche benutzerdefinierte Felder | ||||
|    - NFC-Tag-UID wird als Referenz gespeichert | ||||
|  | ||||
| 3. **Filterung** | ||||
|    - "Nur Spulen ohne NFC-Tag anzeigen" für einfache Tag-Zuordnung | ||||
|    - Kategorisierung nach Herstellern und Materialtypen | ||||
|  | ||||
| ### Spoolman Octoprint Plugin | ||||
|  | ||||
| Für Octoprint-Benutzer ist eine automatische Spulen-Zuordnung verfügbar: | ||||
|  | ||||
| 1. **Plugin installieren** | ||||
|    ``` | ||||
|    https://github.com/ManuelW77/OctoPrint-Spoolman-Filaman/archive/refs/heads/master.zip | ||||
|    ``` | ||||
|  | ||||
| 2. **FilaMan konfigurieren** | ||||
|    - Aktivieren Sie "Send to Octo-Plugin" | ||||
|    - Geben Sie Octoprint-URL und API-Key ein | ||||
|  | ||||
| 3. **Automatische Zuordnung** | ||||
|    - Nach dem Wiegen wird die Spule automatisch in Octoprint aktiviert | ||||
|    - Unterstützt aktuell nur Tool0 (erste Düse) | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Hersteller Tags | ||||
|  | ||||
| ### Überblick | ||||
|  | ||||
| Hersteller Tags ermöglichen es Filament-Produzenten, vorkonfigurierte NFC-Tags zu liefern, die automatisch alle notwendigen Einträge in Spoolman erstellen. | ||||
|  | ||||
| ### Erste Schritte mit Hersteller Tags | ||||
|  | ||||
| 1. **Tag scannen** | ||||
|    - Platzieren Sie die Spule mit dem Hersteller-Tag auf die Waage über dem NFC-Reader | ||||
|    - Bei Problemen beim Lesen: Spule etwas anders positionieren (nicht ganz an den Rand) | ||||
|    - Das System erkennt automatisch das Hersteller-Format | ||||
|  | ||||
| 2. **Automatische Erstellung** | ||||
|    - **Marke** wird in Spoolman angelegt (falls nicht vorhanden) | ||||
|    - **Filament-Typ** wird mit allen Spezifikationen erstellt | ||||
|    - **Spule** wird automatisch registriert | ||||
|  | ||||
| 3. **Zukünftige Scans** | ||||
|    - Nach der ersten Einrichtung nutzen Tags das Fast-Path-System | ||||
|    - Sofortige Gewichtsmessung ohne erneute Einrichtung | ||||
|  | ||||
| ### Unterstützte Hersteller | ||||
|  | ||||
| - **RecyclingFabrik**: Erster offizieller Partner | ||||
| - Weitere Hersteller folgen | ||||
|  | ||||
| ### Vorteile | ||||
|  | ||||
| - ✅ **Null manuelle Einrichtung** | ||||
| - ✅ **Perfekte Datengenauigkeit** | ||||
| - ✅ **Sofortige Integration** | ||||
| - ✅ **Zukunftssicher** | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Fehlerbehebung | ||||
|  | ||||
| ### Häufige Probleme | ||||
|  | ||||
| #### WiFi-Verbindung | ||||
|  | ||||
| **Problem**: Kann nicht mit FilaMan-Hotspot verbinden | ||||
| - Lösung: Stellen Sie sicher, dass der ESP32 gestartet ist | ||||
| - Alternative: Manuell zu http://192.168.4.1 navigieren | ||||
|  | ||||
| **Problem**: Weboberfläche nicht erreichbar | ||||
| - Lösung: Prüfen Sie die IP-Adresse im Router | ||||
| - Alternative: Verwenden Sie http://filaman.local | ||||
|  | ||||
| #### Waage | ||||
|  | ||||
| **Problem**: Ungenaue Gewichtsmessungen | ||||
| - Lösung: Kalibrierung wiederholen | ||||
| - Tipp: Verwenden Sie "Tare Scale" für Nullstellung | ||||
|  | ||||
| **Problem**: Wägezelle reagiert nicht | ||||
| - Lösung: Überprüfen Sie die Verkabelung (E+, E-, A+, A-) | ||||
| - Tipp: Testen Sie mit einem Multimeter | ||||
|  | ||||
| #### NFC-Tags | ||||
|  | ||||
| **Problem**: Tag wird nicht erkannt | ||||
| - Lösung: Überprüfen Sie die PN532 DIP-Schalter (I2C-Modus) | ||||
| - Tipp: Spule etwas anders auf der Waage positionieren (nicht ganz an den Rand) | ||||
|  | ||||
| **Problem**: Tag kann nicht beschrieben werden | ||||
| - Lösung: Verwenden Sie NTAG215 für bessere Kompatibilität | ||||
| - Tipp: Stellen Sie sicher, dass der Tag nicht schreibgeschützt ist | ||||
|  | ||||
| #### Spoolman | ||||
|  | ||||
| **Problem**: Verbindung zu Spoolman schlägt fehl | ||||
| - Lösung: Aktivieren Sie SPOOLMAN_DEBUG_MODE=TRUE | ||||
| - Tipp: Überprüfen Sie die URL-Formatierung | ||||
|  | ||||
| **Problem**: Spulen werden nicht angezeigt | ||||
| - Lösung: Stellen Sie sicher, dass Spoolman läuft | ||||
| - Tipp: Prüfen Sie die Netzwerk-Firewall-Einstellungen | ||||
|  | ||||
| #### Bambu Lab | ||||
|  | ||||
| **Problem**: Drucker verbindet nicht | ||||
| - Lösung: Überprüfen Sie Access Code und IP-Adresse | ||||
| - Tipp: Stellen Sie sicher, dass der Drucker im LAN-Modus ist | ||||
|  | ||||
| **Problem**: AMS-Status wird nicht angezeigt | ||||
| - Lösung: Prüfen Sie die MQTT-Verbindung | ||||
| - Hinweis: Bambu kann die API jederzeit schließen | ||||
|  | ||||
| ### Debug-Informationen | ||||
|  | ||||
| Falls Sie Probleme haben, können Sie diese Schritte zur Diagnose verwenden: | ||||
|  | ||||
| #### Serieller Monitor (für Entwickler) | ||||
| - Verbinden Sie den ESP32 über USB mit Ihrem Computer | ||||
| - Öffnen Sie einen seriellen Monitor (z.B. Arduino IDE) mit 115200 Baud | ||||
| - Sie sehen detaillierte Log-Nachrichten des Systems | ||||
|  | ||||
| #### Browser-Konsole | ||||
| - Öffnen Sie die Weboberfläche von FilaMan | ||||
| - Drücken Sie F12 um die Entwicklertools zu öffnen   | ||||
| - Schauen Sie in der Konsole nach Fehlermeldungen | ||||
|  | ||||
| #### Neustart bei anhaltenden Problemen | ||||
| 1. ESP32 vom Strom trennen | ||||
| 2. 10 Sekunden warten | ||||
| 3. Wieder anschließen | ||||
| 4. 30 Sekunden für vollständigen Start warten | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Support | ||||
|  | ||||
| ### Community | ||||
|  | ||||
| - **Discord Server**: [https://discord.gg/my7Gvaxj2v](https://discord.gg/my7Gvaxj2v) | ||||
| - **GitHub Issues**: [Filaman Repository](https://github.com/ManuelW77/Filaman/issues) | ||||
| - **YouTube Kanal**: [Deutsches Erklärvideo](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaOHU) | ||||
|  | ||||
| ### Dokumentation | ||||
|  | ||||
| - **Offizielle Website**: [www.filaman.app](https://www.filaman.app) | ||||
| - **GitHub Wiki**: [Detaillierte Dokumentation](https://github.com/ManuelW77/Filaman/wiki) | ||||
| - **Hardware-Referenz**: ESP32 Pinout-Diagramme in `/img/` | ||||
|  | ||||
| ### Entwicklung unterstützen | ||||
|  | ||||
| Wenn Sie das Projekt unterstützen möchten: | ||||
|  | ||||
| [](https://www.buymeacoffee.com/manuelw) | ||||
|  | ||||
| ### Lizenz | ||||
|  | ||||
| Dieses Projekt ist unter der MIT-Lizenz veröffentlicht. Siehe [LICENSE](LICENSE.txt) für Details. | ||||
|  | ||||
| --- | ||||
|  | ||||
| **Letzte Aktualisierung**: August 2025 | ||||
| **Version**: 2.0 | ||||
| **Maintainer**: Manuel W. | ||||
							
								
								
									
										746
									
								
								WIKI_EN.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										746
									
								
								WIKI_EN.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,746 @@ | ||||
| # FilaMan Wiki - English | ||||
|  | ||||
| ## Table of Contents | ||||
|  | ||||
| 1. [Overview](#overview) | ||||
| 2. [Installation](#installation) | ||||
| 3. [Hardware Requirements](#hardware-requirements) | ||||
| 4. [Initial Setup](#initial-setup) | ||||
| 5. [Configuration](#configuration) | ||||
| 6. [Usage](#usage) | ||||
| 7. [NFC Tags](#nfc-tags) | ||||
| 8. [Bambu Lab Integration](#bambu-lab-integration) | ||||
| 9. [Spoolman Integration](#spoolman-integration) | ||||
| 10. [Octoprint Integration](#octoprint-integration) | ||||
| 11. [Manufacturer Tags](#manufacturer-tags) | ||||
| 12. [Troubleshooting](#troubleshooting) | ||||
| 13. [Support](#support) | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Overview | ||||
|  | ||||
| FilaMan is a comprehensive filament management system for 3D printers based on ESP32 hardware. It provides weight measurement, NFC tag management, and seamless integration with Spoolman and Bambu Lab 3D printers. | ||||
|  | ||||
| ### Key Features | ||||
|  | ||||
| - **Precise weight measurement** with HX711 load cell amplifier | ||||
| - **NFC tag reading and writing** for filament identification | ||||
| - **OLED display** for status information | ||||
| - **WiFi connectivity** with easy configuration | ||||
| - **Web-based user interface** with real-time updates | ||||
| - **Spoolman integration** for inventory management | ||||
| - **Bambu Lab AMS control** via MQTT | ||||
| - **OpenSpool NFC format** compatibility | ||||
| - **Manufacturer tag support** for automatic setup | ||||
|  | ||||
| ### System Requirements | ||||
|  | ||||
| - **ESP32 Development Board** | ||||
| - **Spoolman Instance** (required for full functionality) | ||||
| - **WiFi Network** | ||||
| - **Web Browser** (Chrome/Firefox/Safari) | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
| ### Easy Installation (Recommended) | ||||
|  | ||||
| 1. **Open the [FilaMan Web Installer](https://www.filaman.app/installer.html)** | ||||
|    - Use a Chrome-based browser | ||||
|  | ||||
| 2. **Prepare ESP32** | ||||
|    - Connect ESP32 via USB to your computer | ||||
|    - Click "Connect" | ||||
|  | ||||
| 3. **Select Port** | ||||
|    - Choose the appropriate USB port | ||||
|    - Confirm selection | ||||
|  | ||||
| 4. **Start Installation** | ||||
|    - Click "Install FilaMan" | ||||
|    - Wait for installation to complete | ||||
|  | ||||
| ### Manual Compilation | ||||
|  | ||||
| For advanced users with PlatformIO: | ||||
|  | ||||
| ```bash | ||||
| git clone https://github.com/ManuelW77/Filaman.git | ||||
| cd FilaMan/esp32 | ||||
| pio lib install | ||||
| pio run --target upload | ||||
| ``` | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Hardware Requirements | ||||
|  | ||||
| ### Required Components | ||||
|  | ||||
| | Component | Description | Amazon Link (Affiliate) | | ||||
| |-----------|-------------|-------------------------| | ||||
| | ESP32 Development Board | Any ESP32 variant | [Amazon](https://amzn.to/3FHea6D) | | ||||
| | HX711 + Load Cell | 5kg Load Cell Amplifier | [Amazon](https://amzn.to/4ja1KTe) | | ||||
| | OLED Display | 0.96" I2C 128x64 SSD1306 | [Amazon](https://amzn.to/445aaa9) | | ||||
| | PN532 NFC Module | V3 RFID Module | [Amazon](https://amzn.eu/d/gy9vaBX) | | ||||
| | NFC Tags | NTAG213/NTAG215 | [Amazon](https://amzn.to/3E071xO) | | ||||
| | TTP223 Touch Sensor | Optional for tare function | [Amazon](https://amzn.to/4hTChMK) | | ||||
|  | ||||
| ### Pin Configuration | ||||
|  | ||||
| | Component | ESP32 Pin | Function | | ||||
| |-----------|-----------|----------| | ||||
| | HX711 DOUT | 16 | Load cell data output | | ||||
| | HX711 SCK | 17 | Load cell clock | | ||||
| | OLED SDA | 21 | I2C data | | ||||
| | OLED SCL | 22 | I2C clock | | ||||
| | PN532 IRQ | 32 | Interrupt | | ||||
| | PN532 RESET | 33 | Reset | | ||||
| | PN532 SDA | 21 | I2C data (shared) | | ||||
| | PN532 SCL | 22 | I2C clock (shared) | | ||||
| | TTP223 I/O | 25 | Touch sensor (optional) | | ||||
|  | ||||
| ### Important Notes | ||||
|  | ||||
| - **PN532 DIP switches** must be set to I2C mode | ||||
| - **3V pin** from ESP32 for touch sensor | ||||
| - **Load cell wiring**: E+ (red), E- (black), A- (white), A+ (green) | ||||
|  | ||||
|  | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Initial Setup | ||||
|  | ||||
| ### After Installation | ||||
|  | ||||
| 1. **ESP32 Restart** | ||||
|    - System automatically creates a WiFi hotspot "FilaMan" | ||||
|  | ||||
| 2. **WiFi Configuration** | ||||
|    - Connect to the "FilaMan" network | ||||
|    - Open browser (automatic portal or <http://192.168.4.1>) | ||||
|    - Configure your WiFi credentials | ||||
|  | ||||
| 3. **First Access** | ||||
|    - After successful WiFi connection, access system at <http://filaman.local> | ||||
|    - Alternative: Use IP address assigned by router | ||||
|  | ||||
| ### Spoolman Preparation | ||||
|  | ||||
| **Important Note**: Spoolman must run in debug mode: | ||||
|  | ||||
| ```env | ||||
| # Uncomment in Spoolman's .env file: | ||||
| SPOOLMAN_DEBUG_MODE=TRUE | ||||
| ``` | ||||
|  | ||||
| This is required as Spoolman doesn't support CORS domain configuration yet. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Configuration | ||||
|  | ||||
| ### Scale Calibration | ||||
|  | ||||
| 1. **Start Calibration** | ||||
|    - Go to "Scale" page | ||||
|    - Prepare a 500g reference weight (e.g., water glass) | ||||
|  | ||||
| 2. **Calibration Steps** | ||||
|    - Follow instructions on display | ||||
|    - Place weight when prompted | ||||
|    - Wait for calibration to complete | ||||
|  | ||||
| 3. **Validation** | ||||
|    - Test accuracy with known weights | ||||
|    - Use "Tare Scale" for zero adjustment if needed | ||||
|  | ||||
| ### Spoolman Connection | ||||
|  | ||||
| 1. **Enter Spoolman URL** | ||||
|    - Go to "Spoolman/Bambu" page | ||||
|    - Enter complete URL of your Spoolman instance | ||||
|    - Format: `http://spoolman-server:7912` | ||||
|  | ||||
| 2. **Test Connection** | ||||
|    - System automatically checks connection | ||||
|    - Successful connection shown by green status | ||||
|  | ||||
| ### Bambu Lab Printer (Optional) | ||||
|  | ||||
| 1. **Printer Settings** | ||||
|    - Open settings menu on your Bambu printer | ||||
|    - Note the following data: | ||||
|      - Printer IP address | ||||
|      - Access Code | ||||
|      - Serial Number | ||||
|  | ||||
| 2. **FilaMan Configuration** | ||||
|    - Enter printer data on "Spoolman/Bambu" page | ||||
|    - Enable "Auto Send to Bambu" for automatic AMS assignment | ||||
|  | ||||
| 3. **Auto-Send Timeout** | ||||
|    - Configure waiting time for automatic spool detection | ||||
|    - Recommended value: 10-30 seconds | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Usage | ||||
|  | ||||
| ### Basic Operation | ||||
|  | ||||
| 1. **Weigh Filament** | ||||
|    - Place spool on scale | ||||
|    - Weight automatically displayed on screen and web interface | ||||
|  | ||||
| 2. **Scan NFC Tag** | ||||
|    - Hold tag near PN532 module | ||||
|    - Recognized tags display spool information | ||||
|    - Weight automatically updated in Spoolman | ||||
|  | ||||
| 3. **Status Monitoring** | ||||
|    - **OLED Display** shows current weight and connection status | ||||
|    - **Web Interface** provides detailed information and control | ||||
|  | ||||
| ### Web Interface Navigation | ||||
|  | ||||
| - **Home**: Main functions and current status | ||||
| - **Scale**: Scale calibration and settings | ||||
| - **Spoolman/Bambu**: System configuration | ||||
| - **Statistics**: Usage statistics (if enabled) | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## NFC Tags | ||||
|  | ||||
| ### Supported Tag Types | ||||
|  | ||||
| - **NTAG213**: 144 bytes (basic functions) | ||||
| - **NTAG215**: 504 bytes (recommended) | ||||
| - **NTAG216**: 888 bytes (extended functions) | ||||
|  | ||||
| ### Writing Tags | ||||
|  | ||||
| 1. **Prepare Spool in Spoolman** | ||||
|    - Create new spool in Spoolman | ||||
|    - Ensure all required data is entered | ||||
|  | ||||
| 2. **Start Tag Writing** | ||||
|    - Select spool from list | ||||
|    - Click "Write Tag" | ||||
|    - Display shows "Waiting for Tag" | ||||
|  | ||||
| 3. **Place Tag** | ||||
|    - Position NFC tag on PN532 module | ||||
|    - Wait for confirmation | ||||
|  | ||||
| 4. **Success Message** | ||||
|    - Successful writing shows checkmark | ||||
|    - Tag is now linked to Spoolman spool | ||||
|  | ||||
| ### Reading Tags | ||||
|  | ||||
| 1. **Scan Tag** | ||||
|    - Place the spool with NFC tag on the scale over the NFC reader | ||||
|    - If reading fails: Reposition spool slightly (not completely at the edge) | ||||
|    - Spool information automatically loaded | ||||
|  | ||||
| 2. **Automatic Updates** | ||||
|    - Current weight transferred to Spoolman | ||||
|    - Spool automatically selected in web interface | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Bambu Lab Integration | ||||
|  | ||||
| ### AMS (Automatic Material System) | ||||
|  | ||||
| 1. **Display AMS Status** | ||||
|    - Web interface shows current state of all AMS slots | ||||
|    - Loaded slots display filament information | ||||
|  | ||||
| 2. **Manual Filament Assignment** | ||||
|    - Select spool from Spoolman list | ||||
|    - Click corresponding AMS slot icon | ||||
|    - Filament assigned to slot | ||||
|  | ||||
| 3. **Automatic Assignment** | ||||
|    - After weighing with "Auto Send to Bambu" enabled | ||||
|    - System waits for new spools in AMS | ||||
|    - Calibrated filaments automatically assigned | ||||
|  | ||||
| ### Bambu Studio Integration | ||||
|  | ||||
| 1. **Sync Filament Profiles** | ||||
|    - Calibrate filaments in Bambu Studio | ||||
|    - Use Device → AMS → Pencil icon → Select | ||||
|  | ||||
| 2. **Save Setting IDs** | ||||
|    - FilaMan automatically detects available setting IDs | ||||
|    - Click "Save Settings to Spoolman" | ||||
|    - Profiles used for future prints | ||||
|  | ||||
| ### Restore Connection | ||||
|  | ||||
| - For connection issues, click red dot in menu bar | ||||
| - System automatically establishes new connection | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Spoolman Integration | ||||
|  | ||||
| ### Automatic Functions | ||||
|  | ||||
| 1. **Spool Synchronization** | ||||
|    - Automatic transfer of weight changes | ||||
|    - Real-time updates of spool data | ||||
|  | ||||
| 2. **Extra Fields** | ||||
|    - FilaMan automatically creates required custom fields | ||||
|    - NFC tag UID stored as reference | ||||
|  | ||||
| 3. **Filtering** | ||||
|    - "Show only spools without NFC tag" for easy tag assignment | ||||
|    - Categorization by manufacturers and material types | ||||
|  | ||||
| ### Spoolman Octoprint Plugin | ||||
|  | ||||
| For Octoprint users, automatic spool assignment is available: | ||||
|  | ||||
| 1. **Install Plugin** | ||||
|  | ||||
|    ```text | ||||
|    https://github.com/ManuelW77/OctoPrint-Spoolman-Filaman/archive/refs/heads/master.zip | ||||
|    ``` | ||||
|  | ||||
| 2. **Configure FilaMan** | ||||
|    - Enable "Send to Octo-Plugin" | ||||
|    - Enter Octoprint URL and API key | ||||
|  | ||||
| 3. **Automatic Assignment** | ||||
|    - After weighing, spool automatically activated in Octoprint | ||||
|    - Currently supports only Tool0 (first nozzle) | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Manufacturer Tags | ||||
|  | ||||
| ### Overview | ||||
|  | ||||
| Manufacturer tags allow filament producers to provide pre-configured NFC tags that automatically create all necessary entries in Spoolman. | ||||
|  | ||||
| ### Getting Started with Manufacturer Tags | ||||
|  | ||||
| 1. **Scan Tag** | ||||
|    - Place spool with manufacturer tag on the scale over the NFC reader | ||||
|    - If reading fails: Reposition spool slightly (not completely at the edge) | ||||
|    - System automatically recognizes manufacturer format | ||||
|  | ||||
| 2. **Automatic Creation** | ||||
|    - **Brand** created in Spoolman (if not present) | ||||
|    - **Filament type** created with all specifications | ||||
|    - **Spool** automatically registered | ||||
|  | ||||
| 3. **Future Scans** | ||||
|    - After initial setup, tags use fast-path system | ||||
|    - Immediate weight measurement without re-setup | ||||
|  | ||||
| ### Supported Manufacturers | ||||
|  | ||||
| - **RecyclingFabrik**: First official partner | ||||
| - More manufacturers coming soon | ||||
|  | ||||
| ### Benefits | ||||
|  | ||||
| - ✅ **Zero manual setup** | ||||
| - ✅ **Perfect data accuracy** | ||||
| - ✅ **Instant integration** | ||||
| - ✅ **Future-proof** | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Troubleshooting | ||||
|  | ||||
| ### Common Issues | ||||
|  | ||||
| #### WiFi Connection | ||||
|  | ||||
| **Issue**: Cannot connect to FilaMan hotspot | ||||
|  | ||||
| - Solution: Ensure ESP32 is started | ||||
| - Alternative: Manually navigate to <http://192.168.4.1> | ||||
|  | ||||
| **Issue**: Web interface not accessible | ||||
|  | ||||
| - Solution: Check IP address in router | ||||
| - Alternative: Use <http://filaman.local> | ||||
|  | ||||
| #### Scale | ||||
|  | ||||
| **Issue**: Inaccurate weight measurements | ||||
|  | ||||
| - Solution: Repeat calibration | ||||
| - Tip: Use "Tare Scale" for zero adjustment | ||||
|  | ||||
| **Issue**: Load cell not responding | ||||
|  | ||||
| - Solution: Check wiring (E+, E-, A+, A-) | ||||
| - Tip: Test with multimeter | ||||
|  | ||||
| #### NFC Tags | ||||
|  | ||||
| **Issue**: Tag not recognized | ||||
|  | ||||
| - Solution: Check PN532 DIP switches (I2C mode) | ||||
| - Tip: Reposition spool slightly on scale (not completely at the edge) | ||||
|  | ||||
| **Issue**: Cannot write tag | ||||
|  | ||||
| - Solution: Use NTAG215 for better compatibility | ||||
| - Tip: Ensure tag is not write-protected | ||||
|  | ||||
| #### Spoolman | ||||
|  | ||||
| **Issue**: Connection to Spoolman fails | ||||
|  | ||||
| - Solution: Enable SPOOLMAN_DEBUG_MODE=TRUE | ||||
| - Tip: Check URL formatting | ||||
|  | ||||
| **Issue**: Spools not displayed | ||||
|  | ||||
| - Solution: Ensure Spoolman is running | ||||
| - Tip: Check network firewall settings | ||||
|  | ||||
| #### Bambu Lab | ||||
|  | ||||
| **Issue**: Printer won't connect | ||||
|  | ||||
| - Solution: Check access code and IP address | ||||
| - Tip: Ensure printer is in LAN mode | ||||
|  | ||||
| **Issue**: AMS status not displayed | ||||
|  | ||||
| - Solution: Check MQTT connection | ||||
| - Note: Bambu may close API at any time | ||||
|  | ||||
| ### Debug Information | ||||
|  | ||||
| If you have problems, you can use these steps for diagnosis: | ||||
|  | ||||
| #### Serial Monitor (for developers) | ||||
|  | ||||
| - Connect the ESP32 via USB to your computer | ||||
| - Open a serial monitor (e.g., Arduino IDE) with 115200 baud | ||||
| - You will see detailed log messages from the system | ||||
|  | ||||
| #### Browser Console | ||||
|  | ||||
| - Open the FilaMan web interface | ||||
| - Press F12 to open developer tools | ||||
| - Check the console for error messages | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Maintenance and Updates | ||||
|  | ||||
| ### Firmware Update | ||||
|  | ||||
| 1. **Via Web Interface**: Access `http://filaman.local/upgrade.html` | ||||
| 2. **Select firmware file** (.bin format) | ||||
| 3. **Upload** - System restarts automatically | ||||
| 4. **Configuration preserved** - Settings remain intact | ||||
|  | ||||
| ### System Reset | ||||
|  | ||||
| For persistent issues: | ||||
|  | ||||
| 1. Disconnect ESP32 from power | ||||
| 2. Wait 10 seconds | ||||
| 3. Reconnect | ||||
| 4. Wait 30 seconds for complete startup | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Support and Information | ||||
|  | ||||
| **Manufacturer**: Your Company Name | ||||
| **Maintainer**: Manuel W. | ||||
|  | ||||
| ### Scale Technology | ||||
|  | ||||
| #### Weight Stabilization | ||||
|  | ||||
| The system uses multiple filters for precise measurements: | ||||
|  | ||||
| ```cpp | ||||
| // Moving Average Filter with 8 values | ||||
| #define MOVING_AVERAGE_SIZE 8 | ||||
| // Low-Pass Filter for smoothing | ||||
| #define LOW_PASS_ALPHA 0.3f | ||||
| // Thresholds for updates | ||||
| #define DISPLAY_THRESHOLD 0.3f    // Display update | ||||
| #define API_THRESHOLD 1.5f        // API actions | ||||
| ``` | ||||
|  | ||||
| #### Calibration Algorithm | ||||
|  | ||||
| 1. **System Pause**: All tasks are temporarily paused | ||||
| 2. **Zero Setting**: Tare scale without weight | ||||
| 3. **Reference Measurement**: 500g weight for 10 measurements | ||||
| 4. **Calculation**: `newValue = rawValue / SCALE_LEVEL_WEIGHT` | ||||
| 5. **NVS Storage**: Permanent value with verification | ||||
| 6. **Filter Reset**: New baseline for stabilization | ||||
|  | ||||
| #### Auto-Tare Logic | ||||
|  | ||||
| ```cpp | ||||
| // Conditions for Auto-Tare | ||||
| if (autoTare && (weight > 2 && weight < 7) || weight < -2) { | ||||
|     scale_tare_counter++; | ||||
|     if (scale_tare_counter >= 5) { | ||||
|         // Automatic zero setting | ||||
|         scale.tare(); | ||||
|         resetWeightFilter(); | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### NFC Technology | ||||
|  | ||||
| #### PN532 Communication | ||||
|  | ||||
| - **Interface**: I2C at 400kHz | ||||
| - **IRQ Pin**: Interrupt-based tag detection | ||||
| - **Reset Handling**: Automatic recovery from communication errors | ||||
| - **DIP Switches**: Must be set to I2C mode (00) | ||||
|  | ||||
| #### NDEF Implementation | ||||
|  | ||||
| ```json | ||||
| // FilaMan Spoolman Format (with sm_id) | ||||
| { | ||||
|   "sm_id": "123", | ||||
|   "color": "#FF5733", | ||||
|   "type": "PLA",  | ||||
|   "brand": "Example Brand" | ||||
| } | ||||
| ``` | ||||
|  | ||||
| #### Manufacturer Tag Schema | ||||
|  | ||||
| Compact JSON format for storage efficiency: | ||||
|  | ||||
| ```json | ||||
| { | ||||
|   "b": "RecyclingFabrik",           // brand | ||||
|   "an": "FX1_PLA-S175-1000-RED",  // article number | ||||
|   "t": "PLA",                      // type | ||||
|   "c": "FF0000",                   // color (hex without #) | ||||
|   "cn": "Red",                     // color name | ||||
|   "et": "210",                     // extruder temp | ||||
|   "bt": "60",                      // bed temp | ||||
|   "di": "1.75",                    // diameter | ||||
|   "de": "1.24",                    // density | ||||
|   "sw": "240",                      // spool weight | ||||
|   "u": "https://www.yoururl.com/search?q=" // URL used vor Brand Link and Filament Link | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Display System | ||||
|  | ||||
| #### OLED Architecture (SSD1306) | ||||
|  | ||||
| - **Resolution**: 128x64 pixels monochrome | ||||
| - **Areas**: | ||||
|   - Status bar: 0-16 pixels (version, icons) | ||||
|   - Main area: 17-64 pixels (weight, messages) | ||||
| - **Update Interval**: 1 second for status line | ||||
|  | ||||
| #### Icon System | ||||
|  | ||||
| Bitmap icons for various states: | ||||
|  | ||||
| ```cpp | ||||
| // Status Icons (16x16 pixels) | ||||
| - icon_success: Checkmark for successful operations | ||||
| - icon_failed: X for errors | ||||
| - icon_transfer: Arrow for data transmission | ||||
| - icon_loading: Loading circle for ongoing operations | ||||
|  | ||||
| // Connection Icons with strikethrough indicator | ||||
| - wifi_on/wifi_off: WLAN status | ||||
| - bambu_on: Bambu Lab connection | ||||
| - spoolman_on: Spoolman API status | ||||
| ``` | ||||
|  | ||||
| ### API Integration | ||||
|  | ||||
| #### Spoolman REST API | ||||
|  | ||||
| FilaMan interacts with the following endpoints: | ||||
|  | ||||
| ```http | ||||
| GET  /api/v1/spool/          # List spools | ||||
| POST /api/v1/spool/          # Create new spool | ||||
| PUT  /api/v1/spool/{id}/     # Update spool | ||||
|  | ||||
| GET  /api/v1/vendor/         # List vendors | ||||
| POST /api/v1/vendor/         # Create new vendor | ||||
|  | ||||
| GET  /api/v1/filament/       # List filaments | ||||
| POST /api/v1/filament/       # Create new filament | ||||
| ``` | ||||
|  | ||||
| #### Request Handling | ||||
|  | ||||
| ```cpp | ||||
| // Sequential API processing | ||||
| enum spoolmanApiStateType { | ||||
|     API_IDLE = 0, | ||||
|     API_PROCESSING = 1, | ||||
|     API_ERROR = 2 | ||||
| }; | ||||
| ``` | ||||
|  | ||||
| Prevents simultaneous API calls and deadlocks. | ||||
|  | ||||
| #### Weight Update Logic | ||||
|  | ||||
| ```cpp | ||||
| // Conditions for Spoolman update | ||||
| if (activeSpoolId != "" &&  | ||||
|     weigthCouterToApi > 3 &&    // 3+ stable measurements | ||||
|     weightSend == 0 &&          // Not yet sent | ||||
|     weight > 5 &&               // Minimum weight 5g | ||||
|     spoolmanApiState == API_IDLE) { | ||||
|     updateSpoolWeight(activeSpoolId, weight); | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Bambu Lab MQTT | ||||
|  | ||||
| #### Connection Parameters | ||||
|  | ||||
| ```cpp | ||||
| // SSL/TLS Configuration | ||||
| #define BAMBU_PORT 8883 | ||||
| #define BAMBU_USERNAME "bblp" | ||||
|  | ||||
| // Topic Structure | ||||
| String topic = "device/" + bambu_serial + "/report"; | ||||
| String request_topic = "device/" + bambu_serial + "/request"; | ||||
| ``` | ||||
|  | ||||
| #### AMS Data Structure | ||||
|  | ||||
| ```cpp | ||||
| struct AMSData { | ||||
|     String tray_id; | ||||
|     String tray_type; | ||||
|     String tray_color; | ||||
|     String tray_material; | ||||
|     String setting_id; | ||||
|     String tray_info_idx; | ||||
|     bool has_spool; | ||||
| }; | ||||
| ``` | ||||
|  | ||||
| #### Auto-Send Mechanism | ||||
|  | ||||
| ```cpp | ||||
| // After tag recognition | ||||
| if (bambuCredentials.autosend_enable) { | ||||
|     autoSetToBambuSpoolId = activeSpoolId.toInt(); | ||||
|     // Countdown starts automatically | ||||
|     // Waits for new spool in AMS | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### WebSocket Communication | ||||
|  | ||||
| #### Message Types | ||||
|  | ||||
| ```javascript | ||||
| // Client → Server | ||||
| { | ||||
|   "type": "writeNfcTag", | ||||
|   "tagType": "spool", | ||||
|   "payload": { /* JSON data */ } | ||||
| } | ||||
|  | ||||
| { | ||||
|   "type": "scale", | ||||
|   "payload": "tare|calibrate|setAutoTare", | ||||
|   "enabled": true | ||||
| } | ||||
|  | ||||
| // Server → Client | ||||
| { | ||||
|   "type": "heartbeat", | ||||
|   "freeHeap": 245, | ||||
|   "bambu_connected": true, | ||||
|   "spoolman_connected": true | ||||
| } | ||||
|  | ||||
| { | ||||
|   "type": "amsData", | ||||
|   "data": [ /* AMS array */ ] | ||||
| } | ||||
| ``` | ||||
|  | ||||
| #### Connection Management | ||||
|  | ||||
| - **Auto-Reconnect**: Client-side reconnection | ||||
| - **Heartbeat**: Every 30 seconds for connection monitoring | ||||
| - **Cleanup**: Automatic removal of dead connections | ||||
|  | ||||
| ### Watchdog and Error Handling | ||||
|  | ||||
| #### System Watchdog | ||||
|  | ||||
| ```cpp | ||||
| // WDT Configuration | ||||
| esp_task_wdt_init(10, true);  // 10s timeout, panic on overflow | ||||
| esp_task_wdt_add(NULL);       // Add current task | ||||
| ``` | ||||
|  | ||||
| #### Error Recovery | ||||
|  | ||||
| - **NFC Reset**: Automatic PN532 restart on communication errors | ||||
| - **MQTT Reconnect**: Bambu Lab connection automatically restored | ||||
| - **WiFi Monitoring**: Connection check every 60 seconds | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Support | ||||
|  | ||||
| ### Community | ||||
|  | ||||
| - **Discord Server**: [https://discord.gg/my7Gvaxj2v](https://discord.gg/my7Gvaxj2v) | ||||
| - **GitHub Issues**: [Filaman Repository](https://github.com/ManuelW77/Filaman/issues) | ||||
| - **YouTube Channel**: [German explanation video](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaOHU) | ||||
|  | ||||
| ### Documentation | ||||
|  | ||||
| - **Official Website**: [www.filaman.app](https://www.filaman.app) | ||||
| - **GitHub Wiki**: [Detailed documentation](https://github.com/ManuelW77/Filaman/wiki) | ||||
| - **Hardware Reference**: ESP32 pinout diagrams in `/img/` | ||||
|  | ||||
| ### Support Development | ||||
|  | ||||
| If you'd like to support the project: | ||||
|  | ||||
| [](https://www.buymeacoffee.com/manuelw) | ||||
|  | ||||
| ### License | ||||
|  | ||||
| This project is released under the MIT License. See [LICENSE](LICENSE.txt) for details. | ||||
|  | ||||
| --- | ||||
|  | ||||
| **Last Updated**: August 2025   | ||||
| **Version**: 2.0   | ||||
| **Maintainer**: Manuel W. | ||||
| @@ -9,7 +9,7 @@ | ||||
| ; https://docs.platformio.org/page/projectconf.html | ||||
|  | ||||
| [common] | ||||
| version = "2.0.0-beta4" | ||||
| version = "2.0.2-beta2" | ||||
| to_old_version = "1.5.10" | ||||
|  | ||||
| ## | ||||
|   | ||||
| @@ -14,17 +14,39 @@ def get_version(): | ||||
|         return version_match.group(1) if version_match else None | ||||
|  | ||||
| def get_last_tag(): | ||||
|     """Get the last non-beta tag for changelog generation""" | ||||
|     try: | ||||
|         result = subprocess.run(['git', 'describe', '--tags', '--abbrev=0'],  | ||||
|         # Get all tags sorted by version | ||||
|         result = subprocess.run(['git', 'tag', '-l', '--sort=-version:refname'],  | ||||
|                               capture_output=True, text=True) | ||||
|         return result.stdout.strip() | ||||
|         if result.returncode != 0: | ||||
|             return None | ||||
|              | ||||
|         tags = result.stdout.strip().split('\n') | ||||
|          | ||||
|         # Find the first (newest) non-beta tag | ||||
|         for tag in tags: | ||||
|             if tag and not '-beta' in tag.lower(): | ||||
|                 print(f"Using last stable tag for changelog: {tag}") | ||||
|                 return tag | ||||
|          | ||||
|         # Fallback: if no non-beta tags found, use the newest tag | ||||
|         print("No stable tags found, using newest tag") | ||||
|         if tags and tags[0]: | ||||
|             return tags[0] | ||||
|         return None | ||||
|     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']): | ||||
|      | ||||
|     # Check for breaking changes first | ||||
|     if ('!' in commit_msg and any(x in lower_msg for x in ['feat!', 'fix!', 'chore!', 'refactor!'])) or \ | ||||
|        'breaking change' in lower_msg or 'breaking:' in lower_msg: | ||||
|         return 'Breaking Changes' | ||||
|     elif 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' | ||||
| @@ -34,6 +56,7 @@ def categorize_commit(commit_msg): | ||||
| def get_changes_from_git(): | ||||
|     """Get changes from git commits since last tag""" | ||||
|     changes = { | ||||
|         'Breaking Changes': [], | ||||
|         'Added': [], | ||||
|         'Changed': [], | ||||
|         'Fixed': [] | ||||
| @@ -55,7 +78,9 @@ def get_changes_from_git(): | ||||
|             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() | ||||
|                 clean_msg = re.sub(r'^(feat|fix|chore|docs|style|refactor|perf|test)(\(.*\))?!?:', '', commit).strip() | ||||
|                 # Remove BREAKING CHANGE prefix if present | ||||
|                 clean_msg = re.sub(r'^breaking change:\s*', '', clean_msg, flags=re.IGNORECASE).strip() | ||||
|                 changes[category].append(clean_msg) | ||||
|                  | ||||
|     except subprocess.CalledProcessError: | ||||
|   | ||||
							
								
								
									
										106
									
								
								src/api.cpp
									
									
									
									
									
								
							
							
						
						
									
										106
									
								
								src/api.cpp
									
									
									
									
									
								
							| @@ -21,6 +21,7 @@ uint16_t foundVendorId = 0;    // Store ID of found vendor | ||||
| uint16_t foundFilamentId = 0;  // Store ID of found filament | ||||
| uint16_t createdFilamentId = 0;  // Store ID of newly created filament | ||||
| uint16_t createdSpoolId = 0;  // Store ID of newly created spool | ||||
| uint16_t updateOctoSpoolId = 0; // Store spool ID for OctoPrint update | ||||
| bool spoolmanConnected = false; | ||||
| bool spoolmanExtraFieldsChecked = false; | ||||
| TaskHandle_t* apiTask; | ||||
| @@ -124,28 +125,69 @@ void sendToApi(void *parameter) { | ||||
|     String octoToken = params->octoToken; | ||||
|     bool triggerWeightUpdate = params->triggerWeightUpdate; | ||||
|     String spoolIdForWeight = params->spoolIdForWeight; | ||||
|     uint16_t weightValue = params->weightValue;     | ||||
|     uint16_t weightValue = params->weightValue; | ||||
|  | ||||
|     HTTPClient http; | ||||
|     http.setReuse(false); | ||||
|     // Retry mechanism with configurable parameters | ||||
|     const uint8_t MAX_RETRIES = 3; | ||||
|     const uint16_t RETRY_DELAY_MS = 1000; // 1 second between retries | ||||
|     const uint16_t HTTP_TIMEOUT_MS = 10000; // 10 second HTTP timeout | ||||
|      | ||||
|     bool success = false; | ||||
|     int httpCode = -1; | ||||
|     String responsePayload = ""; | ||||
|      | ||||
|     // Try request with retries | ||||
|     for (uint8_t attempt = 1; attempt <= MAX_RETRIES && !success; attempt++) { | ||||
|         Serial.printf("API Request attempt %d/%d to: %s\n", attempt, MAX_RETRIES, spoolsUrl.c_str()); | ||||
|          | ||||
|         HTTPClient http; | ||||
|         http.setReuse(false); | ||||
|         http.setTimeout(HTTP_TIMEOUT_MS); // Set HTTP timeout | ||||
|          | ||||
|         http.begin(spoolsUrl); | ||||
|         http.addHeader("Content-Type", "application/json"); | ||||
|         if (octoEnabled && octoToken != "") http.addHeader("X-Api-Key", octoToken); | ||||
|  | ||||
|     http.begin(spoolsUrl); | ||||
|     http.addHeader("Content-Type", "application/json"); | ||||
|     if (octoEnabled && octoToken != "") http.addHeader("X-Api-Key", octoToken); | ||||
|         // Execute HTTP request based on type | ||||
|         if (httpType == "PATCH") httpCode = http.PATCH(updatePayload); | ||||
|         else if (httpType == "POST") httpCode = http.POST(updatePayload); | ||||
|         else if (httpType == "GET") httpCode = http.GET(); | ||||
|         else httpCode = http.PUT(updatePayload); | ||||
|  | ||||
|     int httpCode; | ||||
|     if (httpType == "PATCH") httpCode = http.PATCH(updatePayload); | ||||
|     else if (httpType == "POST") httpCode = http.POST(updatePayload); | ||||
|     else if (httpType == "GET") httpCode = http.GET(); | ||||
|     else httpCode = http.PUT(updatePayload); | ||||
|         // Check if request was successful | ||||
|         if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_CREATED) { | ||||
|             responsePayload = http.getString(); | ||||
|             success = true; | ||||
|             Serial.printf("API Request successful on attempt %d, HTTP Code: %d\n", attempt, httpCode); | ||||
|         } else { | ||||
|             Serial.printf("API Request failed on attempt %d, HTTP Code: %d (%s)\n",  | ||||
|                          attempt, httpCode, http.errorToString(httpCode).c_str()); | ||||
|              | ||||
|             // Don't retry on certain error codes (client errors) | ||||
|             if (httpCode >= 400 && httpCode < 500 && httpCode != 408 && httpCode != 429) { | ||||
|                 Serial.println("Client error detected, stopping retries"); | ||||
|                 break; | ||||
|             } | ||||
|              | ||||
|             // Wait before retry (except on last attempt) | ||||
|             if (attempt < MAX_RETRIES) { | ||||
|                 Serial.printf("Waiting %dms before retry...\n", RETRY_DELAY_MS); | ||||
|                 http.end(); | ||||
|                 vTaskDelay(RETRY_DELAY_MS / portTICK_PERIOD_MS); | ||||
|                 continue; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         http.end(); | ||||
|     } | ||||
|  | ||||
|     if (httpCode == HTTP_CODE_OK) { | ||||
|     // Process successful response | ||||
|     if (success) { | ||||
|         Serial.println("Spoolman Abfrage erfolgreich"); | ||||
|  | ||||
|         // Restgewicht der Spule auslesen | ||||
|         String payload = http.getString(); | ||||
|         JsonDocument doc; | ||||
|         DeserializationError error = deserializeJson(doc, payload); | ||||
|         DeserializationError error = deserializeJson(doc, responsePayload); | ||||
|         if (error) { | ||||
|             Serial.print("Fehler beim Parsen der JSON-Antwort: "); | ||||
|             Serial.println(error.c_str()); | ||||
| @@ -225,10 +267,9 @@ void sendToApi(void *parameter) { | ||||
|     } else if (httpCode == HTTP_CODE_CREATED) { | ||||
|         Serial.println("Spoolman erfolgreich erstellt"); | ||||
|          | ||||
|         // Parse response for created resources | ||||
|         String payload = http.getString(); | ||||
|         // Parse response for created resources   | ||||
|         JsonDocument doc; | ||||
|         DeserializationError error = deserializeJson(doc, payload); | ||||
|         DeserializationError error = deserializeJson(doc, responsePayload); | ||||
|         if (error) { | ||||
|             Serial.print("Fehler beim Parsen der JSON-Antwort: "); | ||||
|             Serial.println(error.c_str()); | ||||
| @@ -280,14 +321,17 @@ void sendToApi(void *parameter) { | ||||
|             Serial.println(weightPayload); | ||||
|  | ||||
|             // Execute weight update | ||||
|             http.begin(weightUrl); | ||||
|             http.addHeader("Content-Type", "application/json"); | ||||
|             HTTPClient weightHttp; | ||||
|             weightHttp.setReuse(false); | ||||
|             weightHttp.setTimeout(HTTP_TIMEOUT_MS); | ||||
|             weightHttp.begin(weightUrl); | ||||
|             weightHttp.addHeader("Content-Type", "application/json"); | ||||
|              | ||||
|             int weightHttpCode = http.PUT(weightPayload); | ||||
|             int weightHttpCode = weightHttp.PUT(weightPayload); | ||||
|              | ||||
|             if (weightHttpCode == HTTP_CODE_OK) { | ||||
|                 Serial.println("Weight update successful"); | ||||
|                 String weightResponse = http.getString(); | ||||
|                 String weightResponse = weightHttp.getString(); | ||||
|                 JsonDocument weightResponseDoc; | ||||
|                 DeserializationError weightError = deserializeJson(weightResponseDoc, weightResponse); | ||||
|                  | ||||
| @@ -310,6 +354,7 @@ void sendToApi(void *parameter) { | ||||
|                 oledShowProgressBar(1, 1, "Failure!", "Weight update"); | ||||
|             } | ||||
|              | ||||
|             weightHttp.end(); | ||||
|             weightDoc.clear(); | ||||
|         } | ||||
|     } else { | ||||
| @@ -325,14 +370,25 @@ void sendToApi(void *parameter) { | ||||
|         case API_REQUEST_BAMBU_UPDATE: | ||||
|             oledShowProgressBar(1, 1, "Failure!", "Bambu update"); | ||||
|             break; | ||||
|         case API_REQUEST_VENDOR_CHECK: | ||||
|             oledShowProgressBar(1, 1, "Failure!", "Vendor check"); | ||||
|             foundVendorId = 0; // Set to 0 to indicate error/not found | ||||
|             break; | ||||
|         case API_REQUEST_VENDOR_CREATE: | ||||
|             oledShowProgressBar(1, 1, "Failure!", "Vendor create"); | ||||
|             createdVendorId = 0; // Set to 0 to indicate error | ||||
|             break; | ||||
|         case API_REQUEST_FILAMENT_CHECK: | ||||
|             oledShowProgressBar(1, 1, "Failure!", "Filament check"); | ||||
|             foundFilamentId = 0; // Set to 0 to indicate error/not found | ||||
|             break; | ||||
|         case API_REQUEST_FILAMENT_CREATE: | ||||
|             oledShowProgressBar(1, 1, "Failure!", "Filament create"); | ||||
|             createdFilamentId = 0; // Set to 0 to indicate error | ||||
|             break; | ||||
|         case API_REQUEST_SPOOL_CREATE: | ||||
|             oledShowProgressBar(1, 1, "Failure!", "Spool create"); | ||||
|             createdSpoolId = 0; // Set to 0 to indicate error instead of hanging | ||||
|             break; | ||||
|         } | ||||
|         Serial.println("Fehler beim Senden an Spoolman! HTTP Code: " + String(httpCode)); | ||||
| @@ -340,7 +396,6 @@ void sendToApi(void *parameter) { | ||||
|         nfcReaderState = NFC_IDLE; // Reset NFC state to allow retry | ||||
|     } | ||||
|  | ||||
|     http.end(); | ||||
|     vTaskDelay(50 / portTICK_PERIOD_MS); | ||||
|  | ||||
|     // Speicher freigeben | ||||
| @@ -952,6 +1007,13 @@ uint16_t createSpool(uint16_t vendorId, uint16_t filamentId, JsonDocument& paylo | ||||
|     while(createdSpoolId == 65535) { | ||||
|         vTaskDelay(50 / portTICK_PERIOD_MS); | ||||
|     } | ||||
|      | ||||
|     // Check if spool creation was successful | ||||
|     if (createdSpoolId == 0) { | ||||
|         Serial.println("ERROR: Spool creation failed"); | ||||
|         nfcReaderState = NFC_IDLE; // Reset NFC state | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     // Write data to tag with startWriteJsonToTag | ||||
|     // void startWriteJsonToTag(const bool isSpoolTag, const char* payload); | ||||
|   | ||||
| @@ -33,6 +33,7 @@ extern bool sendOctoUpdate; | ||||
| extern String octoUrl; | ||||
| extern String octoToken; | ||||
| extern bool spoolmanConnected; | ||||
| extern uint16_t updateOctoSpoolId; | ||||
|  | ||||
| bool checkSpoolmanInstance(); | ||||
| bool saveSpoolmanUrl(const String& url, bool octoOn, const String& octoWh, const String& octoTk); | ||||
|   | ||||
| @@ -21,7 +21,7 @@ TaskHandle_t BambuMqttTask; | ||||
| bool bambuDisabled = false; | ||||
|  | ||||
| bool bambu_connected = false; | ||||
| int autoSetToBambuSpoolId = 0; | ||||
| uint16_t autoSetToBambuSpoolId = 0; | ||||
|  | ||||
| BambuCredentials bambuCredentials; | ||||
|  | ||||
|   | ||||
| @@ -37,7 +37,7 @@ extern bool bambu_connected; | ||||
| extern int ams_count; | ||||
| extern AMSData ams_data[MAX_AMS]; | ||||
| //extern bool autoSendToBambu; | ||||
| extern int autoSetToBambuSpoolId; | ||||
| extern uint16_t autoSetToBambuSpoolId; | ||||
| extern bool bambuDisabled; | ||||
| extern BambuCredentials bambuCredentials; | ||||
|  | ||||
|   | ||||
							
								
								
									
										30
									
								
								src/main.cpp
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								src/main.cpp
									
									
									
									
									
								
							| @@ -177,9 +177,11 @@ void loop() { | ||||
|     // Ausgabe der Waage auf Display | ||||
|     if(pauseMainTask == 0) | ||||
|     { | ||||
|       // Use filtered weight for smooth display, but still check API weight for significant changes | ||||
|       int16_t displayWeight = getFilteredDisplayWeight(); | ||||
|       if (mainTaskWasPaused || (weight != lastWeight && nfcReaderState == NFC_IDLE && (!bambuCredentials.autosend_enable || autoSetToBambuSpoolId == 0))) | ||||
|       { | ||||
|         (weight < 2) ? ((weight < -2) ? oledShowMessage("!! -0") : oledShowWeight(0)) : oledShowWeight(weight); | ||||
|         (displayWeight < 2) ? ((displayWeight < -2) ? oledShowMessage("!! -0") : oledShowWeight(0)) : oledShowWeight(displayWeight); | ||||
|       } | ||||
|       mainTaskWasPaused = false; | ||||
|     } | ||||
| @@ -218,7 +220,6 @@ void loop() { | ||||
|     } | ||||
|  | ||||
|     // reset weight counter after writing tag | ||||
|     // TBD: what exactly is the logic behind this? | ||||
|     if (currentMillis - lastWeightReadTime >= weightReadInterval && nfcReaderState != NFC_IDLE && nfcReaderState != NFC_READ_SUCCESS) | ||||
|     { | ||||
|       weigthCouterToApi = 0; | ||||
| @@ -241,6 +242,29 @@ void loop() { | ||||
|         { | ||||
|           autoSetToBambuSpoolId = activeSpoolId.toInt(); | ||||
|         } | ||||
|         if (octoEnabled)  | ||||
|         { | ||||
|           updateOctoSpoolId = activeSpoolId.toInt(); | ||||
|         } | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|         oledShowIcon("failed"); | ||||
|         vTaskDelay(2000 / portTICK_PERIOD_MS); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // Handle successful tag write: Send weight to Spoolman but NEVER auto-send to Bambu | ||||
|     if (activeSpoolId != "" && weigthCouterToApi > 3 && weightSend == 0 && nfcReaderState == NFC_WRITE_SUCCESS && tagProcessed == false && spoolmanApiState == API_IDLE)  | ||||
|     { | ||||
|       // set the current tag as processed to prevent it beeing processed again | ||||
|       tagProcessed = true; | ||||
|  | ||||
|       if (updateSpoolWeight(activeSpoolId, weight))  | ||||
|       { | ||||
|         weightSend = 1; | ||||
|         Serial.println("Tag written: Weight sent to Spoolman, but NO auto-send to Bambu"); | ||||
|         // INTENTIONALLY do NOT set autoSetToBambuSpoolId here to prevent Bambu auto-send | ||||
|       } | ||||
|       else | ||||
|       { | ||||
| @@ -251,7 +275,7 @@ void loop() { | ||||
|  | ||||
|     if(octoEnabled && sendOctoUpdate && spoolmanApiState == API_IDLE) | ||||
|     { | ||||
|       updateSpoolOcto(autoSetToBambuSpoolId); | ||||
|       updateSpoolOcto(updateOctoSpoolId); | ||||
|       sendOctoUpdate = false; | ||||
|     } | ||||
|   } | ||||
|   | ||||
							
								
								
									
										274
									
								
								src/nfc.cpp
									
									
									
									
									
								
							
							
						
						
									
										274
									
								
								src/nfc.cpp
									
									
									
									
									
								
							| @@ -108,6 +108,37 @@ bool formatNdefTag() { | ||||
|   return buffer[2]*8; | ||||
| } | ||||
|  | ||||
| // Robust page reading with error recovery | ||||
| bool robustPageRead(uint8_t page, uint8_t* buffer) { | ||||
|     const int MAX_READ_ATTEMPTS = 3; | ||||
|      | ||||
|     for (int attempt = 0; attempt < MAX_READ_ATTEMPTS; attempt++) { | ||||
|         esp_task_wdt_reset(); | ||||
|         yield(); | ||||
|          | ||||
|         if (nfc.ntag2xx_ReadPage(page, buffer)) { | ||||
|             return true; | ||||
|         } | ||||
|          | ||||
|         Serial.printf("Page %d read failed, attempt %d/%d\n", page, attempt + 1, MAX_READ_ATTEMPTS); | ||||
|          | ||||
|         // Try to stabilize connection between attempts | ||||
|         if (attempt < MAX_READ_ATTEMPTS - 1) { | ||||
|             vTaskDelay(pdMS_TO_TICKS(25)); | ||||
|              | ||||
|             // Re-verify tag presence with quick check | ||||
|             uint8_t uid[7]; | ||||
|             uint8_t uidLength; | ||||
|             if (!nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 100)) { | ||||
|                 Serial.println("Tag lost during read operation"); | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| String detectNtagType() | ||||
| { | ||||
|   // Read capability container from page 3 to determine exact NTAG type | ||||
| @@ -1268,6 +1299,61 @@ bool decodeNdefAndReturnJson(const byte* encodedMessage, String uidString) { | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| // Read complete JSON data for fast-path to enable web interface display | ||||
| bool readCompleteJsonForFastPath() { | ||||
|     Serial.println("=== FAST-PATH: Reading complete JSON for web interface ==="); | ||||
|      | ||||
|     // Read tag size first | ||||
|     uint16_t tagSize = readTagSize(); | ||||
|     if (tagSize == 0) { | ||||
|         Serial.println("FAST-PATH: Could not determine tag size"); | ||||
|         return false; | ||||
|     } | ||||
|      | ||||
|     // Create buffer for complete data | ||||
|     uint8_t* data = (uint8_t*)malloc(tagSize); | ||||
|     if (!data) { | ||||
|         Serial.println("FAST-PATH: Could not allocate memory for complete read"); | ||||
|         return false; | ||||
|     } | ||||
|     memset(data, 0, tagSize); | ||||
|      | ||||
|     // Read all pages | ||||
|     uint8_t numPages = tagSize / 4; | ||||
|     for (uint8_t i = 4; i < 4 + numPages; i++) { | ||||
|         if (!robustPageRead(i, data + (i - 4) * 4)) { | ||||
|             Serial.printf("FAST-PATH: Failed to read page %d\n", i); | ||||
|             free(data); | ||||
|             return false; | ||||
|         } | ||||
|          | ||||
|         // Check for NDEF message end | ||||
|         if (data[(i - 4) * 4] == 0xFE) { | ||||
|             Serial.println("FAST-PATH: Found NDEF message end marker"); | ||||
|             break; | ||||
|         } | ||||
|          | ||||
|         yield(); | ||||
|         esp_task_wdt_reset(); | ||||
|         vTaskDelay(pdMS_TO_TICKS(2)); | ||||
|     } | ||||
|      | ||||
|     // Decode NDEF and extract JSON | ||||
|     bool success = decodeNdefAndReturnJson(data, ""); // Empty UID string for fast-path | ||||
|      | ||||
|     free(data); | ||||
|      | ||||
|     if (success) { | ||||
|         Serial.println("✓ FAST-PATH: Complete JSON data successfully loaded"); | ||||
|         Serial.print("nfcJsonData length: "); | ||||
|         Serial.println(nfcJsonData.length()); | ||||
|     } else { | ||||
|         Serial.println("✗ FAST-PATH: Failed to decode complete JSON data"); | ||||
|     } | ||||
|      | ||||
|     return success; | ||||
| } | ||||
|  | ||||
| bool quickSpoolIdCheck(String uidString) { | ||||
|     // Fast-path: Read NDEF structure to quickly locate and check JSON payload | ||||
|     // This dramatically speeds up known spool recognition | ||||
| @@ -1285,10 +1371,11 @@ bool quickSpoolIdCheck(String uidString) { | ||||
|     memset(ndefData, 0, 20); | ||||
|      | ||||
|     for (uint8_t page = 4; page < 9; page++) { | ||||
|         if (!nfc.ntag2xx_ReadPage(page, ndefData + (page - 4) * 4)) { | ||||
|             Serial.print("Failed to read page "); | ||||
|             Serial.println(page); | ||||
|             return false; // Fall back to full read | ||||
|         if (!robustPageRead(page, ndefData + (page - 4) * 4)) { | ||||
|             Serial.print("FAST-PATH: Failed to read page "); | ||||
|             Serial.print(page); | ||||
|             Serial.println(" - falling back to full read"); | ||||
|             return false; // Fall back to full read if any page read fails | ||||
|         } | ||||
|     } | ||||
|      | ||||
| @@ -1358,10 +1445,11 @@ bool quickSpoolIdCheck(String uidString) { | ||||
|         memset(extraData, 0, 16); | ||||
|          | ||||
|         for (uint8_t page = 9; page < 13; page++) { | ||||
|             if (!nfc.ntag2xx_ReadPage(page, extraData + (page - 9) * 4)) { | ||||
|                 Serial.print("Failed to read additional page "); | ||||
|                 Serial.println(page); | ||||
|                 return false; | ||||
|             if (!robustPageRead(page, extraData + (page - 9) * 4)) { | ||||
|                 Serial.print("FAST-PATH: Failed to read additional page "); | ||||
|                 Serial.print(page); | ||||
|                 Serial.println(" - falling back to full read"); | ||||
|                 return false; // Fall back to full read if extended read fails | ||||
|             } | ||||
|         } | ||||
|          | ||||
| @@ -1403,6 +1491,14 @@ bool quickSpoolIdCheck(String uidString) { | ||||
|                     activeSpoolId = quickSpoolId; | ||||
|                     lastSpoolId = activeSpoolId; | ||||
|                      | ||||
|                     // Read complete JSON data for web interface display | ||||
|                     Serial.println("FAST-PATH: Reading complete JSON data for web interface..."); | ||||
|                     if (readCompleteJsonForFastPath()) { | ||||
|                         Serial.println("✓ FAST-PATH: Complete JSON data loaded for web interface"); | ||||
|                     } else { | ||||
|                         Serial.println("⚠ FAST-PATH: Could not read complete JSON, web interface may show limited data"); | ||||
|                     } | ||||
|                      | ||||
|                     oledShowProgressBar(2, octoEnabled?5:4, "Known Spool", "Quick mode"); | ||||
|                     Serial.println("✓ FAST-PATH SUCCESS: Known spool processed quickly"); | ||||
|                     return true; | ||||
| @@ -1450,6 +1546,14 @@ bool quickSpoolIdCheck(String uidString) { | ||||
|                 activeSpoolId = quickSpoolId; | ||||
|                 lastSpoolId = activeSpoolId; | ||||
|                  | ||||
|                 // Read complete JSON data for web interface display | ||||
|                 Serial.println("FAST-PATH: Reading complete JSON data for web interface..."); | ||||
|                 if (readCompleteJsonForFastPath()) { | ||||
|                     Serial.println("✓ FAST-PATH: Complete JSON data loaded for web interface"); | ||||
|                 } else { | ||||
|                     Serial.println("⚠ FAST-PATH: Could not read complete JSON, web interface may show limited data"); | ||||
|                 } | ||||
|                  | ||||
|                 oledShowProgressBar(2, octoEnabled?5:4, "Known Spool", "Quick mode"); | ||||
|                 Serial.println("✓ FAST-PATH SUCCESS: Known spool processed quickly"); | ||||
|                 return true; | ||||
| @@ -1496,6 +1600,10 @@ void writeJsonToTag(void *parameter) { | ||||
|   // aktualisieren der Website wenn sich der Status ändert | ||||
|   sendNfcData(); | ||||
|   vTaskDelay(100 / portTICK_PERIOD_MS); | ||||
|    | ||||
|   // Show waiting message for tag detection | ||||
|   oledShowProgressBar(0, 1, "Write Tag", "Warte auf Tag"); | ||||
|    | ||||
|   // Wait 10sec for tag | ||||
|   uint8_t success = 0; | ||||
|   String uidString = ""; | ||||
| @@ -1542,7 +1650,30 @@ void writeJsonToTag(void *parameter) { | ||||
|         if(params->tagType){ | ||||
|           // TBD: should this be simplified? | ||||
|           if (updateSpoolTagId(uidString, params->payload) && params->tagType) { | ||||
|              | ||||
|             // Check if weight is over 20g and send to Spoolman | ||||
|             if (weight > 20) { | ||||
|               Serial.println("Tag successfully written and weight > 20g - sending weight to Spoolman"); | ||||
|                | ||||
|               // Extract spool ID from payload for weight update | ||||
|               JsonDocument payloadDoc; | ||||
|               DeserializationError error = deserializeJson(payloadDoc, params->payload); | ||||
|                | ||||
|               if (!error && payloadDoc["sm_id"].is<String>()) { | ||||
|                 String spoolId = payloadDoc["sm_id"].as<String>(); | ||||
|                 if (spoolId != "") { | ||||
|                   Serial.printf("Updating spool %s with weight %dg\n", spoolId.c_str(), weight); | ||||
|                   updateSpoolWeight(spoolId, weight); | ||||
|                 } else { | ||||
|                   Serial.println("No valid spool ID found for weight update"); | ||||
|                 } | ||||
|               } else { | ||||
|                 Serial.println("Error parsing payload for spool ID extraction"); | ||||
|               } | ||||
|                | ||||
|               payloadDoc.clear(); | ||||
|             } else { | ||||
|               Serial.printf("Weight %dg is not above 20g threshold - skipping weight update\n", weight); | ||||
|             } | ||||
|           }else{ | ||||
|             // Potentially handle errors | ||||
|           } | ||||
| @@ -1655,10 +1786,60 @@ void writeJsonToTag(void *parameter) { | ||||
|   vTaskDelete(NULL); | ||||
| } | ||||
|  | ||||
| // Ensures sm_id is always the first key in JSON for fast-path detection | ||||
| String optimizeJsonForFastPath(const char* payload) { | ||||
|     JsonDocument inputDoc; | ||||
|     DeserializationError error = deserializeJson(inputDoc, payload); | ||||
|      | ||||
|     if (error) { | ||||
|         Serial.print("JSON optimization failed: "); | ||||
|         Serial.println(error.c_str()); | ||||
|         return String(payload); // Return original if parsing fails | ||||
|     } | ||||
|      | ||||
|     // Create optimized JSON with sm_id first | ||||
|     JsonDocument optimizedDoc; | ||||
|      | ||||
|     // Always add sm_id first (even if it's "0" for brand filaments) | ||||
|     if (inputDoc["sm_id"].is<String>()) { | ||||
|         optimizedDoc["sm_id"] = inputDoc["sm_id"].as<String>(); | ||||
|         Serial.print("Optimizing JSON: sm_id found = "); | ||||
|         Serial.println(inputDoc["sm_id"].as<String>()); | ||||
|     } else { | ||||
|         optimizedDoc["sm_id"] = "0"; // Default for brand filaments | ||||
|         Serial.println("Optimizing JSON: No sm_id found, setting to '0'"); | ||||
|     } | ||||
|      | ||||
|     // Add all other keys in original order | ||||
|     for (JsonPair kv : inputDoc.as<JsonObject>()) { | ||||
|         String key = kv.key().c_str(); | ||||
|         if (key != "sm_id") { // Skip sm_id as it's already added first | ||||
|             optimizedDoc[key] = kv.value(); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     String optimizedJson; | ||||
|     serializeJson(optimizedDoc, optimizedJson); | ||||
|      | ||||
|     Serial.println("JSON optimized for fast-path detection:"); | ||||
|     Serial.print("Original:  "); | ||||
|     Serial.println(payload); | ||||
|     Serial.print("Optimized: "); | ||||
|     Serial.println(optimizedJson); | ||||
|      | ||||
|     inputDoc.clear(); | ||||
|     optimizedDoc.clear(); | ||||
|      | ||||
|     return optimizedJson; | ||||
| } | ||||
|  | ||||
| void startWriteJsonToTag(const bool isSpoolTag, const char* payload) { | ||||
|   // Optimize JSON to ensure sm_id is first key for fast-path detection | ||||
|   String optimizedPayload = optimizeJsonForFastPath(payload); | ||||
|    | ||||
|   NfcWriteParameterType* parameters = new NfcWriteParameterType(); | ||||
|   parameters->tagType = isSpoolTag; | ||||
|   parameters->payload = strdup(payload); | ||||
|   parameters->payload = strdup(optimizedPayload.c_str()); // Use optimized payload | ||||
|    | ||||
|   // Task nicht mehrfach starten | ||||
|   if (nfcReaderState == NFC_IDLE || nfcReaderState == NFC_READ_ERROR || nfcReaderState == NFC_READ_SUCCESS) { | ||||
| @@ -1678,9 +1859,44 @@ void startWriteJsonToTag(const bool isSpoolTag, const char* payload) { | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Safe tag detection with manual retry logic and short timeouts | ||||
| bool safeTagDetection(uint8_t* uid, uint8_t* uidLength) { | ||||
|     const int MAX_ATTEMPTS = 3; | ||||
|     const int SHORT_TIMEOUT = 100; // Very short timeout to prevent hanging | ||||
|      | ||||
|     for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { | ||||
|         // Watchdog reset on each attempt | ||||
|         esp_task_wdt_reset(); | ||||
|         yield(); | ||||
|          | ||||
|         // Use short timeout to avoid blocking | ||||
|         bool success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, uidLength, SHORT_TIMEOUT); | ||||
|          | ||||
|         if (success) { | ||||
|             Serial.printf("✓ Tag detected on attempt %d with %dms timeout\n", attempt + 1, SHORT_TIMEOUT); | ||||
|             return true; | ||||
|         } | ||||
|          | ||||
|         // Short pause between attempts | ||||
|         vTaskDelay(pdMS_TO_TICKS(25)); | ||||
|          | ||||
|         // Refresh RF field after failed attempt (but not on last attempt) | ||||
|         if (attempt < MAX_ATTEMPTS - 1) { | ||||
|             nfc.SAMConfig(); | ||||
|             vTaskDelay(pdMS_TO_TICKS(10)); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| void scanRfidTask(void * parameter) { | ||||
|   Serial.println("RFID Task gestartet"); | ||||
|   for(;;) { | ||||
|     // Regular watchdog reset | ||||
|     esp_task_wdt_reset(); | ||||
|     yield(); | ||||
|      | ||||
|     // Skip scanning during write operations, but keep NFC interface active | ||||
|     if (nfcReaderState != NFC_WRITING && !nfcWriteInProgress && !nfcReadingTaskSuspendRequest && !booting) | ||||
|     { | ||||
| @@ -1691,10 +1907,16 @@ void scanRfidTask(void * parameter) { | ||||
|       uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 };  // Buffer to store the returned UID | ||||
|       uint8_t uidLength; | ||||
|  | ||||
|       success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 500); | ||||
|       // Use safe tag detection instead of blocking readPassiveTargetID | ||||
|       success = safeTagDetection(uid, &uidLength); | ||||
|  | ||||
|       foundNfcTag(nullptr, success); | ||||
|        | ||||
|       // Reset activeSpoolId immediately when no tag is detected to prevent stale autoSet | ||||
|       if (!success) { | ||||
|         activeSpoolId = ""; | ||||
|       } | ||||
|        | ||||
|       // As long as there is still a tag on the reader, do not try to read it again | ||||
|       if (success && nfcReaderState == NFC_IDLE) | ||||
|       { | ||||
| @@ -1708,9 +1930,9 @@ void scanRfidTask(void * parameter) { | ||||
|  | ||||
|         oledShowProgressBar(0, octoEnabled?5:4, "Reading", "Detecting tag"); | ||||
|  | ||||
|         // Wait 1 second after tag detection to stabilize connection | ||||
|         Serial.println("Tag detected, waiting 1 second for stabilization..."); | ||||
|         vTaskDelay(1000 / portTICK_PERIOD_MS); | ||||
|         // Reduced stabilization time for better responsiveness | ||||
|         Serial.println("Tag detected, minimal stabilization..."); | ||||
|         vTaskDelay(200 / portTICK_PERIOD_MS); // Reduced from 1000ms to 200ms | ||||
|  | ||||
|         // create Tag UID string | ||||
|         String uidString = ""; | ||||
| @@ -1753,9 +1975,10 @@ void scanRfidTask(void * parameter) { | ||||
|              | ||||
|             for (uint8_t i = 4; i < 4+numPages; i++) { | ||||
|                | ||||
|               if (!nfc.ntag2xx_ReadPage(i, data+(i-4) * 4)) | ||||
|               if (!robustPageRead(i, data+(i-4) * 4)) | ||||
|               { | ||||
|                 break; // Stop if reading fails | ||||
|                 Serial.printf("Failed to read page %d after retries, stopping\n", i); | ||||
|                 break; // Stop if reading fails after retries | ||||
|               } | ||||
|               | ||||
|               // Check for NDEF message end | ||||
| @@ -1767,8 +1990,8 @@ void scanRfidTask(void * parameter) { | ||||
|  | ||||
|               yield(); | ||||
|               esp_task_wdt_reset(); | ||||
|               // Increased delay to ensure stable reading | ||||
|               vTaskDelay(pdMS_TO_TICKS(5)); // Increased from 1ms to 5ms | ||||
|               // Reduced delay for faster reading | ||||
|               vTaskDelay(pdMS_TO_TICKS(2)); // Reduced from 5ms to 2ms | ||||
|             } | ||||
|              | ||||
|             Serial.println("Tag reading completed, starting NDEF decode..."); | ||||
| @@ -1789,6 +2012,9 @@ void scanRfidTask(void * parameter) { | ||||
|           { | ||||
|             oledShowProgressBar(1, 1, "Failure", "Tag read error"); | ||||
|             nfcReaderState = NFC_READ_ERROR; | ||||
|             // Reset activeSpoolId when tag reading fails to prevent autoSet | ||||
|             activeSpoolId = ""; | ||||
|             Serial.println("Tag read failed - activeSpoolId reset to prevent autoSet"); | ||||
|           } | ||||
|         } | ||||
|         else | ||||
| @@ -1796,6 +2022,9 @@ void scanRfidTask(void * parameter) { | ||||
|           //TBD: Show error here?! | ||||
|           oledShowProgressBar(1, 1, "Failure", "Unkown tag type"); | ||||
|           Serial.println("This doesn't seem to be an NTAG2xx tag (UUID length != 7 bytes)!"); | ||||
|           // Reset activeSpoolId when tag type is unknown to prevent autoSet | ||||
|           activeSpoolId = ""; | ||||
|           Serial.println("Unknown tag type - activeSpoolId reset to prevent autoSet"); | ||||
|         } | ||||
|       } | ||||
|  | ||||
| @@ -1815,10 +2044,13 @@ void scanRfidTask(void * parameter) { | ||||
|         Serial.println("Tag nach erfolgreichem Lesen entfernt - bereit für nächsten Tag"); | ||||
|       } | ||||
|  | ||||
|       // Add a longer pause after successful reading to prevent immediate re-reading | ||||
|       // Add a pause after successful reading to prevent immediate re-reading | ||||
|       if (nfcReaderState == NFC_READ_SUCCESS) { | ||||
|         Serial.println("Tag erfolgreich gelesen - warte 5 Sekunden vor nächstem Scan"); | ||||
|         vTaskDelay(5000 / portTICK_PERIOD_MS); // 5 second pause | ||||
|         Serial.println("Tag erfolgreich gelesen - warte 3 Sekunden vor nächstem Scan"); | ||||
|         vTaskDelay(3000 / portTICK_PERIOD_MS); // Reduced from 5 seconds to 3 seconds | ||||
|       } else { | ||||
|         // Faster scanning when no tag or idle state | ||||
|         vTaskDelay(150 / portTICK_PERIOD_MS); // Faster scan interval | ||||
|       } | ||||
|  | ||||
|       // aktualisieren der Website wenn sich der Status ändert | ||||
|   | ||||
| @@ -17,6 +17,7 @@ void startNfc(); | ||||
| void scanRfidTask(void * parameter); | ||||
| void startWriteJsonToTag(const bool isSpoolTag, const char* payload); | ||||
| bool quickSpoolIdCheck(String uidString); | ||||
| bool readCompleteJsonForFastPath(); // Read complete JSON data for fast-path web interface display | ||||
|  | ||||
| extern TaskHandle_t RfidReaderTask; | ||||
| extern String nfcJsonData; | ||||
|   | ||||
							
								
								
									
										187
									
								
								src/scale.cpp
									
									
									
									
									
								
							
							
						
						
									
										187
									
								
								src/scale.cpp
									
									
									
									
									
								
							| @@ -13,6 +13,21 @@ TaskHandle_t ScaleTask; | ||||
|  | ||||
| int16_t weight = 0; | ||||
|  | ||||
| // Weight stabilization variables | ||||
| #define MOVING_AVERAGE_SIZE 8           // Reduced from 20 to 8 for faster response | ||||
| #define LOW_PASS_ALPHA 0.3f            // Increased from 0.15 to 0.3 for faster tracking | ||||
| #define DISPLAY_THRESHOLD 0.3f         // Reduced from 0.5 to 0.3g for more responsive display | ||||
| #define API_THRESHOLD 1.5f             // Reduced from 2.0 to 1.5g for faster API actions | ||||
| #define MEASUREMENT_INTERVAL_MS 30     // Reduced from 50ms to 30ms for faster updates | ||||
|  | ||||
| float weightBuffer[MOVING_AVERAGE_SIZE]; | ||||
| uint8_t bufferIndex = 0; | ||||
| bool bufferFilled = false; | ||||
| float filteredWeight = 0.0f; | ||||
| int16_t lastDisplayedWeight = 0; | ||||
| int16_t lastStableWeight = 0;        // For API/action triggering | ||||
| unsigned long lastMeasurementTime = 0; | ||||
|  | ||||
| uint8_t weigthCouterToApi = 0; | ||||
| uint8_t scale_tare_counter = 0; | ||||
| bool scaleTareRequest = false; | ||||
| @@ -21,6 +36,93 @@ bool scaleCalibrated; | ||||
| bool autoTare = true; | ||||
| bool scaleCalibrationActive = false; | ||||
|  | ||||
| // ##### Weight stabilization functions ##### | ||||
|  | ||||
| /** | ||||
|  * Reset weight filter buffer - call after tare or calibration | ||||
|  */ | ||||
| void resetWeightFilter() { | ||||
|   bufferIndex = 0; | ||||
|   bufferFilled = false; | ||||
|   filteredWeight = 0.0f; | ||||
|   lastDisplayedWeight = 0; | ||||
|   lastStableWeight = 0;            // Reset stable weight for API actions | ||||
|    | ||||
|   // Initialize buffer with zeros | ||||
|   for (int i = 0; i < MOVING_AVERAGE_SIZE; i++) { | ||||
|     weightBuffer[i] = 0.0f; | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Calculate moving average from weight buffer | ||||
|  */ | ||||
| float calculateMovingAverage() { | ||||
|   float sum = 0.0f; | ||||
|   int count = bufferFilled ? MOVING_AVERAGE_SIZE : bufferIndex; | ||||
|    | ||||
|   for (int i = 0; i < count; i++) { | ||||
|     sum += weightBuffer[i]; | ||||
|   } | ||||
|    | ||||
|   return (count > 0) ? sum / count : 0.0f; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Apply low-pass filter to smooth weight readings | ||||
|  * Uses exponential smoothing: y_new = alpha * x_new + (1-alpha) * y_old | ||||
|  */ | ||||
| float applyLowPassFilter(float newValue) { | ||||
|   filteredWeight = LOW_PASS_ALPHA * newValue + (1.0f - LOW_PASS_ALPHA) * filteredWeight; | ||||
|   return filteredWeight; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Process new weight reading with stabilization | ||||
|  * Returns stabilized weight value | ||||
|  */ | ||||
| int16_t processWeightReading(float rawWeight) { | ||||
|   // Add to moving average buffer | ||||
|   weightBuffer[bufferIndex] = rawWeight; | ||||
|   bufferIndex = (bufferIndex + 1) % MOVING_AVERAGE_SIZE; | ||||
|    | ||||
|   if (bufferIndex == 0) { | ||||
|     bufferFilled = true; | ||||
|   } | ||||
|    | ||||
|   // Calculate moving average | ||||
|   float avgWeight = calculateMovingAverage(); | ||||
|    | ||||
|   // Apply low-pass filter | ||||
|   float smoothedWeight = applyLowPassFilter(avgWeight); | ||||
|    | ||||
|   // Round to nearest gram | ||||
|   int16_t newWeight = round(smoothedWeight); | ||||
|    | ||||
|   // Update displayed weight if display threshold is reached | ||||
|   if (abs(newWeight - lastDisplayedWeight) >= DISPLAY_THRESHOLD) { | ||||
|     lastDisplayedWeight = newWeight; | ||||
|   } | ||||
|    | ||||
|   // Update global weight for API actions only if stable threshold is reached | ||||
|   int16_t weightToReturn = weight; // Default: keep current weight | ||||
|    | ||||
|   if (abs(newWeight - lastStableWeight) >= API_THRESHOLD) { | ||||
|     lastStableWeight = newWeight; | ||||
|     weightToReturn = newWeight; | ||||
|   } | ||||
|    | ||||
|   return weightToReturn; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Get current filtered weight for display purposes | ||||
|  * This returns the smoothed weight even if it hasn't triggered API actions | ||||
|  */ | ||||
| int16_t getFilteredDisplayWeight() { | ||||
|   return lastDisplayedWeight; | ||||
| } | ||||
|  | ||||
| // ##### Funktionen für Waage ##### | ||||
| uint8_t setAutoTare(bool autoTareValue) { | ||||
|   Serial.print("Set AutoTare to "); | ||||
| @@ -39,6 +141,7 @@ uint8_t setAutoTare(bool autoTareValue) { | ||||
| uint8_t tareScale() { | ||||
|   Serial.println("Tare scale"); | ||||
|   scale.tare(); | ||||
|   resetWeightFilter(); // Reset stabilization filter after tare | ||||
|    | ||||
|   return 1; | ||||
| } | ||||
| @@ -48,37 +151,61 @@ void scale_loop(void * parameter) { | ||||
|   Serial.println("Scale Loop started"); | ||||
|   Serial.println("++++++++++++++++++++++++++++++"); | ||||
|  | ||||
|   // Initialize weight filter | ||||
|   resetWeightFilter(); | ||||
|   lastMeasurementTime = millis(); | ||||
|  | ||||
|   for(;;) { | ||||
|     if (scale.is_ready())  | ||||
|     { | ||||
|       // Waage automatisch Taren, wenn zu lange Abweichung | ||||
|       if (autoTare && scale_tare_counter >= 5)  | ||||
|     unsigned long currentTime = millis(); | ||||
|      | ||||
|     // Only measure at defined intervals to reduce noise | ||||
|     if (currentTime - lastMeasurementTime >= MEASUREMENT_INTERVAL_MS) { | ||||
|       if (scale.is_ready())  | ||||
|       { | ||||
|         Serial.println("Auto Tare scale"); | ||||
|         scale.tare(); | ||||
|         scale_tare_counter = 0; | ||||
|       } | ||||
|         // Waage automatisch Taren, wenn zu lange Abweichung | ||||
|         if (autoTare && scale_tare_counter >= 5)  | ||||
|         { | ||||
|           Serial.println("Auto Tare scale"); | ||||
|           scale.tare(); | ||||
|           resetWeightFilter(); // Reset filter after auto tare | ||||
|           scale_tare_counter = 0; | ||||
|         } | ||||
|  | ||||
|       // Waage manuell Taren | ||||
|       if (scaleTareRequest == true)  | ||||
|       { | ||||
|         Serial.println("Re-Tare scale"); | ||||
|         oledShowMessage("TARE Scale"); | ||||
|         vTaskDelay(pdMS_TO_TICKS(1000)); | ||||
|         scale.tare(); | ||||
|         vTaskDelay(pdMS_TO_TICKS(1000)); | ||||
|         oledShowWeight(0); | ||||
|         scaleTareRequest = false; | ||||
|       } | ||||
|         // Waage manuell Taren | ||||
|         if (scaleTareRequest == true)  | ||||
|         { | ||||
|           Serial.println("Re-Tare scale"); | ||||
|           oledShowMessage("TARE Scale"); | ||||
|           vTaskDelay(pdMS_TO_TICKS(1000)); | ||||
|           scale.tare(); | ||||
|           resetWeightFilter(); // Reset filter after manual tare | ||||
|           vTaskDelay(pdMS_TO_TICKS(1000)); | ||||
|           oledShowWeight(0); | ||||
|           scaleTareRequest = false; | ||||
|         } | ||||
|  | ||||
|       // Only update weight if median changed more than 1 | ||||
|       int16_t newWeight = round(scale.get_units()); | ||||
|       if(abs(weight-newWeight) > 1){ | ||||
|         weight = newWeight; | ||||
|         // Get raw weight reading | ||||
|         float rawWeight = scale.get_units(); | ||||
|          | ||||
|         // Process weight with stabilization | ||||
|         int16_t stabilizedWeight = processWeightReading(rawWeight); | ||||
|          | ||||
|         // Update global weight variable only if it changed significantly (for API actions) | ||||
|         if (stabilizedWeight != weight) { | ||||
|           weight = stabilizedWeight; | ||||
|         } | ||||
|          | ||||
|         // Debug output for monitoring (can be removed in production) | ||||
|         static unsigned long lastDebugTime = 0; | ||||
|         if (currentTime - lastDebugTime > 2000) { // Print every 2 seconds | ||||
|           lastDebugTime = currentTime; | ||||
|         } | ||||
|          | ||||
|         lastMeasurementTime = currentTime; | ||||
|       } | ||||
|     } | ||||
|      | ||||
|     vTaskDelay(pdMS_TO_TICKS(100)); | ||||
|     vTaskDelay(pdMS_TO_TICKS(10)); // Shorter delay for more responsive loop | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -122,12 +249,19 @@ void start_scale(bool touchSensorConnected) { | ||||
|   } | ||||
|  | ||||
|   scale.set_scale(calibrationValue); | ||||
|   vTaskDelay(pdMS_TO_TICKS(5000)); | ||||
|   scale.tare(); | ||||
|   //vTaskDelay(pdMS_TO_TICKS(5000)); | ||||
|  | ||||
|   // Initialize weight stabilization filter | ||||
|   resetWeightFilter(); | ||||
|  | ||||
|   // Display Gewicht | ||||
|   oledShowWeight(0); | ||||
|  | ||||
|   vTaskDelay(500 / portTICK_PERIOD_MS); | ||||
|   scale.tare(); | ||||
|   vTaskDelay(500 / portTICK_PERIOD_MS); | ||||
|   weight = 0; | ||||
|  | ||||
|   Serial.println("starte Scale Task"); | ||||
|   BaseType_t result = xTaskCreatePinnedToCore( | ||||
|     scale_loop, /* Function to implement the task */ | ||||
| @@ -209,6 +343,7 @@ uint8_t calibrate_scale() { | ||||
|       oledShowProgressBar(2, 3, "Scale Cal.", "Remove weight"); | ||||
|  | ||||
|       scale.set_scale(newCalibrationValue); | ||||
|       resetWeightFilter(); // Reset filter after calibration | ||||
|       for (uint16_t i = 0; i < 2000; i++) { | ||||
|         yield(); | ||||
|         vTaskDelay(pdMS_TO_TICKS(1)); | ||||
|   | ||||
| @@ -9,6 +9,13 @@ uint8_t start_scale(bool touchSensorConnected); | ||||
| uint8_t calibrate_scale(); | ||||
| uint8_t tareScale(); | ||||
|  | ||||
| // Weight stabilization functions | ||||
| void resetWeightFilter(); | ||||
| float calculateMovingAverage(); | ||||
| float applyLowPassFilter(float newValue); | ||||
| int16_t processWeightReading(float rawWeight); | ||||
| int16_t getFilteredDisplayWeight(); | ||||
|  | ||||
| extern HX711 scale; | ||||
| extern int16_t weight; | ||||
| extern uint8_t weigthCouterToApi; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user