Compare commits

...

57 Commits

Author SHA1 Message Date
8a558c3121 refactor: remove unnecessary delay in MQTT setup and add delay before restart 2025-03-03 16:58:24 +01:00
5afb60df32 fix: correct typo in console log for total length 2025-03-02 20:21:27 +01:00
3394e6eb01 feat: add new 3D print file for Filaman scale 2025-03-02 08:06:59 +01:00
3818c2c059 refactor: remove redundant scale calibration checks and enhance task management 2025-03-01 18:50:20 +01:00
0afc543b5f refactor: enhance AMS data handling and streamline spool auto-setting logic 2025-03-01 18:44:35 +01:00
adee46e3fc refactor: adjust stack size and improve scale calibration logic 2025-03-01 18:44:29 +01:00
1db74867e6 refactor: update labels and input types for better clarity and functionality 2025-03-01 18:44:17 +01:00
0f24a63d32 added Discord Server 2025-03-01 15:33:39 +01:00
3640809502 update documentation for clarity and accuracy 2025-03-01 13:04:28 +01:00
289d5357be docs: update changelog and header for version v1.4.0
All checks were successful
Release Workflow / detect-provider (push) Successful in 5s
Release Workflow / github-release (push) Has been skipped
Release Workflow / gitea-release (push) Successful in 2m45s
2025-03-01 12:46:18 +01:00
315530d1ea update NFC tag references to include NTAG213 and clarify storage capacity 2025-03-01 12:45:55 +01:00
f36773a4c4 bump version to 1.4.0 2025-03-01 12:37:50 +01:00
b35163936f add support for Spoolman Octoprint Plugin in README files 2025-03-01 12:33:26 +01:00
7a2c9d6d17 add OctoPrint integration with configurable fields and update functionality 2025-03-01 12:18:33 +01:00
eb2a8dc128 add version comparison function and check for outdated versions before updates 2025-03-01 12:18:21 +01:00
bec2c91331 remove unused version and protocol fields from JSON output; add error message for insufficient memory 2025-03-01 10:42:06 +01:00
c6e727de06 remove unused version and protocol fields from NFC data packet 2025-03-01 10:41:51 +01:00
3253e7d407 sort vendors alphabetically in the dropdown list 2025-03-01 10:41:44 +01:00
bce2ad2ed8
Merge pull request #10 from janecker/nfc-improvements
Improves NFC Tag handling
2025-03-01 10:03:46 +01:00
Jan Philipp Ecker
0eff29ef4a Improves NFC Tag handling
Fixes memory underflow when reading tags. Reads tags with their actual data size and uses actual size instead of constnat value for tag size when writing a tag.
2025-02-28 22:35:34 +01:00
492bf6cdb8 docs: update changelog and header for version v1.3.99
All checks were successful
Release Workflow / detect-provider (push) Successful in 3s
Release Workflow / github-release (push) Has been skipped
Release Workflow / gitea-release (push) Successful in 2m39s
2025-02-28 18:35:16 +01:00
b0317f4001 docs: update platformio.ini for version v1.3.99 2025-02-28 18:35:16 +01:00
58ff6458b0 refactor: update workflows to build firmware with LittleFS instead of SPIFFS 2025-02-28 18:35:05 +01:00
d9c40f5124 docs: update changelog and header for version v1.3.98
Some checks failed
Release Workflow / detect-provider (push) Successful in 4s
Release Workflow / github-release (push) Has been skipped
Release Workflow / gitea-release (push) Failing after 2m51s
2025-02-28 18:12:56 +01:00
68bc31e29a docs: update platformio.ini for version v1.3.98 2025-02-28 18:12:56 +01:00
9b23ac5fd2 refactor: migrate from SPIFFS to LittleFS for file handling 2025-02-28 18:12:42 +01:00
d31bff14c3 chore: remove unused VSCode settings file 2025-02-28 09:29:34 +01:00
150f92484a refactor: remove commented-out spoolman and filaman data from api.cpp 2025-02-28 09:26:09 +01:00
fa74832fb9 docs: update changelog and header for version v1.3.97
All checks were successful
Release Workflow / detect-provider (push) Successful in 3s
Release Workflow / github-release (push) Has been skipped
Release Workflow / gitea-release (push) Successful in 2m47s
2025-02-28 08:53:08 +01:00
2eab3db77d docs: update platformio.ini for version v1.3.97 2025-02-28 08:53:07 +01:00
0a1bf22f7e feat: füge Bestätigungsmeldung für Spool-Einstellung hinzu 2025-02-27 22:07:47 +01:00
d58244c1f8 fix: Speichernutzung optimiert 2025-02-27 21:56:31 +01:00
db626ea516 fix: behebe doppelte http.end() Aufrufe in checkSpoolmanExtraFields 2025-02-27 21:54:47 +01:00
fd8f7685a1 fix: optimiere Verzögerungen und Stackgrößen in NFC-Task-Funktionen 2025-02-27 21:54:32 +01:00
944b156528 feat: verbessere WLAN-Konfiguration und füge mDNS-Unterstützung hinzu 2025-02-27 21:53:48 +01:00
76100593cc refactor: entferne ungenutzte Bibliotheken und Debug-Ausgaben aus main.cpp 2025-02-27 15:50:04 +01:00
732d590344 feat: aktualisiere OLED-Anzeige mit Versionsnummer und verbessere Textausrichtung 2025-02-27 14:35:53 +01:00
46cd953b80 feat: füge regelmäßige WLAN-Verbindungsüberprüfung hinzu 2025-02-27 09:38:54 +01:00
c645035bbe feat: aktualisiere Schaltplan-Bild 2025-02-26 18:29:37 +01:00
9e76620cd3 style: entferne text-shadow von deaktivierten Schaltflächen 2025-02-26 18:07:22 +01:00
faddda6201 feat: zeige Versionsnummer im OLED-Display an 2025-02-26 18:01:35 +01:00
de9c1706c0 docs: füge Link zum Wiki für detaillierte Informationen über die Nutzung hinzu 2025-02-25 20:19:04 +01:00
9f7ee13e78 docs: update changelog and header for version v1.3.96
All checks were successful
Release Workflow / detect-provider (push) Successful in 3s
Release Workflow / github-release (push) Has been skipped
Release Workflow / gitea-release (push) Successful in 3m1s
2025-02-25 16:29:37 +01:00
cf3f6f6741 docs: update platformio.ini for version v1.3.96 2025-02-25 16:29:37 +01:00
b87d43c64e feat: füge Unterstützung für Spoolman-Einstellungen hinzu und aktualisiere die Benutzeroberfläche 2025-02-25 16:24:22 +01:00
3d0411e3c1 feat: entferne die sendAmsData-Funktion aus der API-Schnittstelle 2025-02-25 14:52:47 +01:00
9c61b708aa fix: aktualisiere Bedingungen für die AMS-Datenaktualisierung und entferne unnötige Aufrufe 2025-02-25 14:52:27 +01:00
90f800d042 fix: aktualisiere Bedingung für den Fortschritt der OTA-Update-Nachricht 2025-02-25 12:19:24 +01:00
a7b1721e1d feat: erweitere Bambu-Credentials um AutoSend-Zeit und aktualisiere die Benutzeroberfläche 2025-02-25 12:17:20 +01:00
e4825d2905 feat: erweitere Bambu-Credentials mit AutoSend-Wartezeit und aktualisiere die Benutzeroberfläche 2025-02-25 11:32:57 +01:00
c1733848d3 feat: add espRestart function and replace delay with vTaskDelay for OTA update process 2025-02-25 11:02:54 +01:00
484c95523d feat: implement OTA update functionality with backup and restore for configurations 2025-02-25 10:57:49 +01:00
8499613215 fix: update auto set logic to check RFID tag before setting Bambu spool 2025-02-25 10:57:36 +01:00
08f37186b4 feat: add own_filaments.json and integrate custom filament loading in bambu.cpp 2025-02-25 09:02:11 +01:00
2948a35fa8 docs: update changelog and header for version v1.3.95
All checks were successful
Release Workflow / detect-provider (push) Successful in 2s
Release Workflow / github-release (push) Has been skipped
Release Workflow / gitea-release (push) Successful in 2m45s
2025-02-24 19:56:19 +01:00
730724fe58 docs: update webpages for version v1.3.95 2025-02-24 19:56:18 +01:00
714b7065e7 fix: bind autoSendToBambu variable to checkbox in spoolman.html 2025-02-24 19:56:01 +01:00
33 changed files with 16395 additions and 614 deletions

View File

@ -41,16 +41,16 @@ jobs:
run: |
VERSION=$(grep '^version = ' platformio.ini | cut -d'"' -f2)
# Build firmware and SPIFFS
echo "Building firmware and SPIFFS..."
# Build firmware and LittleFS
echo "Building firmware and LittleFS..."
pio run -e esp32dev
pio run -t buildfs
# Copy firmware binary
cp .pio/build/esp32dev/firmware.bin .pio/build/esp32dev/upgrade_filaman_firmware_v${VERSION}.bin
# Create SPIFFS binary - direct copy without header
cp .pio/build/esp32dev/spiffs.bin .pio/build/esp32dev/upgrade_filaman_website_v${VERSION}.bin
# Create LittleFS binary - direct copy without header
cp .pio/build/esp32dev/littlefs.bin .pio/build/esp32dev/upgrade_filaman_website_v${VERSION}.bin
# Create full binary
(cd .pio/build/esp32dev &&
@ -63,7 +63,7 @@ jobs:
0x1000 bootloader.bin \
0x8000 partitions.bin \
0x10000 firmware.bin \
0x3D0000 spiffs.bin)
0x3D0000 littlefs.bin)
# Verify file sizes
echo "File sizes:"

View File

@ -39,16 +39,16 @@ jobs:
run: |
VERSION=$(grep '^version = ' platformio.ini | cut -d'"' -f2)
# Always build firmware and SPIFFS
echo "Building firmware and SPIFFS..."
# Always build firmware and LittleFS
echo "Building firmware and LittleFS..."
pio run -e esp32dev
pio run -t buildfs
# Copy firmware binary
cp .pio/build/esp32dev/firmware.bin .pio/build/esp32dev/upgrade_filaman_firmware_v${VERSION}.bin
# Create SPIFFS binary - direct copy without header
cp .pio/build/esp32dev/spiffs.bin .pio/build/esp32dev/upgrade_filaman_website_v${VERSION}.bin
# Create LittleFS binary - direct copy without header
cp .pio/build/esp32dev/littlefs.bin .pio/build/esp32dev/upgrade_filaman_website_v${VERSION}.bin
# Create full binary (always)
(cd .pio/build/esp32dev &&
@ -61,7 +61,7 @@ jobs:
0x1000 bootloader.bin \
0x8000 partitions.bin \
0x10000 firmware.bin \
0x3D0000 spiffs.bin)
0x3D0000 littlefs.bin)
# Verify file sizes
echo "File sizes:"
@ -131,7 +131,7 @@ jobs:
FILES_TO_UPLOAD="$FILES_TO_UPLOAD upgrade_filaman_firmware_v${VERSION}.bin"
fi
# Add SPIFFS and full binary only if they exist
# Add LittleFS and full binary only if they exist
if [ -f "upgrade_filaman_website_v${VERSION}.bin" ]; then
FILES_TO_UPLOAD="$FILES_TO_UPLOAD upgrade_filaman_website_v${VERSION}.bin"
fi

54
.vscode/settings.json vendored
View File

@ -1,54 +0,0 @@
{
"files.associations": {
"algorithm": "cpp",
"vector": "cpp",
"cmath": "cpp",
"array": "cpp",
"atomic": "cpp",
"*.tcc": "cpp",
"bitset": "cpp",
"cctype": "cpp",
"clocale": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"exception": "cpp",
"functional": "cpp",
"iterator": "cpp",
"map": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"regex": "cpp",
"string": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"fstream": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"istream": "cpp",
"limits": "cpp",
"new": "cpp",
"ostream": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"cinttypes": "cpp",
"typeinfo": "cpp"
}
}

View File

@ -1,5 +1,83 @@
# Changelog
## [1.4.0] - 2025-03-01
### Added
- add support for Spoolman Octoprint Plugin in README files
- add OctoPrint integration with configurable fields and update functionality
- add version comparison function and check for outdated versions before updates
- remove unused version and protocol fields from JSON output; add error message for insufficient memory
### Changed
- update NFC tag references to include NTAG213 and clarify storage capacity
- bump version to 1.4.0
- remove unused version and protocol fields from NFC data packet
- sort vendors alphabetically in the dropdown list
- Merge pull request #10 from janecker/nfc-improvements
- Improves NFC Tag handling
## [1.3.99] - 2025-02-28
### Changed
- update platformio.ini for version v1.3.99
- update workflows to build firmware with LittleFS instead of SPIFFS
## [1.3.98] - 2025-02-28
### Changed
- update platformio.ini for version v1.3.98
- migrate from SPIFFS to LittleFS for file handling
- remove unused VSCode settings file
- remove commented-out spoolman and filaman data from api.cpp
## [1.3.97] - 2025-02-28
### Added
- füge Bestätigungsmeldung für Spool-Einstellung hinzu
- verbessere WLAN-Konfiguration und füge mDNS-Unterstützung hinzu
- aktualisiere OLED-Anzeige mit Versionsnummer und verbessere Textausrichtung
- füge regelmäßige WLAN-Verbindungsüberprüfung hinzu
- aktualisiere Schaltplan-Bild
- zeige Versionsnummer im OLED-Display an
### Changed
- update platformio.ini for version v1.3.97
- entferne text-shadow von deaktivierten Schaltflächen
- füge Link zum Wiki für detaillierte Informationen über die Nutzung hinzu
### Fixed
- Speichernutzung optimiert
- behebe doppelte http.end() Aufrufe in checkSpoolmanExtraFields
- optimiere Verzögerungen und Stackgrößen in NFC-Task-Funktionen
- entferne ungenutzte Bibliotheken und Debug-Ausgaben aus main.cpp
## [1.3.96] - 2025-02-25
### Added
- füge Unterstützung für Spoolman-Einstellungen hinzu und aktualisiere die Benutzeroberfläche
- entferne die sendAmsData-Funktion aus der API-Schnittstelle
- erweitere Bambu-Credentials um AutoSend-Zeit und aktualisiere die Benutzeroberfläche
- erweitere Bambu-Credentials mit AutoSend-Wartezeit und aktualisiere die Benutzeroberfläche
- add espRestart function and replace delay with vTaskDelay for OTA update process
- implement OTA update functionality with backup and restore for configurations
- add own_filaments.json and integrate custom filament loading in bambu.cpp
### Changed
- update platformio.ini for version v1.3.96
### Fixed
- aktualisiere Bedingungen für die AMS-Datenaktualisierung und entferne unnötige Aufrufe
- aktualisiere Bedingung für den Fortschritt der OTA-Update-Nachricht
- update auto set logic to check RFID tag before setting Bambu spool
## [1.3.95] - 2025-02-24
### Changed
- update webpages for version v1.3.95
### Fixed
- bind autoSendToBambu variable to checkbox in spoolman.html
## [1.3.94] - 2025-02-24
### Changed
- update webpages for version v1.3.94

View File

@ -6,9 +6,12 @@ Das System integriert sich nahtlos mit der [Spoolman](https://github.com/Donkie/
![Scale](./img/scale_trans.png)
Weitere Bilder finden Sie im [img Ordner](/img/)
Weitere Bilder finden Sie im [img Ordner](/img/)
oder auf meiner Website: [FilaMan Website](https://www.filaman.app)
Deutsches Erklärvideo: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaOHU)
Deutsches Erklärvideo: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaOHU)
Discord Server: [https://discord.gg/vMAx2gf5](https://discord.gg/vMAx2gf5)
### Es gibt jetzt auch ein Wiki, dort sind nochmal alle Funktionen beschrieben: [Wiki](https://github.com/ManuelW77/Filaman/wiki)
### ESP32 Hardware-Funktionen
- **Gewichtsmessung:** Verwendung einer Wägezelle mit HX711-Verstärker für präzise Gewichtsverfolgung.
@ -16,7 +19,7 @@ Deutsches Erklärvideo: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaO
- **OLED-Display:** Zeigt aktuelles Gewicht, Verbindungsstatus (WiFi, Bambu Lab, Spoolman).
- **WLAN-Konnektivität:** WiFiManager für einfache Netzwerkkonfiguration.
- **MQTT-Integration:** Verbindet sich mit Bambu Lab Drucker für AMS-Steuerung.
- **NFC-Tag NTAG215:** Verwendung von NTAG215 wegen ausreichendem Speicherplatz auf dem Tag
- **NFC-Tag NTAG213 NTAG215:** Verwendung von NTAG213, besser NTAG215 wegen ausreichendem Speicherplatz auf dem Tag
### Weboberflächen-Funktionen
- **Echtzeit-Updates:** WebSocket-Verbindung für Live-Daten-Updates.
@ -33,6 +36,7 @@ Deutsches Erklärvideo: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaO
- Filtern und Auswählen von Filamenten.
- Automatische Aktualisierung der Spulengewichte.
- Verfolgung von NFC-Tag-Zuweisungen.
- Unterstützt das Spoolman Octoprint Plugin
### Wenn Sie meine Arbeit unterstützen möchten, freue ich mich über einen Kaffee
<a href="https://www.buymeacoffee.com/manuelw" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 30px !important;width: 108px !important;" ></a>
@ -59,7 +63,7 @@ Deutsches Erklärvideo: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaO
[Amazon Link](https://amzn.eu/d/0AuBp2c)
- **PN532 NFC NXP RFID-Modul V3:** Für NFC-Tag-Operationen.
[Amazon Link](https://amzn.eu/d/jfIuQXb)
- **NFC Tags Ntag215:** RFID Tag
- **NFC Tags NTAG213 NTA215:** RFID Tag
[Amazon Link](https://amzn.eu/d/9Z6mXc1)
### Pin-Konfiguration

View File

@ -6,12 +6,16 @@ FilaMan is a filament management system for 3D printing. It uses ESP32 hardware
Users can manage filament spools, monitor the status of the Automatic Material System (AMS) and make settings via a web interface.
The system integrates seamlessly with [Bambulab](https://bambulab.com/en-us) 3D printers and [Spoolman](https://github.com/Donkie/Spoolman) filament management as well as the [Openspool](https://github.com/spuder/OpenSpool) NFC-TAG format.
![Scale](./img/scale_trans.png)
More Images can be found in the [img Folder](/img/)
or my website:[FilaMan Website](https://www.filaman.app)
german explanatory video: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaOHU)
or my website: [FilaMan Website](https://www.filaman.app)
german explanatory video: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaOHU)
Discord Server: [https://discord.gg/vMAx2gf5](https://discord.gg/vMAx2gf5)
### Now more detailed informations about the usage: [Wiki](https://github.com/ManuelW77/Filaman/wiki)
### ESP32 Hardware Features
- **Weight Measurement:** Using a load cell with HX711 amplifier for precise weight tracking.
@ -19,7 +23,7 @@ german explanatory video: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62z
- **OLED Display:** Shows current weight, connection status (WiFi, Bambu Lab, Spoolman).
- **WiFi Connectivity:** WiFiManager for easy network configuration.
- **MQTT Integration:** Connects to Bambu Lab printer for AMS control.
- **NFC-Tag NTAG215:** Use NTAG215 because of enaught space on the Tag
- **NFC-Tag NTAG213 NTAG215:** Use NTAG213, better NTAG215 because of enaught space on the Tag
### Web Interface Features
- **Real-time Updates:** WebSocket connection for live data updates.
@ -36,6 +40,7 @@ german explanatory video: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62z
- Filter and select filaments.
- Update spool weights automatically.
- Track NFC tag assignments.
- Supports Spoolman Octoprint Plugin
### If you want to support my work, i would be happy to get a coffe
<a href="https://www.buymeacoffee.com/manuelw" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 30px !important;width: 108px !important;" ></a>
@ -62,7 +67,7 @@ german explanatory video: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62z
[Amazon Link](https://amzn.eu/d/0AuBp2c)
- **PN532 NFC NXP RFID-Modul V3:** For NFC tag operations.
[Amazon Link](https://amzn.eu/d/jfIuQXb)
- **NFC Tags Ntag215:** RFID Tag
- **NFC Tags NTAG213 NTAG215:** RFID Tag
[Amazon Link](https://amzn.eu/d/9Z6mXc1)

File diff suppressed because it is too large Load Diff

Binary file not shown.

31
html/own_filaments.json Normal file
View File

@ -0,0 +1,31 @@
{
"TPU": "GFU99",
"PA": "GFN99",
"PA-CF": "GFN98",
"PLA": "GFL99",
"PLA Silk": "GFL96",
"PLA-CF": "GFL98",
"PLA High Speed": "GFL95",
"PETG": "GFG99",
"PETG-CF": "GFG98",
"PCTG": "GFG97",
"ABS": "GFB99",
"ABS+HS": "GFB99",
"PC": "GFC99",
"PC/ABS": "GFC99",
"ASA": "GFB98",
"PVA": "GFS99",
"HIPS": "GFS98",
"PPS-CF": "GFT98",
"PPS": "GFT97",
"PPA-CF": "GFN97",
"PPA-GF": "GFN96",
"PE": "GFP99",
"PE-CF": "GFP98",
"PP": "GFP97",
"PP-CF": "GFP96",
"PP-GF": "GFP95",
"EVA": "GFR99",
"PHA": "GFR98",
"BVOH": "GFS97"
}

View File

@ -150,6 +150,13 @@ function initWebSocket() {
ramStatus.textContent = `${data.freeHeap}k`;
}
}
else if (data.type === 'setSpoolmanSettings') {
if (data.payload == 'success') {
showNotification(`Spoolman Settings set successfully`, true);
} else {
showNotification(`Error setting Spoolman Settings`, false);
}
}
};
} catch (error) {
isConnected = false;
@ -285,6 +292,14 @@ function displayAmsData(amsData) {
<img src="spool_in.png" alt="Spool In" style="width: 48px; height: 48px; transform: rotate(180deg) scaleX(-1);">
</button>`;
const spoolmanButtonHtml = `
<button class="spool-button" onclick="handleSpoolmanSettings('${tray.tray_info_idx}', '${tray.setting_id}', '${tray.cali_idx}', '${tray.nozzle_temp_min}', '${tray.nozzle_temp_max}')"
style="position: absolute; bottom: 0px; right: 0px;
background: none; border: none; padding: 0;
cursor: pointer; display: none;">
<img src="set_spoolman.png" alt="Spool In" style="width: 38px; height: 38px;">
</button>`;
if (!hasAnyContent) {
return `
<div class="tray">
@ -348,6 +363,7 @@ function displayAmsData(amsData) {
${trayDetails}
${tempHTML}
${(ams.ams_id === 255 && tray.tray_type !== '') ? outButtonHtml : ''}
${(tray.setting_id != "" && tray.setting_id != "null") ? spoolmanButtonHtml : ''}
</div>
</div>`;
@ -373,6 +389,36 @@ function updateSpoolButtons(show) {
});
}
function handleSpoolmanSettings(tray_info_idx, setting_id, cali_idx, nozzle_temp_min, nozzle_temp_max) {
// Hole das ausgewählte Filament
const selectedText = document.getElementById("selected-filament").textContent;
// Finde die ausgewählte Spule in den Daten
const selectedSpool = spoolsData.find(spool =>
`${spool.id} | ${spool.filament.name} (${spool.filament.material})` === selectedText
);
const payload = {
type: 'setSpoolmanSettings',
payload: {
filament_id: selectedSpool.filament.id,
tray_info_idx: tray_info_idx,
setting_id: setting_id,
cali_idx: cali_idx,
temp_min: nozzle_temp_min,
temp_max: nozzle_temp_max
}
};
try {
socket.send(JSON.stringify(payload));
showNotification(`Setting send to Spoolman`, true);
} catch (error) {
console.error("Error while sending settings to Spoolman:", error);
showNotification("Error while sending!", false);
}
}
function handleSpoolOut() {
// Erstelle Payload
const payload = {
@ -594,8 +640,6 @@ function writeNfcTag() {
// Erstelle das NFC-Datenpaket mit korrekten Datentypen
const nfcData = {
version: "2.0",
protocol: "openspool",
color_hex: selectedSpool.filament.color_hex || "FFFFFF",
type: selectedSpool.filament.material,
min_temp: minTemp,

BIN
html/set_spoolman.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -52,11 +52,18 @@
if (spoolmanUrl && spoolmanUrl.trim() !== "") {
document.getElementById('spoolmanUrl').value = spoolmanUrl;
}
// Initialize OctoPrint fields visibility
toggleOctoFields();
};
function checkSpoolmanInstance() {
const url = document.getElementById('spoolmanUrl').value;
fetch(`/api/checkSpoolman?url=${encodeURIComponent(url)}`)
const spoolmanOctoEnabled = document.getElementById('spoolmanOctoEnabled').checked;
const spoolmanOctoUrl = document.getElementById('spoolmanOctoUrl').value;
const spoolmanOctoToken = document.getElementById('spoolmanOctoToken').value;
fetch(`/api/checkSpoolman?url=${encodeURIComponent(url)}&octoEnabled=${spoolmanOctoEnabled}&octoUrl=${spoolmanOctoUrl}&octoToken=${spoolmanOctoToken}`)
.then(response => response.json())
.then(data => {
if (data.healthy) {
@ -75,8 +82,9 @@
const serial = document.getElementById('bambuSerial').value;
const code = document.getElementById('bambuCode').value;
const autoSend = document.getElementById('autoSend').checked;
const autoSendTime = document.getElementById('autoSendTime').value;
fetch(`/api/bambu?bambu_ip=${encodeURIComponent(ip)}&bambu_serialnr=${encodeURIComponent(serial)}&bambu_accesscode=${encodeURIComponent(code)}&autoSend=${autoSend}`)
fetch(`/api/bambu?bambu_ip=${encodeURIComponent(ip)}&bambu_serialnr=${encodeURIComponent(serial)}&bambu_accesscode=${encodeURIComponent(code)}&autoSend=${autoSend}&autoSendTime=${autoSendTime}`)
.then(response => response.json())
.then(data => {
if (data.healthy) {
@ -89,6 +97,15 @@
document.getElementById('bambuStatusMessage').innerText = 'Error while saving: ' + error.message;
});
}
/**
* Controls visibility of OctoPrint configuration fields based on checkbox state
* Called on page load and when checkbox changes
*/
function toggleOctoFields() {
const octoEnabled = document.getElementById('spoolmanOctoEnabled').checked;
document.getElementById('octoFields').style.display = octoEnabled ? 'block' : 'none';
}
</script>
<script>
var spoolmanUrl = "{{spoolmanUrl}}";
@ -101,6 +118,17 @@
<div class="card-body">
<h5 class="card-title">Set URL/IP to your Spoolman-Instanz</h5>
<input type="text" id="spoolmanUrl" placeholder="http://ip-or-url-of-your-spoolman-instanz:port">
<h5 class="card-title">If you want to enable sending Spool to Spoolman Octoprint Plugin:</h5>
<p>
<input type="checkbox" id="spoolmanOctoEnabled" {{spoolmanOctoEnabled}} onchange="toggleOctoFields()"> Send to Octo-Plugin
</p>
<div id="octoFields" style="display: none;">
<p>
<input type="text" id="spoolmanOctoUrl" placeholder="http://ip-or-url-of-your-octoprint-instanz:port" value="{{spoolmanOctoUrl}}">
<input type="text" id="spoolmanOctoToken" placeholder="Your Octoprint Token" value="{{spoolmanOctoToken}}">
</p>
</div>
<button onclick="checkSpoolmanInstance()">Save Spoolman URL</button>
<p id="statusMessage"></p>
</div>
@ -122,13 +150,18 @@
<label for="bambuCode">Access Code:</label>
<input type="text" id="bambuCode" placeholder="Access Code vom Drucker" value="{{bambuCode}}">
</div>
<div class="input-group">
If activated, FilaMan will automatically update the next filled tray with the last scanned and weighed spool.
<label for="autoSend">Auto Send to Bambu:</label>
<input type="checkbox" id="autoSend">
<hr>
<p>If activated, FilaMan will automatically update the next filled tray with the last scanned and weighed spool.</p>
<div class="input-group" style="display: flex; margin-bottom: 0;">
<label for="autoSend" style="width: 250px; margin-right: 5px;">Auto Send to Bambu:</label>
<label for="autoSendTime" style="width: 250px; margin-right: 5px;">Wait for Spool in Sec:</label>
</div>
<div class="input-group" style="display: flex;">
<input type="checkbox" id="autoSend" {{autoSendToBambu}} style="width: 190px; margin-right: 10px;">
<input type="number" min="60" id="autoSendTime" placeholder="Time to wait" value="{{autoSendTime}}" style="width: 100px;">
</div>
<button onclick="saveBambuCredentials()">Save Bambu Credentials</button>
<button style="margin: 0;" onclick="saveBambuCredentials()">Save Bambu Credentials</button>
<p id="bambuStatusMessage"></p>
</div>
</div>

View File

@ -86,7 +86,7 @@ function populateVendorDropdown(data, selectedSmId = null) {
});
// Nach der Schleife: Formatierung der Gesamtlänge
console.log("Total Lenght: ", totalLength);
console.log("Total Length: ", totalLength);
const formattedLength = totalLength > 1000
? (totalLength / 1000).toFixed(2) + " km"
: totalLength.toFixed(2) + " m";
@ -97,13 +97,15 @@ function populateVendorDropdown(data, selectedSmId = null) {
? (weightInKg / 1000).toFixed(2) + " t"
: weightInKg.toFixed(2) + " kg";
// Dropdown mit gefilterten Herstellern befüllen
Object.entries(filteredVendors).forEach(([id, name]) => {
const option = document.createElement("option");
option.value = id;
option.textContent = name;
vendorSelect.appendChild(option);
});
// Dropdown mit gefilterten Herstellern befüllen - alphabetisch sortiert
Object.entries(filteredVendors)
.sort(([, nameA], [, nameB]) => nameA.localeCompare(nameB)) // Sort vendors alphabetically by name
.forEach(([id, name]) => {
const option = document.createElement("option");
option.value = id;
option.textContent = name;
vendorSelect.appendChild(option);
});
document.getElementById("totalSpools").textContent = totalSpools;
document.getElementById("spoolsWithoutTag").textContent = spoolsWithoutTag;

View File

@ -188,14 +188,18 @@ label {
font-weight: bold;
}
input[type="text"], input[type="submit"] {
input[type="text"], input[type="submit"], input[type="number"] {
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
font-size: 16px;
}
input[type="text"]:focus {
input[type="number"] {
width: 108px !important;
}
input[type="text"]:focus, input[type="number"]:focus {
border-color: #007bff;
outline: none;
}
@ -761,17 +765,19 @@ a:hover {
right: 20px;
padding: 15px 25px;
border-radius: 4px;
color: white;
color: black;
z-index: 1000;
animation: slideIn 0.3s ease-out;
}
.notification.success {
background-color: #28a745;
color: black !important;
}
.notification.error {
background-color: #dc3545;
color: white !important;
}
.notification.fade-out {
@ -1013,6 +1019,7 @@ input[type="submit"]:disabled,
color: #000;
vertical-align: middle;
margin-left: 0.5rem;
text-shadow: none !important;
}
.progress-container {

BIN
img/7-enable.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 KiB

After

Width:  |  Height:  |  Size: 283 KiB

View File

@ -9,7 +9,9 @@
; https://docs.platformio.org/page/projectconf.html
[common]
version = "1.3.94"
version = "1.4.0"
to_old_version = "1.4.0"
##
[env:esp32dev]
platform = espressif32
@ -33,7 +35,8 @@ lib_deps =
digitaldragon/SSLClient @ ^1.3.2
; Enable SPIFFS upload
board_build.filesystem = spiffs
#board_build.filesystem = spiffs
board_build.filesystem = littlefs
; Update partition settings
board_build.partitions = partitions.csv
board_upload.flash_size = 4MB
@ -44,13 +47,14 @@ build_flags =
-Os
-ffunction-sections
-fdata-sections
-DNDEBUG
#-DNDEBUG
-mtext-section-literals
-DVERSION=\"${common.version}\"
-DTOOLDVERSION=\"${common.to_old_version}\"
-DASYNCWEBSERVER_REGEX
-DCORE_DEBUG_LEVEL=3
#-DCORE_DEBUG_LEVEL=3
-DCONFIG_ARDUHAL_LOG_COLORS=1
-DOTA_DEBUG=1
#-DOTA_DEBUG=1
-DCONFIG_OPTIMIZATION_LEVEL_DEBUG=1
-DBOOT_APP_PARTITION_OTA_0=1
-DCONFIG_LWIP_TCP_MSL=60000

View File

@ -5,38 +5,17 @@
bool spoolman_connected = false;
String spoolmanUrl = "";
bool octoEnabled = false;
String octoUrl = "";
String octoToken = "";
struct SendToApiParams {
String httpType;
String spoolsUrl;
String updatePayload;
String octoToken;
};
/*
// Spoolman Data
{
"version":"1.0",
"protocol":"openspool",
"color_hex":"AF7933",
"type":"ABS",
"min_temp":175,
"max_temp":275,
"brand":"Overture"
}
// FilaMan Data
{
"version":"1.0",
"protocol":"openspool",
"color_hex":"AF7933",
"type":"ABS",
"min_temp":175,
"max_temp":275,
"brand":"Overture",
"sm_id":
}
*/
JsonDocument fetchSingleSpoolInfo(int spoolId) {
HTTPClient http;
String spoolsUrl = spoolmanUrl + apiUrl + "/spool/" + spoolId;
@ -112,19 +91,21 @@ void sendToApi(void *parameter) {
String httpType = params->httpType;
String spoolsUrl = params->spoolsUrl;
String updatePayload = params->updatePayload;
String octoToken = params->octoToken;
HTTPClient http;
http.begin(spoolsUrl);
http.addHeader("Content-Type", "application/json");
if (octoEnabled && octoToken != "") http.addHeader("X-Api-Key", octoToken);
int httpCode = http.PUT(updatePayload);
if (httpType == "PATCH") httpCode = http.PATCH(updatePayload);
if (httpType == "POST") httpCode = http.POST(updatePayload);
if (httpCode == HTTP_CODE_OK) {
Serial.println("Gewicht der Spule erfolgreich aktualisiert");
Serial.println("Spoolman erfolgreich aktualisiert");
} else {
Serial.println("Fehler beim Aktualisieren des Gewichts der Spule");
Serial.println("Fehler beim Senden an Spoolman!");
oledShowMessage("Spoolman update failed");
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
@ -223,6 +204,89 @@ uint8_t updateSpoolWeight(String spoolId, uint16_t weight) {
return 1;
}
bool updateSpoolOcto(int spoolId) {
String spoolsUrl = octoUrl + "/plugin/Spoolman/selectSpool";
Serial.print("Update Spule in Octoprint mit URL: ");
Serial.println(spoolsUrl);
JsonDocument updateDoc;
updateDoc["spool_id"] = spoolId;
updateDoc["tool"] = "tool0";
String updatePayload;
serializeJson(updateDoc, updatePayload);
Serial.print("Update Payload: ");
Serial.println(updatePayload);
SendToApiParams* params = new SendToApiParams();
if (params == nullptr) {
Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren.");
return false;
}
params->httpType = "POST";
params->spoolsUrl = spoolsUrl;
params->updatePayload = updatePayload;
params->octoToken = octoToken;
// Erstelle die Task
BaseType_t result = xTaskCreate(
sendToApi, // Task-Funktion
"SendToApiTask", // Task-Name
4096, // Stackgröße in Bytes
(void*)params, // Parameter
0, // Priorität
NULL // Task-Handle (nicht benötigt)
);
return true;
}
bool updateSpoolBambuData(String payload) {
JsonDocument doc;
DeserializationError error = deserializeJson(doc, payload);
if (error) {
Serial.print("Fehler beim JSON-Parsing: ");
Serial.println(error.c_str());
return false;
}
String spoolsUrl = spoolmanUrl + apiUrl + "/filament/" + doc["filament_id"].as<String>();
Serial.print("Update Spule mit URL: ");
Serial.println(spoolsUrl);
JsonDocument updateDoc;
updateDoc["extra"]["bambu_setting_id"] = "\"" + doc["setting_id"].as<String>() + "\"";
updateDoc["extra"]["bambu_cali_id"] = "\"" + doc["cali_idx"].as<String>() + "\"";
updateDoc["extra"]["bambu_idx"] = "\"" + doc["tray_info_idx"].as<String>() + "\"";
updateDoc["extra"]["nozzle_temperature"] = "[" + doc["temp_min"].as<String>() + "," + doc["temp_max"].as<String>() + "]";
String updatePayload;
serializeJson(updateDoc, updatePayload);
Serial.print("Update Payload: ");
Serial.println(updatePayload);
SendToApiParams* params = new SendToApiParams();
if (params == nullptr) {
Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren.");
return false;
}
params->httpType = "PATCH";
params->spoolsUrl = spoolsUrl;
params->updatePayload = updatePayload;
// Erstelle die Task
BaseType_t result = xTaskCreate(
sendToApi, // Task-Funktion
"SendToApiTask", // Task-Name
4096, // Stackgröße in Bytes
(void*)params, // Parameter
0, // Priorität
NULL // Task-Handle (nicht benötigt)
);
return true;
}
// #### Spoolman init
bool checkSpoolmanExtraFields() {
HTTPClient http;
@ -364,12 +428,13 @@ bool checkSpoolmanExtraFields() {
}
}
}
http.end();
}
Serial.println("-------- ENDE Prüfe Felder --------");
Serial.println();
http.end();
return true;
}
@ -413,17 +478,24 @@ bool checkSpoolmanInstance(const String& url) {
return false;
}
bool saveSpoolmanUrl(const String& url) {
bool saveSpoolmanUrl(const String& url, bool octoOn, const String& octoWh, const String& octoTk) {
if (!checkSpoolmanInstance(url)) return false;
JsonDocument doc;
doc["url"] = url;
Serial.print("Speichere URL in Datei: ");
Serial.println(url);
doc["octoEnabled"] = octoOn;
doc["octoUrl"] = octoWh;
doc["octoToken"] = octoTk;
Serial.print("Speichere Spoolman Data in Datei: ");
Serial.println(doc.as<String>());
if (!saveJsonValue("/spoolman_url.json", doc)) {
Serial.println("Fehler beim Speichern der Spoolman-URL.");
return false;
}
spoolmanUrl = url;
octoEnabled = octoOn;
octoUrl = octoWh;
octoToken = octoTk;
return true;
}
@ -431,6 +503,13 @@ bool saveSpoolmanUrl(const String& url) {
String loadSpoolmanUrl() {
JsonDocument doc;
if (loadJsonValue("/spoolman_url.json", doc) && doc["url"].is<String>()) {
octoEnabled = (doc["octoEnabled"].is<bool>()) ? doc["octoEnabled"].as<bool>() : false;
if (octoEnabled && doc["octoToken"].is<String>() && doc["octoUrl"].is<String>())
{
octoUrl = doc["octoUrl"].as<String>();
octoToken = doc["octoToken"].as<String>();
}
return doc["url"].as<String>();
}
Serial.println("Keine gültige Spoolman-URL gefunden.");

View File

@ -9,15 +9,19 @@
extern bool spoolman_connected;
extern String spoolmanUrl;
extern bool octoEnabled;
extern String octoUrl;
extern String octoToken;
bool checkSpoolmanInstance(const String& url);
bool saveSpoolmanUrl(const String& url);
bool saveSpoolmanUrl(const String& url, bool octoOn, const String& octoWh, const String& octoTk);
String loadSpoolmanUrl(); // Neue Funktion zum Laden der URL
bool checkSpoolmanExtraFields(); // Neue Funktion zum Überprüfen der Extrafelder
JsonDocument fetchSingleSpoolInfo(int spoolId); // API-Funktion für die Webseite
void sendAmsData(AsyncWebSocketClient *client); // Neue Funktion zum Senden von AMS-Daten
bool updateSpoolTagId(String uidString, const char* payload); // Neue Funktion zum Aktualisieren eines Spools
uint8_t updateSpoolWeight(String spoolId, uint16_t weight); // Neue Funktion zum Aktualisieren des Gewichts
bool initSpoolman(); // Neue Funktion zum Initialisieren von Spoolman
bool updateSpoolBambuData(String payload); // Neue Funktion zum Aktualisieren der Bambu-Daten
bool updateSpoolOcto(int spoolId); // Neue Funktion zum Aktualisieren der Octo-Daten
#endif

View File

@ -23,6 +23,11 @@ const char* bambu_username = "bblp";
const char* bambu_ip = nullptr;
const char* bambu_accesscode = nullptr;
const char* bambu_serialnr = nullptr;
String g_bambu_ip = "";
String g_bambu_accesscode = "";
String g_bambu_serialnr = "";
bool bambu_connected = false;
bool autoSendToBambu = false;
int autoSetToBambuSpoolId = 0;
@ -32,7 +37,7 @@ int ams_count = 0;
String amsJsonData; // Speichert das fertige JSON für WebSocket-Clients
AMSData ams_data[MAX_AMS]; // Definition des Arrays;
bool saveBambuCredentials(const String& ip, const String& serialnr, const String& accesscode, bool autoSend) {
bool saveBambuCredentials(const String& ip, const String& serialnr, const String& accesscode, bool autoSend, const String& autoSendTime) {
if (BambuMqttTask) {
vTaskDelete(BambuMqttTask);
}
@ -42,6 +47,7 @@ bool saveBambuCredentials(const String& ip, const String& serialnr, const String
doc["bambu_accesscode"] = accesscode;
doc["bambu_serialnr"] = serialnr;
doc["autoSendToBambu"] = autoSend;
doc["autoSendTime"] = (autoSendTime != "") ? autoSendTime.toInt() : autoSetBambuAmsCounter;
if (!saveJsonValue("/bambu_credentials.json", doc)) {
Serial.println("Fehler beim Speichern der Bambu-Credentials.");
@ -53,6 +59,7 @@ bool saveBambuCredentials(const String& ip, const String& serialnr, const String
bambu_accesscode = accesscode.c_str();
bambu_serialnr = serialnr.c_str();
autoSendToBambu = autoSend;
autoSetBambuAmsCounter = autoSendTime.toInt();
vTaskDelay(100 / portTICK_PERIOD_MS);
if (!setupMqtt()) return false;
@ -67,16 +74,22 @@ bool loadBambuCredentials() {
String ip = doc["bambu_ip"].as<String>();
String code = doc["bambu_accesscode"].as<String>();
String serial = doc["bambu_serialnr"].as<String>();
autoSendToBambu = doc["autoSendToBambu"].as<bool>();
g_bambu_ip = ip;
g_bambu_accesscode = code;
g_bambu_serialnr = serial;
if (doc["autoSendToBambu"].is<bool>()) autoSendToBambu = doc["autoSendToBambu"].as<bool>();
if (doc["autoSendTime"].is<int>()) autoSetBambuAmsCounter = doc["autoSendTime"].as<int>();
ip.trim();
code.trim();
serial.trim();
// Dynamische Speicherallokation für die globalen Pointer
bambu_ip = strdup(ip.c_str());
bambu_accesscode = strdup(code.c_str());
bambu_serialnr = strdup(serial.c_str());
bambu_ip = g_bambu_ip.c_str();
bambu_accesscode = g_bambu_accesscode.c_str();
bambu_serialnr = g_bambu_serialnr.c_str();
report_topic = "device/" + String(bambu_serialnr) + "/report";
//request_topic = "device/" + String(bambu_serialnr) + "/request";
@ -95,12 +108,38 @@ FilamentResult findFilamentIdx(String brand, String type) {
// JSON-Dokument für die Filament-Daten erstellen
JsonDocument doc;
// Laden der own_filaments.json
String ownFilament = "";
if (!loadJsonValue("/own_filaments.json", doc))
{
Serial.println("Fehler beim Laden der eigenen Filament-Daten");
}
else
{
// Durchsuche direkt nach dem Type als Schlüssel
if (doc[type].is<String>()) {
ownFilament = doc[type].as<String>();
}
doc.clear();
}
doc.clear();
// Laden der bambu_filaments.json
if (!loadJsonValue("/bambu_filaments.json", doc)) {
if (!loadJsonValue("/bambu_filaments.json", doc))
{
Serial.println("Fehler beim Laden der Filament-Daten");
return {"GFL99", "PLA"}; // Fallback auf Generic PLA
}
// Wenn eigener Typ
if (ownFilament != "")
{
if (doc[ownFilament].is<String>())
{
return {ownFilament, doc[ownFilament].as<String>()};
}
}
// 1. Erst versuchen wir die exakte Brand + Type Kombination zu finden
String searchKey;
if (brand == "Bambu" || brand == "Bambulab") {
@ -157,7 +196,7 @@ FilamentResult findFilamentIdx(String brand, String type) {
return {"GFL99", "PLA"};
}
bool sendMqttMessage(String payload) {
bool sendMqttMessage(const String& payload) {
Serial.println("Sending MQTT message");
Serial.println(payload);
if (client.publish(report_topic.c_str(), payload.c_str()))
@ -275,16 +314,102 @@ void autoSetSpool(int spoolId, uint8_t trayId) {
Serial.println(spoolInfo.as<String>());
setBambuSpool(spoolInfo.as<String>());
oledShowMessage("Spool set");
}
// id wieder zurücksetzen damit abgeschlossen
autoSetToBambuSpoolId = 0;
}
void updateAmsWsData(JsonDocument& doc, JsonArray& amsArray, int& ams_count, JsonObject& vtTray) {
// Fortfahren mit der bestehenden Verarbeitung, da Änderungen gefunden wurden
ams_count = amsArray.size();
for (int i = 0; i < ams_count && i < 16; i++) {
JsonObject amsObj = amsArray[i];
JsonArray trayArray = amsObj["tray"].as<JsonArray>();
ams_data[i].ams_id = i; // Setze die AMS-ID
for (int j = 0; j < trayArray.size() && j < 4; j++) { // Annahme: Maximal 4 Trays pro AMS
JsonObject trayObj = trayArray[j];
ams_data[i].trays[j].id = trayObj["id"].as<uint8_t>();
ams_data[i].trays[j].tray_info_idx = trayObj["tray_info_idx"].as<String>();
ams_data[i].trays[j].tray_type = trayObj["tray_type"].as<String>();
ams_data[i].trays[j].tray_sub_brands = trayObj["tray_sub_brands"].as<String>();
ams_data[i].trays[j].tray_color = trayObj["tray_color"].as<String>();
ams_data[i].trays[j].nozzle_temp_min = trayObj["nozzle_temp_min"].as<int>();
ams_data[i].trays[j].nozzle_temp_max = trayObj["nozzle_temp_max"].as<int>();
if (trayObj["tray_type"].as<String>() == "") ams_data[i].trays[j].setting_id = "";
ams_data[i].trays[j].cali_idx = trayObj["cali_idx"].as<String>();
}
}
// Setze ams_count auf die Anzahl der normalen AMS
ams_count = amsArray.size();
// Wenn externe Spule vorhanden, füge sie hinzu
if (doc["print"]["vt_tray"].is<JsonObject>()) {
//JsonObject vtTray = doc["print"]["vt_tray"];
int extIdx = ams_count; // Index für externe Spule
ams_data[extIdx].ams_id = 255; // Spezielle ID für externe Spule
ams_data[extIdx].trays[0].id = 254; // Spezielle ID für externes Tray
ams_data[extIdx].trays[0].tray_info_idx = vtTray["tray_info_idx"].as<String>();
ams_data[extIdx].trays[0].tray_type = vtTray["tray_type"].as<String>();
ams_data[extIdx].trays[0].tray_sub_brands = vtTray["tray_sub_brands"].as<String>();
ams_data[extIdx].trays[0].tray_color = vtTray["tray_color"].as<String>();
ams_data[extIdx].trays[0].nozzle_temp_min = vtTray["nozzle_temp_min"].as<int>();
ams_data[extIdx].trays[0].nozzle_temp_max = vtTray["nozzle_temp_max"].as<int>();
if (doc["print"]["vt_tray"]["tray_type"].as<String>() != "")
{
//ams_data[extIdx].trays[0].setting_id = vtTray["setting_id"].as<String>();
ams_data[extIdx].trays[0].cali_idx = vtTray["cali_idx"].as<String>();
}
else
{
ams_data[extIdx].trays[0].setting_id = "";
ams_data[extIdx].trays[0].cali_idx = "";
}
ams_count++; // Erhöhe ams_count für die externe Spule
}
// Erstelle JSON für WebSocket-Clients
JsonDocument wsDoc;
JsonArray wsArray = wsDoc.to<JsonArray>();
for (int i = 0; i < ams_count; i++) {
JsonObject amsObj = wsArray.add<JsonObject>();
amsObj["ams_id"] = ams_data[i].ams_id;
JsonArray trays = amsObj["tray"].to<JsonArray>();
int maxTrays = (ams_data[i].ams_id == 255) ? 1 : 4;
for (int j = 0; j < maxTrays; j++) {
JsonObject trayObj = trays.add<JsonObject>();
trayObj["id"] = ams_data[i].trays[j].id;
trayObj["tray_info_idx"] = ams_data[i].trays[j].tray_info_idx;
trayObj["tray_type"] = ams_data[i].trays[j].tray_type;
trayObj["tray_sub_brands"] = ams_data[i].trays[j].tray_sub_brands;
trayObj["tray_color"] = ams_data[i].trays[j].tray_color;
trayObj["nozzle_temp_min"] = ams_data[i].trays[j].nozzle_temp_min;
trayObj["nozzle_temp_max"] = ams_data[i].trays[j].nozzle_temp_max;
trayObj["setting_id"] = ams_data[i].trays[j].setting_id;
trayObj["cali_idx"] = ams_data[i].trays[j].cali_idx;
}
}
serializeJson(wsArray, amsJsonData);
wsDoc.clear();
Serial.println("AMS data updated");
sendAmsData(nullptr);
}
// init
void mqtt_callback(char* topic, byte* payload, unsigned int length) {
String message;
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
@ -292,6 +417,7 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
// JSON-Dokument parsen
JsonDocument doc;
DeserializationError error = deserializeJson(doc, message);
message = "";
if (error)
{
Serial.print("Fehler beim Parsen des JSON: ");
@ -299,16 +425,8 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
return;
}
// Wenn bambu auto set spool aktiv und eine spule erkannt und mqtt meldung das neue spule im ams
if (autoSendToBambu && autoSetToBambuSpoolId > 0 &&
doc["print"]["command"].as<String>() == "push_status" && doc["print"]["ams"]["tray_pre"].as<uint8_t>()
&& !doc["print"]["ams"]["ams"].as<JsonArray>())
{
autoSetSpool(autoSetToBambuSpoolId, doc["print"]["ams"]["tray_pre"].as<uint8_t>());
}
// Prüfen, ob "print->upgrade_state" und "print.ams.ams" existieren
if (doc["print"]["upgrade_state"].is<JsonObject>())
if (doc["print"]["upgrade_state"].is<JsonObject>() || (doc["print"]["command"].is<String>() && doc["print"]["command"] == "push_status"))
{
// Prüfen ob AMS-Daten vorhanden sind
if (!doc["print"]["ams"].is<JsonObject>() || !doc["print"]["ams"]["ams"].is<JsonArray>())
@ -317,7 +435,7 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
}
JsonArray amsArray = doc["print"]["ams"]["ams"].as<JsonArray>();
// Prüfe ob sich die AMS-Daten geändert haben
bool hasChanges = false;
@ -344,158 +462,81 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
// Vergleiche die Trays
for (int j = 0; j < trayArray.size() && j < 4 && !hasChanges; j++) {
JsonObject trayObj = trayArray[j];
if (trayObj["tray_type"].as<String>() == "") ams_data[storedIndex].trays[j].setting_id = "";
if (trayObj["setting_id"].isNull()) trayObj["setting_id"] = "";
if (trayObj["tray_info_idx"].as<String>() != ams_data[storedIndex].trays[j].tray_info_idx ||
trayObj["tray_type"].as<String>() != ams_data[storedIndex].trays[j].tray_type ||
trayObj["tray_color"].as<String>() != ams_data[storedIndex].trays[j].tray_color ||
(trayObj["setting_id"].as<String>() != "" && trayObj["setting_id"].as<String>() != ams_data[storedIndex].trays[j].setting_id) ||
trayObj["cali_idx"].as<String>() != ams_data[storedIndex].trays[j].cali_idx) {
hasChanges = true;
if (autoSendToBambu && autoSetToBambuSpoolId > 0 && hasChanges)
{
autoSetSpool(autoSetToBambuSpoolId, ams_data[storedIndex].trays[j].id);
}
break;
}
}
}
// Prüfe die externe Spule
if (!hasChanges && doc["print"]["vt_tray"].is<JsonObject>()) {
JsonObject vtTray = doc["print"]["vt_tray"];
bool foundExternal = false;
JsonObject vtTray = doc["print"]["vt_tray"];
if (doc["print"]["vt_tray"].is<JsonObject>()) {
for (int i = 0; i < ams_count; i++) {
if (ams_data[i].ams_id == 255) {
foundExternal = true;
if (vtTray["tray_type"].as<String>() == "") ams_data[i].trays[0].setting_id = "";
if (vtTray["setting_id"].isNull()) vtTray["setting_id"] = "";
if (vtTray["tray_info_idx"].as<String>() != ams_data[i].trays[0].tray_info_idx ||
vtTray["tray_type"].as<String>() != ams_data[i].trays[0].tray_type ||
vtTray["tray_color"].as<String>() != ams_data[i].trays[0].tray_color ||
vtTray["cali_idx"].as<String>() != ams_data[i].trays[0].cali_idx) {
(vtTray["setting_id"].as<String>() != "" && vtTray["setting_id"].as<String>() != ams_data[i].trays[0].setting_id) ||
(vtTray["tray_type"].as<String>() != "" && vtTray["cali_idx"].as<String>() != ams_data[i].trays[0].cali_idx)) {
hasChanges = true;
if (autoSendToBambu && autoSetToBambuSpoolId > 0 && hasChanges)
{
autoSetSpool(autoSetToBambuSpoolId, 254);
}
}
break;
}
}
if (!foundExternal) hasChanges = true;
}
if (!hasChanges) return;
// Fortfahren mit der bestehenden Verarbeitung, da Änderungen gefunden wurden
ams_count = amsArray.size();
for (int i = 0; i < ams_count && i < 16; i++) {
JsonObject amsObj = amsArray[i];
JsonArray trayArray = amsObj["tray"].as<JsonArray>();
ams_data[i].ams_id = i; // Setze die AMS-ID
for (int j = 0; j < trayArray.size() && j < 4; j++) { // Annahme: Maximal 4 Trays pro AMS
JsonObject trayObj = trayArray[j];
ams_data[i].trays[j].id = trayObj["id"].as<uint8_t>();
ams_data[i].trays[j].tray_info_idx = trayObj["tray_info_idx"].as<String>();
ams_data[i].trays[j].tray_type = trayObj["tray_type"].as<String>();
ams_data[i].trays[j].tray_sub_brands = trayObj["tray_sub_brands"].as<String>();
ams_data[i].trays[j].tray_color = trayObj["tray_color"].as<String>();
ams_data[i].trays[j].nozzle_temp_min = trayObj["nozzle_temp_min"].as<int>();
ams_data[i].trays[j].nozzle_temp_max = trayObj["nozzle_temp_max"].as<int>();
ams_data[i].trays[j].setting_id = trayObj["setting_id"].as<String>();
ams_data[i].trays[j].cali_idx = trayObj["cali_idx"].as<String>();
}
}
// Setze ams_count auf die Anzahl der normalen AMS
ams_count = amsArray.size();
// Wenn externe Spule vorhanden, füge sie hinzu
if (doc["print"]["vt_tray"].is<JsonObject>()) {
JsonObject vtTray = doc["print"]["vt_tray"];
int extIdx = ams_count; // Index für externe Spule
ams_data[extIdx].ams_id = 255; // Spezielle ID für externe Spule
ams_data[extIdx].trays[0].id = 254; // Spezielle ID für externes Tray
ams_data[extIdx].trays[0].tray_info_idx = vtTray["tray_info_idx"].as<String>();
ams_data[extIdx].trays[0].tray_type = vtTray["tray_type"].as<String>();
ams_data[extIdx].trays[0].tray_sub_brands = vtTray["tray_sub_brands"].as<String>();
ams_data[extIdx].trays[0].tray_color = vtTray["tray_color"].as<String>();
ams_data[extIdx].trays[0].nozzle_temp_min = vtTray["nozzle_temp_min"].as<int>();
ams_data[extIdx].trays[0].nozzle_temp_max = vtTray["nozzle_temp_max"].as<int>();
if (doc["print"]["vt_tray"]["tray_type"].as<String>() != "")
{
ams_data[extIdx].trays[0].setting_id = vtTray["setting_id"].as<String>();
ams_data[extIdx].trays[0].cali_idx = vtTray["cali_idx"].as<String>();
}
ams_count++; // Erhöhe ams_count für die externe Spule
}
// Sende die aktualisierten AMS-Daten
//sendAmsData(nullptr);
// Erstelle JSON für WebSocket-Clients
JsonDocument wsDoc;
JsonArray wsArray = wsDoc.to<JsonArray>();
for (int i = 0; i < ams_count; i++) {
JsonObject amsObj = wsArray.add<JsonObject>();
amsObj["ams_id"] = ams_data[i].ams_id;
JsonArray trays = amsObj["tray"].to<JsonArray>();
int maxTrays = (ams_data[i].ams_id == 255) ? 1 : 4;
for (int j = 0; j < maxTrays; j++) {
JsonObject trayObj = trays.add<JsonObject>();
trayObj["id"] = ams_data[i].trays[j].id;
trayObj["tray_info_idx"] = ams_data[i].trays[j].tray_info_idx;
trayObj["tray_type"] = ams_data[i].trays[j].tray_type;
trayObj["tray_sub_brands"] = ams_data[i].trays[j].tray_sub_brands;
trayObj["tray_color"] = ams_data[i].trays[j].tray_color;
trayObj["nozzle_temp_min"] = ams_data[i].trays[j].nozzle_temp_min;
trayObj["nozzle_temp_max"] = ams_data[i].trays[j].nozzle_temp_max;
trayObj["setting_id"] = ams_data[i].trays[j].setting_id;
trayObj["cali_idx"] = ams_data[i].trays[j].cali_idx;
}
}
serializeJson(wsArray, amsJsonData);
sendAmsData(nullptr);
updateAmsWsData(doc, amsArray, ams_count, vtTray);
}
// Neue Bedingung für ams_filament_setting
else if (doc["print"]["command"] == "ams_filament_setting") {
if (doc["print"]["command"] == "ams_filament_setting") {
int amsId = doc["print"]["ams_id"].as<int>();
int trayId = doc["print"]["tray_id"].as<int>();
String settingId = doc["print"]["setting_id"].as<String>();
String settingId = (doc["print"]["setting_id"].is<String>()) ? doc["print"]["setting_id"].as<String>() : "";
// Finde das entsprechende AMS und Tray
for (int i = 0; i < ams_count; i++) {
if (ams_data[i].ams_id == amsId) {
// Update setting_id im entsprechenden Tray
ams_data[i].trays[trayId].setting_id = settingId;
// Erstelle neues JSON für WebSocket-Clients
JsonDocument wsDoc;
JsonArray wsArray = wsDoc.to<JsonArray>();
for (int j = 0; j < ams_count; j++) {
JsonObject amsObj = wsArray.add<JsonObject>();
amsObj["ams_id"] = ams_data[j].ams_id;
JsonArray trays = amsObj["tray"].to<JsonArray>();
int maxTrays = (ams_data[j].ams_id == 255) ? 1 : 4;
for (int k = 0; k < maxTrays; k++) {
JsonObject trayObj = trays.add<JsonObject>();
trayObj["id"] = ams_data[j].trays[k].id;
trayObj["tray_info_idx"] = ams_data[j].trays[k].tray_info_idx;
trayObj["tray_type"] = ams_data[j].trays[k].tray_type;
trayObj["tray_sub_brands"] = ams_data[j].trays[k].tray_sub_brands;
trayObj["tray_color"] = ams_data[j].trays[k].tray_color;
trayObj["nozzle_temp_min"] = ams_data[j].trays[k].nozzle_temp_min;
trayObj["nozzle_temp_max"] = ams_data[j].trays[k].nozzle_temp_max;
trayObj["setting_id"] = ams_data[j].trays[k].setting_id;
trayObj["cali_idx"] = ams_data[j].trays[k].cali_idx;
if (trayId == 254)
{
// Suche AMS mit ID 255 (externe Spule)
for (int j = 0; j < ams_count; j++) {
if (ams_data[j].ams_id == 255) {
ams_data[j].trays[0].setting_id = settingId;
break;
}
}
}
// Aktualisiere das globale amsJsonData
amsJsonData = "";
serializeJson(wsArray, amsJsonData);
else
{
ams_data[i].trays[trayId].setting_id = settingId;
}
// Sende an WebSocket Clients
Serial.println("Filament setting updated");
sendAmsData(nullptr);
break;
}
@ -505,15 +546,16 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
void reconnect() {
// Loop until we're reconnected
uint8_t retries = 0;
while (!client.connected()) {
Serial.println("Attempting MQTT connection...");
Serial.println("Attempting MQTT re/connection...");
bambu_connected = false;
oledShowTopRow();
// Attempt to connect
if (client.connect(bambu_serialnr, bambu_username, bambu_accesscode)) {
Serial.println("... re-connected");
// ... and resubscribe
Serial.println("MQTT re/connected");
client.subscribe(report_topic.c_str());
bambu_connected = true;
oledShowTopRow();
@ -523,14 +565,23 @@ void reconnect() {
Serial.println(" try again in 5 seconds");
bambu_connected = false;
oledShowTopRow();
// Wait 5 seconds before retrying
yield();
vTaskDelay(5000 / portTICK_PERIOD_MS);
if (retries > 5) {
Serial.println("Disable Bambu MQTT Task after 5 retries");
//vTaskSuspend(BambuMqttTask);
vTaskDelete(BambuMqttTask);
break;
}
retries++;
}
}
}
void mqtt_loop(void * parameter) {
Serial.println("Bambu MQTT Task gestartet");
for(;;) {
if (pauseBambuMqttTask) {
vTaskDelay(10000);
@ -544,6 +595,7 @@ void mqtt_loop(void * parameter) {
}
client.loop();
yield();
esp_task_wdt_reset();
vTaskDelay(100);
}
}
@ -551,7 +603,6 @@ void mqtt_loop(void * parameter) {
bool setupMqtt() {
// Wenn Bambu Daten vorhanden
bool success = loadBambuCredentials();
vTaskDelay(100 / portTICK_PERIOD_MS);
if (!success) {
Serial.println("Failed to load Bambu credentials");
@ -584,7 +635,7 @@ bool setupMqtt() {
xTaskCreatePinnedToCore(
mqtt_loop, /* Function to implement the task */
"BambuMqtt", /* Name of the task */
10000, /* Stack size in words */
8192, /* Stack size in words */
NULL, /* Task input parameter */
mqttTaskPrio, /* Priority of the task */
&BambuMqttTask, /* Task handle. */
@ -615,6 +666,7 @@ bool setupMqtt() {
void bambu_restart() {
if (BambuMqttTask) {
vTaskDelete(BambuMqttTask);
delay(10);
}
setupMqtt();
}

View File

@ -32,7 +32,7 @@ extern bool autoSendToBambu;
extern int autoSetToBambuSpoolId;
bool loadBambuCredentials();
bool saveBambuCredentials(const String& bambu_ip, const String& bambu_serialnr, const String& bambu_accesscode, const bool autoSend);
bool saveBambuCredentials(const String& bambu_ip, const String& bambu_serialnr, const String& bambu_accesscode, const bool autoSend, const String& autoSendTime);
bool setupMqtt();
void mqtt_loop(void * parameter);
bool setBambuSpool(String payload);

View File

@ -1,8 +1,8 @@
#include "commonFS.h"
#include <SPIFFS.h>
#include <LittleFS.h>
bool saveJsonValue(const char* filename, const JsonDocument& doc) {
File file = SPIFFS.open(filename, "w");
File file = LittleFS.open(filename, "w");
if (!file) {
Serial.print("Fehler beim Öffnen der Datei zum Schreiben: ");
Serial.println(filename);
@ -20,7 +20,7 @@ bool saveJsonValue(const char* filename, const JsonDocument& doc) {
}
bool loadJsonValue(const char* filename, JsonDocument& doc) {
File file = SPIFFS.open(filename, "r");
File file = LittleFS.open(filename, "r");
if (!file) {
Serial.print("Fehler beim Öffnen der Datei zum Lesen: ");
Serial.println(filename);
@ -36,12 +36,12 @@ bool loadJsonValue(const char* filename, JsonDocument& doc) {
return true;
}
void initializeSPIFFS() {
if (!SPIFFS.begin(true, "/spiffs", 10, "spiffs")) {
Serial.println("SPIFFS Mount Failed");
void initializeFileSystem() {
if (!LittleFS.begin(true)) {
Serial.println("LittleFS Mount Failed");
return;
}
Serial.printf("SPIFFS Total: %u bytes\n", SPIFFS.totalBytes());
Serial.printf("SPIFFS Used: %u bytes\n", SPIFFS.usedBytes());
Serial.printf("SPIFFS Free: %u bytes\n", SPIFFS.totalBytes() - SPIFFS.usedBytes());
Serial.printf("LittleFS Total: %u bytes\n", LittleFS.totalBytes());
Serial.printf("LittleFS Used: %u bytes\n", LittleFS.usedBytes());
Serial.printf("LittleFS Free: %u bytes\n", LittleFS.totalBytes() - LittleFS.usedBytes());
}

View File

@ -2,11 +2,11 @@
#define COMMONFS_H
#include <Arduino.h>
#include <SPIFFS.h>
#include <ArduinoJson.h>
#include <LittleFS.h>
bool saveJsonValue(const char* filename, const JsonDocument& doc);
bool loadJsonValue(const char* filename, JsonDocument& doc);
void initializeSPIFFS();
void initializeFileSystem();
#endif

View File

@ -20,9 +20,9 @@ void setupDisplay() {
// the library initializes this with an Adafruit splash screen.
display.setTextColor(WHITE);
display.display();
delay(1000); // Pause for 2 seconds
oledShowTopRow();
delay(2000);
oledShowMessage("FilaMan v" + String(VERSION));
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
void oledclearline() {
@ -139,8 +139,9 @@ void oledShowMultilineMessage(String message, uint8_t size) {
int totalHeight = lines.size() * lineHeight;
int startY = OLED_DATA_START + ((OLED_DATA_END - OLED_DATA_START - totalHeight) / 2);
uint8_t lineDistance = (lines.size() == 2) ? 5 : 0;
for (size_t i = 0; i < lines.size(); i++) {
display.setCursor(oled_center_h(lines[i]), startY + (i * lineHeight));
display.setCursor(oled_center_h(lines[i]), startY + (i * lineHeight) + (i == 1 ? lineDistance : 0));
display.print(lines[i]);
}

View File

@ -1,6 +1,4 @@
#include <Arduino.h>
#include <DNSServer.h>
#include <ESPmDNS.h>
#include <Wire.h>
#include <WiFi.h>
@ -26,7 +24,7 @@ void setup() {
Serial.printf("%08X\n", (uint32_t)chipid); //print Low 4bytes.
// Initialize SPIFFS
initializeSPIFFS();
initializeFileSystem();
// Start Display
setupDisplay();
@ -35,7 +33,6 @@ void setup() {
initWiFi();
// Webserver
Serial.println("Starte Webserver");
setupWebserver(server);
// Spoolman API
@ -43,19 +40,9 @@ void setup() {
initSpoolman();
// Bambu MQTT
// bambu.cpp
setupMqtt();
// mDNS
Serial.println("Starte MDNS");
if (!MDNS.begin("filaman")) { // Set the hostname to "esp32.local"
Serial.println("Error setting up MDNS responder!");
while(1) {
delay(1000);
}
}
Serial.println("mDNS responder started");
// NFC Reader
startNfc();
uint8_t scaleCalibrated = start_scale();
@ -87,6 +74,21 @@ void setup() {
}
/**
* Safe interval check that handles millis() overflow
* @param currentTime Current millis() value
* @param lastTime Last recorded time
* @param interval Desired interval in milliseconds
* @return True if interval has elapsed
*/
bool intervalElapsed(unsigned long currentTime, unsigned long &lastTime, unsigned long interval) {
if (currentTime - lastTime >= interval || currentTime < lastTime) {
lastTime = currentTime;
return true;
}
return false;
}
unsigned long lastWeightReadTime = 0;
const unsigned long weightReadInterval = 1000; // 1 second
@ -94,39 +96,44 @@ unsigned long lastAutoSetBambuAmsTime = 0;
const unsigned long autoSetBambuAmsInterval = 1000; // 1 second
uint8_t autoAmsCounter = 0;
unsigned long lastAmsSendTime = 0;
const unsigned long amsSendInterval = 60000; // 1 minute
uint8_t weightSend = 0;
int16_t lastWeight = 0;
uint8_t wifiErrorCounter = 0;
unsigned long lastWifiCheckTime = 0;
const unsigned long wifiCheckInterval = 60000; // Überprüfe alle 60 Sekunden (60000 ms)
// ##### PROGRAM START #####
void loop() {
unsigned long currentMillis = millis();
// Send AMS Data min every Minute
if (currentMillis - lastAmsSendTime >= amsSendInterval)
{
lastAmsSendTime = currentMillis;
sendAmsData(nullptr);
// Überprüfe regelmäßig die WLAN-Verbindung
if (intervalElapsed(currentMillis, lastWifiCheckTime, wifiCheckInterval)) {
checkWiFiConnection();
}
// Wenn Bambu auto set Spool aktiv
if (autoSendToBambu && autoSetToBambuSpoolId > 0 && currentMillis - lastAutoSetBambuAmsTime >= autoSetBambuAmsInterval)
{
lastAutoSetBambuAmsTime = currentMillis;
oledShowMessage("Auto Set " + String(autoSetBambuAmsCounter - autoAmsCounter) + "s");
autoAmsCounter++;
if (autoAmsCounter >= autoSetBambuAmsCounter)
if (autoSendToBambu && autoSetToBambuSpoolId > 0) {
if (intervalElapsed(currentMillis, lastAutoSetBambuAmsTime, autoSetBambuAmsInterval))
{
autoSetToBambuSpoolId = 0;
autoAmsCounter = 0;
oledShowWeight(weight);
if (hasReadRfidTag == 0)
{
lastAutoSetBambuAmsTime = currentMillis;
oledShowMessage("Auto Set " + String(autoSetBambuAmsCounter - autoAmsCounter) + "s");
autoAmsCounter++;
if (autoAmsCounter >= autoSetBambuAmsCounter)
{
autoSetToBambuSpoolId = 0;
autoAmsCounter = 0;
oledShowWeight(weight);
}
}
else
{
autoAmsCounter = 0;
}
}
}
// Wenn Waage nicht Kalibriert
if (scaleCalibrated == 3)
@ -190,6 +197,11 @@ void loop() {
vTaskDelay(2000 / portTICK_PERIOD_MS);
weightSend = 1;
autoSetToBambuSpoolId = spoolId.toInt();
if (octoEnabled)
{
updateSpoolOcto(autoSetToBambuSpoolId);
}
}
else
{

View File

@ -44,8 +44,6 @@ void payloadToJson(uint8_t *data) {
DeserializationError error = deserializeJson(doc, jsonString);
if (!error) {
const char* version = doc["version"];
const char* protocol = doc["protocol"];
const char* color_hex = doc["color_hex"];
const char* type = doc["type"];
int min_temp = doc["min_temp"];
@ -55,8 +53,6 @@ void payloadToJson(uint8_t *data) {
Serial.println();
Serial.println("-----------------");
Serial.println("JSON-Parsed Data:");
Serial.println(version);
Serial.println(protocol);
Serial.println(color_hex);
Serial.println(type);
Serial.println(min_temp);
@ -93,8 +89,16 @@ bool formatNdefTag() {
return success;
}
uint16_t readTagSize()
{
uint8_t buffer[4];
memset(buffer, 0, 4);
nfc.ntag2xx_ReadPage(3, buffer);
return buffer[2]*8;
}
uint8_t ntag2xx_WriteNDEF(const char *payload) {
uint8_t tagSize = 240; // 144 bytes is maximum for NTAG213
uint16_t tagSize = readTagSize();
Serial.print("Tag Size: ");Serial.println(tagSize);
uint8_t pageBuffer[4] = {0, 0, 0, 0};
@ -136,6 +140,8 @@ uint8_t ntag2xx_WriteNDEF(const char *payload) {
if (combinedData == NULL)
{
Serial.println("Fehler: Nicht genug Speicher vorhanden.");
oledShowMessage("Tag too small");
vTaskDelay(2000 / portTICK_PERIOD_MS);
return 0;
}
@ -238,12 +244,14 @@ void writeJsonToTag(void *parameter) {
hasReadRfidTag = 3;
vTaskSuspend(RfidReaderTask);
vTaskDelay(500 / portTICK_PERIOD_MS);
vTaskDelay(50 / portTICK_PERIOD_MS);
//pauseBambuMqttTask = true;
// aktualisieren der Website wenn sich der Status ändert
sendNfcData(nullptr);
vTaskDelay(100 / portTICK_PERIOD_MS);
oledShowMessage("Waiting for NFC-Tag");
// Wait 10sec for tag
uint8_t success = 0;
String uidString = "";
@ -331,7 +339,7 @@ void startWriteJsonToTag(const char* payload) {
xTaskCreate(
writeJsonToTag, // Task-Funktion
"WriteJsonToTagTask", // Task-Name
4096, // Stackgröße in Bytes
5115, // Stackgröße in Bytes
(void*)payloadCopy, // Parameter
rfidWriteTaskPrio, // Priorität
NULL // Task-Handle (nicht benötigt)
@ -367,46 +375,51 @@ void scanRfidTask(void * parameter) {
if (uidLength == 7)
{
uint8_t data[256];
// We probably have an NTAG2xx card (though it could be Ultralight as well)
Serial.println("Seems to be an NTAG2xx tag (7 byte UID)");
for (uint8_t i = 0; i < 45; i++) {
/*
if (i < uidLength) {
uidString += String(uid[i], HEX);
if (i < uidLength - 1) {
uidString += ":"; // Optional: Trennzeichen hinzufügen
}
}
*/
if (!nfc.mifareclassic_ReadDataBlock(i, data + (i - 4) * 4))
{
break; // Stop if reading fails
}
// Check for NDEF message end
if (data[(i - 4) * 4] == 0xFE)
{
break; // End of NDEF message
}
yield();
esp_task_wdt_reset();
vTaskDelay(pdMS_TO_TICKS(1));
}
if (!decodeNdefAndReturnJson(data))
uint16_t tagSize = readTagSize();
if(tagSize > 0)
{
oledShowMessage("NFC-Tag unknown");
vTaskDelay(2000 / portTICK_PERIOD_MS);
// Create a buffer depending on the size of the tag
uint8_t* data = (uint8_t*)malloc(tagSize);
memset(data, 0, tagSize);
// We probably have an NTAG2xx card (though it could be Ultralight as well)
Serial.println("Seems to be an NTAG2xx tag (7 byte UID)");
uint8_t numPages = readTagSize()/4;
for (uint8_t i = 4; i < 4+numPages; i++) {
if (!nfc.ntag2xx_ReadPage(i, data+(i-4) * 4))
{
break; // Stop if reading fails
}
// Check for NDEF message end
if (data[(i - 4) * 4] == 0xFE)
{
break; // End of NDEF message
}
yield();
esp_task_wdt_reset();
vTaskDelay(pdMS_TO_TICKS(1));
}
if (!decodeNdefAndReturnJson(data))
{
oledShowMessage("NFC-Tag unknown");
vTaskDelay(2000 / portTICK_PERIOD_MS);
hasReadRfidTag = 2;
}
else
{
hasReadRfidTag = 1;
}
free(data);
}
else
{
oledShowMessage("NFC-Tag read error");
hasReadRfidTag = 2;
}
else
{
hasReadRfidTag = 1;
}
}
else
{
@ -456,7 +469,7 @@ void startNfc() {
BaseType_t result = xTaskCreatePinnedToCore(
scanRfidTask, /* Function to implement the task */
"RfidReader", /* Name of the task */
10000, /* Stack size in words */
5115, /* Stack size in words */
NULL, /* Task input parameter */
rfidTaskPrio, /* Priority of the task */
&RfidReaderTask, /* Task handle. */

243
src/ota.cpp Normal file
View File

@ -0,0 +1,243 @@
#include <Arduino.h>
#include <website.h>
#include <commonFS.h>
// Globale Variablen für Config Backups hinzufügen
String bambuCredentialsBackup;
String spoolmanUrlBackup;
// Globale Variable für den Update-Typ
static int currentUpdateCommand = 0;
// Globale Update-Variablen
static size_t updateTotalSize = 0;
static size_t updateWritten = 0;
static bool isSpiffsUpdate = false;
/**
* Compares two version strings and determines if version1 is less than version2
*
* @param version1 First version string (format: x.y.z)
* @param version2 Second version string (format: x.y.z)
* @return true if version1 is less than version2
*/
bool isVersionLessThan(const String& version1, const String& version2) {
int major1 = 0, minor1 = 0, patch1 = 0;
int major2 = 0, minor2 = 0, patch2 = 0;
// Parse version1
sscanf(version1.c_str(), "%d.%d.%d", &major1, &minor1, &patch1);
// Parse version2
sscanf(version2.c_str(), "%d.%d.%d", &major2, &minor2, &patch2);
// Compare major version
if (major1 < major2) return true;
if (major1 > major2) return false;
// Major versions equal, compare minor
if (minor1 < minor2) return true;
if (minor1 > minor2) return false;
// Minor versions equal, compare patch
return patch1 < patch2;
}
void backupJsonConfigs() {
// Bambu Credentials backup
if (LittleFS.exists("/bambu_credentials.json")) {
File file = LittleFS.open("/bambu_credentials.json", "r");
if (file) {
bambuCredentialsBackup = file.readString();
file.close();
Serial.println("Bambu credentials backed up");
}
}
// Spoolman URL backup
if (LittleFS.exists("/spoolman_url.json")) {
File file = LittleFS.open("/spoolman_url.json", "r");
if (file) {
spoolmanUrlBackup = file.readString();
file.close();
Serial.println("Spoolman URL backed up");
}
}
}
void restoreJsonConfigs() {
// Restore Bambu credentials
if (bambuCredentialsBackup.length() > 0) {
File file = LittleFS.open("/bambu_credentials.json", "w");
if (file) {
file.print(bambuCredentialsBackup);
file.close();
Serial.println("Bambu credentials restored");
}
bambuCredentialsBackup = ""; // Clear backup
}
// Restore Spoolman URL
if (spoolmanUrlBackup.length() > 0) {
File file = LittleFS.open("/spoolman_url.json", "w");
if (file) {
file.print(spoolmanUrlBackup);
file.close();
Serial.println("Spoolman URL restored");
}
spoolmanUrlBackup = ""; // Clear backup
}
}
void espRestart() {
yield();
vTaskDelay(5000 / portTICK_PERIOD_MS);
ESP.restart();
}
void sendUpdateProgress(int progress, const char* status = nullptr, const char* message = nullptr) {
static int lastSentProgress = -1;
// Verhindere zu häufige Updates
if (progress == lastSentProgress && !status && !message) {
return;
}
String progressMsg = "{\"type\":\"updateProgress\",\"progress\":" + String(progress);
if (status) {
progressMsg += ",\"status\":\"" + String(status) + "\"";
}
if (message) {
progressMsg += ",\"message\":\"" + String(message) + "\"";
}
progressMsg += "}";
if (progress >= 100) {
// Sende die Nachricht nur einmal für den Abschluss
ws.textAll("{\"type\":\"updateProgress\",\"progress\":100,\"status\":\"success\",\"message\":\"Update successful! Restarting device...\"}");
delay(50);
}
// Sende die Nachricht mehrmals mit Verzögerung für wichtige Updates
if (status || abs(progress - lastSentProgress) >= 10 || progress == 100) {
for (int i = 0; i < 2; i++) {
ws.textAll(progressMsg);
delay(100); // Längerer Delay zwischen Nachrichten
}
} else {
ws.textAll(progressMsg);
delay(50);
}
lastSentProgress = progress;
}
void handleUpdate(AsyncWebServer &server) {
AsyncCallbackWebHandler* updateHandler = new AsyncCallbackWebHandler();
updateHandler->setUri("/update");
updateHandler->setMethod(HTTP_POST);
// Check if current version is less than defined TOOLVERSION before proceeding with update
if (isVersionLessThan(VERSION, TOOLDVERSION)) {
updateHandler->onRequest([](AsyncWebServerRequest *request) {
request->send(400, "application/json",
"{\"success\":false,\"message\":\"Your current version is too old. Please perform a full upgrade.\"}");
});
server.addHandler(updateHandler);
return;
}
updateHandler->onUpload([](AsyncWebServerRequest *request, String filename,
size_t index, uint8_t *data, size_t len, bool final) {
if (!index) {
updateTotalSize = request->contentLength();
updateWritten = 0;
isSpiffsUpdate = (filename.indexOf("website") > -1);
if (isSpiffsUpdate) {
// Backup vor dem Update
sendUpdateProgress(0, "backup", "Backing up configurations...");
delay(200);
backupJsonConfigs();
delay(200);
const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, NULL);
if (!partition || !Update.begin(partition->size, U_SPIFFS)) {
request->send(400, "application/json", "{\"success\":false,\"message\":\"Update initialization failed\"}");
return;
}
sendUpdateProgress(5, "starting", "Starting SPIFFS update...");
delay(200);
} else {
if (!Update.begin(updateTotalSize)) {
request->send(400, "application/json", "{\"success\":false,\"message\":\"Update initialization failed\"}");
return;
}
sendUpdateProgress(0, "starting", "Starting firmware update...");
delay(200);
}
}
if (len) {
if (Update.write(data, len) != len) {
request->send(400, "application/json", "{\"success\":false,\"message\":\"Write failed\"}");
return;
}
updateWritten += len;
int currentProgress;
// Berechne den Fortschritt basierend auf dem Update-Typ
if (isSpiffsUpdate) {
// SPIFFS: 5-75% für Upload
currentProgress = 6 + (updateWritten * 100) / updateTotalSize;
} else {
// Firmware: 0-100% für Upload
currentProgress = 1 + (updateWritten * 100) / updateTotalSize;
}
static int lastProgress = -1;
if (currentProgress != lastProgress && (currentProgress % 10 == 0 || final)) {
sendUpdateProgress(currentProgress, "uploading");
oledShowMessage("Update: " + String(currentProgress) + "%");
delay(50);
lastProgress = currentProgress;
}
}
if (final) {
if (Update.end(true)) {
if (isSpiffsUpdate) {
restoreJsonConfigs();
}
} else {
request->send(400, "application/json", "{\"success\":false,\"message\":\"Update finalization failed\"}");
}
}
});
updateHandler->onRequest([](AsyncWebServerRequest *request) {
if (Update.hasError()) {
request->send(400, "application/json", "{\"success\":false,\"message\":\"Update failed\"}");
return;
}
// Erste 100% Nachricht
ws.textAll("{\"type\":\"updateProgress\",\"progress\":100,\"status\":\"success\",\"message\":\"Update successful! Restarting device...\"}");
vTaskDelay(2000 / portTICK_PERIOD_MS);
AsyncWebServerResponse *response = request->beginResponse(200, "application/json",
"{\"success\":true,\"message\":\"Update successful! Restarting device...\"}");
response->addHeader("Connection", "close");
request->send(response);
// Zweite 100% Nachricht zur Sicherheit
ws.textAll("{\"type\":\"updateProgress\",\"progress\":100,\"status\":\"success\",\"message\":\"Update successful! Restarting device...\"}");
espRestart();
});
server.addHandler(updateHandler);
}

9
src/ota.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef OTA_H
#define OTA_H
#include <ArduinoOTA.h>
#include <ESPAsyncWebServer.h>
void handleUpdate(AsyncWebServer &server);
#endif

View File

@ -47,7 +47,7 @@ void scale_loop(void * parameter) {
weight = round(scale.get_units());
}
vTaskDelay(pdMS_TO_TICKS(100)); // Verzögerung, um die CPU nicht zu überlasten
vTaskDelay(pdMS_TO_TICKS(100));
}
}
@ -90,7 +90,7 @@ uint8_t start_scale() {
BaseType_t result = xTaskCreatePinnedToCore(
scale_loop, /* Function to implement the task */
"ScaleLoop", /* Name of the task */
10000, /* Stack size in words */
2048, /* Stack size in words */
NULL, /* Task input parameter */
scaleTaskPrio, /* Priority of the task */
&ScaleTask, /* Task handle. */
@ -110,6 +110,7 @@ uint8_t calibrate_scale() {
//vTaskSuspend(RfidReaderTask);
vTaskDelete(RfidReaderTask);
vTaskDelete(ScaleTask);
pauseBambuMqttTask = true;
pauseMainTask = 1;
@ -177,8 +178,6 @@ uint8_t calibrate_scale() {
vTaskDelay(pdMS_TO_TICKS(1));
esp_task_wdt_reset();
}
//ESP.restart();
}
else
{
@ -211,9 +210,8 @@ uint8_t calibrate_scale() {
}
oledShowMessage("Scale Ready");
Serial.println("starte Scale Task");
Serial.println("restart Scale Task");
start_scale();
pauseBambuMqttTask = false;

View File

@ -9,6 +9,7 @@
#include "esp_task_wdt.h"
#include <Update.h>
#include "display.h"
#include "ota.h"
#ifndef VERSION
#define VERSION "1.1.0"
@ -23,48 +24,6 @@ AsyncWebSocket ws("/ws");
uint8_t lastSuccess = 0;
uint8_t lastHasReadRfidTag = 0;
// Globale Variablen für Config Backups hinzufügen
String bambuCredentialsBackup;
String spoolmanUrlBackup;
// Globale Variable für den Update-Typ
static int currentUpdateCommand = 0;
// Globale Update-Variablen
static size_t updateTotalSize = 0;
static size_t updateWritten = 0;
static bool isSpiffsUpdate = false;
void sendUpdateProgress(int progress, const char* status = nullptr, const char* message = nullptr) {
static int lastSentProgress = -1;
// Verhindere zu häufige Updates
if (progress == lastSentProgress && !status && !message) {
return;
}
String progressMsg = "{\"type\":\"updateProgress\",\"progress\":" + String(progress);
if (status) {
progressMsg += ",\"status\":\"" + String(status) + "\"";
}
if (message) {
progressMsg += ",\"message\":\"" + String(message) + "\"";
}
progressMsg += "}";
// Sende die Nachricht mehrmals mit Verzögerung für wichtige Updates
if (status || abs(progress - lastSentProgress) >= 10 || progress == 100) {
for (int i = 0; i < 2; i++) {
ws.textAll(progressMsg);
delay(100); // Längerer Delay zwischen Nachrichten
}
} else {
ws.textAll(progressMsg);
delay(50);
}
lastSentProgress = progress;
}
void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
if (type == WS_EVT_CONNECT) {
@ -136,6 +95,15 @@ void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventTyp
setBambuSpool(doc["payload"]);
}
else if (doc["type"] == "setSpoolmanSettings") {
Serial.println(doc["payload"].as<String>());
if (updateSpoolBambuData(doc["payload"].as<String>())) {
ws.textAll("{\"type\":\"setSpoolmanSettings\",\"payload\":\"success\"}");
} else {
ws.textAll("{\"type\":\"setSpoolmanSettings\",\"payload\":\"error\"}");
}
}
else {
Serial.println("Unbekannter WebSocket-Typ: " + doc["type"].as<String>());
}
@ -145,12 +113,12 @@ void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventTyp
// Funktion zum Laden und Ersetzen des Headers in einer HTML-Datei
String loadHtmlWithHeader(const char* filename) {
Serial.println("Lade HTML-Datei: " + String(filename));
if (!SPIFFS.exists(filename)) {
if (!LittleFS.exists(filename)) {
Serial.println("Fehler: Datei nicht gefunden!");
return "Fehler: Datei nicht gefunden!";
}
File file = SPIFFS.open(filename, "r");
File file = LittleFS.open(filename, "r");
String html = file.readString();
file.close();
@ -207,105 +175,6 @@ void sendAmsData(AsyncWebSocketClient *client) {
}
}
void handleUpdate(AsyncWebServer &server) {
AsyncCallbackWebHandler* updateHandler = new AsyncCallbackWebHandler();
updateHandler->setUri("/update");
updateHandler->setMethod(HTTP_POST);
updateHandler->onUpload([](AsyncWebServerRequest *request, String filename,
size_t index, uint8_t *data, size_t len, bool final) {
if (!index) {
updateTotalSize = request->contentLength();
updateWritten = 0;
isSpiffsUpdate = (filename.indexOf("website") > -1);
if (isSpiffsUpdate) {
// Backup vor dem Update
sendUpdateProgress(0, "backup", "Backing up configurations...");
delay(200);
backupJsonConfigs();
delay(200);
const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, NULL);
if (!partition || !Update.begin(partition->size, U_SPIFFS)) {
request->send(400, "application/json", "{\"success\":false,\"message\":\"Update initialization failed\"}");
return;
}
sendUpdateProgress(5, "starting", "Starting SPIFFS update...");
delay(200);
} else {
if (!Update.begin(updateTotalSize)) {
request->send(400, "application/json", "{\"success\":false,\"message\":\"Update initialization failed\"}");
return;
}
sendUpdateProgress(0, "starting", "Starting firmware update...");
delay(200);
}
}
if (len) {
if (Update.write(data, len) != len) {
request->send(400, "application/json", "{\"success\":false,\"message\":\"Write failed\"}");
return;
}
updateWritten += len;
int currentProgress;
// Berechne den Fortschritt basierend auf dem Update-Typ
if (isSpiffsUpdate) {
// SPIFFS: 5-75% für Upload
currentProgress = 5 + (updateWritten * 100) / updateTotalSize;
} else {
// Firmware: 0-100% für Upload
currentProgress = 1 + (updateWritten * 100) / updateTotalSize;
}
static int lastProgress = -1;
if (currentProgress != lastProgress && (currentProgress % 10 == 0 || final)) {
sendUpdateProgress(currentProgress, "uploading");
oledShowMessage("Update: " + String(currentProgress) + "%");
delay(50);
lastProgress = currentProgress;
}
}
if (final) {
if (Update.end(true)) {
if (isSpiffsUpdate) {
restoreJsonConfigs();
}
} else {
request->send(400, "application/json", "{\"success\":false,\"message\":\"Update finalization failed\"}");
}
}
});
updateHandler->onRequest([](AsyncWebServerRequest *request) {
if (Update.hasError()) {
request->send(400, "application/json", "{\"success\":false,\"message\":\"Update failed\"}");
return;
}
// Erste 100% Nachricht
ws.textAll("{\"type\":\"updateProgress\",\"progress\":100,\"status\":\"success\",\"message\":\"Update successful! Restarting device...\"}");
delay(2000); // Längerer Delay für die erste Nachricht
AsyncWebServerResponse *response = request->beginResponse(200, "application/json",
"{\"success\":true,\"message\":\"Update successful! Restarting device...\"}");
response->addHeader("Connection", "close");
request->send(response);
// Zweite 100% Nachricht zur Sicherheit
ws.textAll("{\"type\":\"updateProgress\",\"progress\":100,\"status\":\"success\",\"message\":\"Update successful! Restarting device...\"}");
delay(3000); // Noch längerer Delay vor dem Neustart
ESP.restart();
});
server.addHandler(updateHandler);
}
void setupWebserver(AsyncWebServer &server) {
// Deaktiviere alle Debug-Ausgaben
Serial.setDebugOutput(false);
@ -326,7 +195,7 @@ void setupWebserver(AsyncWebServer &server) {
// Route für about
server.on("/about", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /about erhalten");
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/index.html.gz", "text/html");
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/index.html.gz", "text/html");
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
@ -335,7 +204,7 @@ void setupWebserver(AsyncWebServer &server) {
// Route für Waage
server.on("/waage", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /waage erhalten");
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/waage.html.gz", "text/html");
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/waage.html.gz", "text/html");
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
@ -344,7 +213,7 @@ void setupWebserver(AsyncWebServer &server) {
// Route für RFID
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /rfid erhalten");
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/rfid.html.gz", "text/html");
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/rfid.html.gz", "text/html");
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
@ -360,7 +229,7 @@ void setupWebserver(AsyncWebServer &server) {
// Route für WiFi
server.on("/wifi", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /wifi erhalten");
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/wifi.html.gz", "text/html");
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/wifi.html.gz", "text/html");
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
@ -370,7 +239,10 @@ void setupWebserver(AsyncWebServer &server) {
server.on("/spoolman", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /spoolman erhalten");
String html = loadHtmlWithHeader("/spoolman.html");
html.replace("{{spoolmanUrl}}", spoolmanUrl);
html.replace("{{spoolmanUrl}}", (spoolmanUrl != "") ? spoolmanUrl : "");
html.replace("{{spoolmanOctoEnabled}}", octoEnabled ? "checked" : "");
html.replace("{{spoolmanOctoUrl}}", (octoUrl != "") ? octoUrl : "");
html.replace("{{spoolmanOctoToken}}", (octoToken != "") ? octoToken : "");
JsonDocument doc;
if (loadJsonValue("/bambu_credentials.json", doc) && doc["bambu_ip"].is<String>())
@ -387,6 +259,7 @@ void setupWebserver(AsyncWebServer &server) {
html.replace("{{bambuSerial}}", bambuSerial ? bambuSerial : "");
html.replace("{{bambuCode}}", bambuCode ? bambuCode : "");
html.replace("{{autoSendToBambu}}", autoSendToBambu ? "checked" : "");
html.replace("{{autoSendTime}}", String(autoSetBambuAmsCounter));
}
else
{
@ -394,6 +267,7 @@ void setupWebserver(AsyncWebServer &server) {
html.replace("{{bambuSerial}}", "");
html.replace("{{bambuCode}}", "");
html.replace("{{autoSendToBambu}}", "");
html.replace("{{autoSendTime}}", String(autoSetBambuAmsCounter));
}
request->send(200, "text/html", html);
@ -406,10 +280,21 @@ void setupWebserver(AsyncWebServer &server) {
return;
}
if (request->getParam("octoEnabled")->value() == "true" && (!request->hasParam("octoUrl") || !request->hasParam("octoToken"))) {
request->send(400, "application/json", "{\"success\": false, \"error\": \"Missing OctoPrint URL or Token parameter\"}");
return;
}
String url = request->getParam("url")->value();
bool octoEnabled = (request->getParam("octoEnabled")->value() == "true") ? true : false;
String octoUrl = request->getParam("octoUrl")->value();
String octoToken = (request->getParam("octoToken")->value() != "") ? request->getParam("octoToken")->value() : "";
url.trim();
octoUrl.trim();
octoToken.trim();
bool healthy = saveSpoolmanUrl(url);
bool healthy = saveSpoolmanUrl(url, octoEnabled, octoUrl, octoToken);
String jsonResponse = "{\"healthy\": " + String(healthy ? "true" : "false") + "}";
request->send(200, "application/json", jsonResponse);
@ -426,17 +311,19 @@ void setupWebserver(AsyncWebServer &server) {
String bambu_serialnr = request->getParam("bambu_serialnr")->value();
String bambu_accesscode = request->getParam("bambu_accesscode")->value();
bool autoSend = (request->getParam("autoSend")->value() == "true") ? true : false;
Serial.println(autoSend);
String autoSendTime = request->getParam("autoSendTime")->value();
bambu_ip.trim();
bambu_serialnr.trim();
bambu_accesscode.trim();
autoSendTime.trim();
if (bambu_ip.length() == 0 || bambu_serialnr.length() == 0 || bambu_accesscode.length() == 0) {
request->send(400, "application/json", "{\"success\": false, \"error\": \"Empty parameter\"}");
return;
}
bool success = saveBambuCredentials(bambu_ip, bambu_serialnr, bambu_accesscode, autoSend);
bool success = saveBambuCredentials(bambu_ip, bambu_serialnr, bambu_accesscode, autoSend, autoSendTime);
request->send(200, "application/json", "{\"healthy\": " + String(success ? "true" : "false") + "}");
});
@ -449,7 +336,7 @@ void setupWebserver(AsyncWebServer &server) {
// Route für das Laden der CSS-Datei
server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Lade style.css");
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/style.css.gz", "text/css");
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/style.css.gz", "text/css");
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
@ -458,7 +345,7 @@ void setupWebserver(AsyncWebServer &server) {
// Route für das Logo
server.on("/logo.png", HTTP_GET, [](AsyncWebServerRequest *request){
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/logo.png.gz", "image/png");
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/logo.png.gz", "image/png");
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
@ -467,7 +354,7 @@ void setupWebserver(AsyncWebServer &server) {
// Route für Favicon
server.on("/favicon.ico", HTTP_GET, [](AsyncWebServerRequest *request){
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/favicon.ico", "image/x-icon");
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/favicon.ico", "image/x-icon");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
Serial.println("favicon.ico gesendet");
@ -475,17 +362,26 @@ void setupWebserver(AsyncWebServer &server) {
// Route für spool_in.png
server.on("/spool_in.png", HTTP_GET, [](AsyncWebServerRequest *request){
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/spool_in.png.gz", "image/png");
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/spool_in.png.gz", "image/png");
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
Serial.println("spool_in.png gesendet");
});
// Route für set_spoolman.png
server.on("/set_spoolman.png", HTTP_GET, [](AsyncWebServerRequest *request){
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/set_spoolman.png.gz", "image/png");
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
Serial.println("set_spoolman.png gesendet");
});
// Route für JavaScript Dateien
server.on("/spoolman.js", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /spoolman.js erhalten");
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/spoolman.js.gz", "text/javascript");
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/spoolman.js.gz", "text/javascript");
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
@ -494,7 +390,7 @@ void setupWebserver(AsyncWebServer &server) {
server.on("/rfid.js", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /rfid.js erhalten");
AsyncWebServerResponse *response = request->beginResponse(SPIFFS,"/rfid.js.gz", "text/javascript");
AsyncWebServerResponse *response = request->beginResponse(LittleFS,"/rfid.js.gz", "text/javascript");
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
@ -503,7 +399,7 @@ void setupWebserver(AsyncWebServer &server) {
// Vereinfachter Update-Handler
server.on("/upgrade", HTTP_GET, [](AsyncWebServerRequest *request) {
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/upgrade.html.gz", "text/html");
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/upgrade.html.gz", "text/html");
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", "no-store");
request->send(response);
@ -534,50 +430,3 @@ void setupWebserver(AsyncWebServer &server) {
server.begin();
Serial.println("Webserver gestartet");
}
void backupJsonConfigs() {
// Bambu Credentials backup
if (SPIFFS.exists("/bambu_credentials.json")) {
File file = SPIFFS.open("/bambu_credentials.json", "r");
if (file) {
bambuCredentialsBackup = file.readString();
file.close();
Serial.println("Bambu credentials backed up");
}
}
// Spoolman URL backup
if (SPIFFS.exists("/spoolman_url.json")) {
File file = SPIFFS.open("/spoolman_url.json", "r");
if (file) {
spoolmanUrlBackup = file.readString();
file.close();
Serial.println("Spoolman URL backed up");
}
}
}
void restoreJsonConfigs() {
// Restore Bambu credentials
if (bambuCredentialsBackup.length() > 0) {
File file = SPIFFS.open("/bambu_credentials.json", "w");
if (file) {
file.print(bambuCredentialsBackup);
file.close();
Serial.println("Bambu credentials restored");
}
bambuCredentialsBackup = ""; // Clear backup
}
// Restore Spoolman URL
if (spoolmanUrlBackup.length() > 0) {
File file = SPIFFS.open("/spoolman_url.json", "w");
if (file) {
file.print(spoolmanUrlBackup);
file.close();
Serial.println("Spoolman URL restored");
}
spoolmanUrlBackup = ""; // Clear backup
}
}

View File

@ -19,7 +19,6 @@ extern AsyncWebSocket ws;
// Server-Initialisierung und Handler
void initWebServer();
void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total);
void setupWebserver(AsyncWebServer &server);
@ -29,8 +28,4 @@ void sendNfcData(AsyncWebSocketClient *client);
void foundNfcTag(AsyncWebSocketClient *client, uint8_t success);
void sendWriteResult(AsyncWebSocketClient *client, uint8_t success);
// Upgrade-Funktionen
void backupJsonConfigs();
void restoreJsonConfigs();
#endif

View File

@ -3,16 +3,20 @@
#include <WiFi.h>
#include <esp_wifi.h>
#include <WiFiManager.h>
#include <DNSServer.h>
#include <ESPmDNS.h>
#include "display.h"
#include "config.h"
WiFiManager wm;
bool wm_nonblocking = false;
uint8_t wifiErrorCounter = 0;
void initWiFi() {
void wifiSettings() {
// Optimierte WiFi-Einstellungen
WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP
WiFi.setSleep(false); // disable sleep mode
WiFi.setHostname("FilaMan");
esp_wifi_set_ps(WIFI_PS_NONE);
// Maximale Sendeleistung
@ -23,33 +27,103 @@ void initWiFi() {
// Aktiviere WiFi-Roaming für bessere Stabilität
esp_wifi_set_rssi_threshold(-80);
if(wm_nonblocking) wm.setConfigPortalBlocking(false);
wm.setConfigPortalTimeout(320); // Portal nach 5min schließen
oledShowTopRow();
oledShowMessage("WiFi Setup");
bool res;
// res = wm.autoConnect(); // auto generated AP name from chipid
res = wm.autoConnect("FilaMan"); // anonymous ap
// res = wm.autoConnect("spoolman","password"); // password protected ap
if(!res) {
Serial.println("Failed to connect or hit timeout");
// ESP.restart();
oledShowTopRow();
oledShowMessage("WiFi not connected Check Portal");
}
else {
wifiOn = true;
//if you get here you have connected to the WiFi
Serial.println("connected...yeey :)");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
oledShowTopRow();
display.display();
}
void startMDNS() {
if (!MDNS.begin("filaman")) {
Serial.println("Error setting up MDNS responder!");
while(1) {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
}
Serial.println("mDNS responder started");
}
void configModeCallback (WiFiManager *myWiFiManager) {
Serial.println("Entered config mode");
oledShowTopRow();
oledShowMessage("WiFi Config Mode");
}
void initWiFi() {
// load Wifi settings
wifiSettings();
wm.setAPCallback(configModeCallback);
wm.setSaveConfigCallback([]() {
Serial.println("Configurations updated");
ESP.restart();
});
if(wm_nonblocking) wm.setConfigPortalBlocking(false);
//wm.setConfigPortalTimeout(320); // Portal nach 5min schließen
wm.setWiFiAutoReconnect(true);
wm.setConnectTimeout(5);
oledShowTopRow();
oledShowMessage("WiFi Setup");
//bool res = wm.autoConnect("FilaMan"); // anonymous ap
if(!wm.autoConnect("FilaMan")) {
Serial.println("Failed to connect or hit timeout");
// ESP.restart();
oledShowTopRow();
oledShowMessage("WiFi not connected Check Portal");
}
else {
wifiOn = true;
//if you get here you have connected to the WiFi
Serial.println("connected...yeey :)");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
oledShowTopRow();
display.display();
vTaskDelay(500 / portTICK_PERIOD_MS);
// mDNS
startMDNS();
}
}
void checkWiFiConnection() {
if (WiFi.status() != WL_CONNECTED)
{
Serial.println("WiFi connection lost. Reconnecting...");
wifiOn = false;
oledShowTopRow();
oledShowMessage("WiFi reconnecting");
WiFi.reconnect(); // Versuche, die Verbindung wiederherzustellen
vTaskDelay(5000 / portTICK_PERIOD_MS); // Warte 5 Sekunden, bevor erneut geprüft wird
if (WiFi.status() != WL_CONNECTED)
{
Serial.println("Failed to reconnect. Restarting WiFi...");
WiFi.disconnect();
Serial.println("WiFi disconnected.");
vTaskDelay(1000 / portTICK_PERIOD_MS);
wifiErrorCounter++;
//wifiSettings();
WiFi.reconnect();
Serial.println("WiFi reconnecting...");
WiFi.waitForConnectResult();
}
else
{
Serial.println("WiFi reconnected.");
wifiErrorCounter = 0;
wifiOn = true;
oledShowTopRow();
startMDNS();
}
}
if (wifiErrorCounter >= 5)
{
Serial.println("Too many WiFi errors. Restarting...");
ESP.restart();
}
}

View File

@ -4,5 +4,6 @@
#include <Arduino.h>
void initWiFi();
void checkWiFiConnection();
#endif