Compare commits
514 Commits
v1.3.98
...
v2.0.0-bet
Author | SHA1 | Date | |
---|---|---|---|
6800c88bb2 | |||
6172242f24 | |||
7f4b3b8d90 | |||
7a15424bc7 | |||
039a29fa3c | |||
6cccf3d603 | |||
693ee839e5 | |||
0bf383ecd9 | |||
6451d91c59 | |||
8d82e221b5 | |||
bf63ecd594 | |||
0daa3a148b | |||
602642c203 | |||
458bd2e67b | |||
e6a5cb29a9 | |||
6502bb7185 | |||
63fafa2463 | |||
f664e85933 | |||
7bf9868d79 | |||
b9e488d675 | |||
2e3fc19741 | |||
4d84169b29 | |||
10aeb9bc52 | |||
00b9bc08af | |||
dfe9e4dbe9 | |||
79eacae225 | |||
d5d7358f58 | |||
9b362b3c73 | |||
bc51956793 | |||
5666a58da2 | |||
a35f15eca5 | |||
f28b34e427 | |||
9215560558 | |||
7f6bce1699 | |||
2a4f8bb679 | |||
480e2da23e | |||
ba22602767 | |||
b2c68d5aac | |||
52a7f6b5b6 | |||
4cce9f8d5d | |||
f0eced8585 | |||
02e31878ee | |||
7ff499f984 | |||
fcd637cc30 | |||
587485d0de | |||
e0cc99e993 | |||
d9a8388ac7 | |||
cb77112976 | |||
1c0ddb52ba | |||
17f03e9472 | |||
213b9c099c | |||
687e57b77a | |||
aea11e0c06 | |||
bd8f4606c6 | |||
ac91e71c14 | |||
0d3503f4f1 | |||
1460c6e5f9 | |||
fef7e5aa4b | |||
bda8c3dd98 | |||
8702469020 | |||
2a0f999f3b | |||
c89adb6256 | |||
1f21954703 | |||
3e59ce1366 | |||
1f880fc8f1 | |||
69bf5f90fa | |||
382caeaced | |||
47bdf022ec | |||
02febfa943 | |||
257f4df800 | |||
bff6e72219 | |||
26e905050d | |||
046f770a52 | |||
2587227e78 | |||
0f19dc4f46 | |||
721dac1ead | |||
08abd1a37f | |||
da78861613 | |||
9231a303f3 | |||
d12e766cd7 | |||
af7bc23703 | |||
de39892f64 | |||
40cb835e51 | |||
eb9d9e74f4 | |||
d8af3f45e5 | |||
96bb8f9c7c | |||
b8b6893cd0 | |||
0a246c1fe4 | |||
965ea5da1e | |||
b8b6f637f2 | |||
12044b657b | |||
95433b4842 | |||
54275f2ac9 | |||
fbd9cb66f1 | |||
f1cdd3f41d | |||
d897817020 | |||
686eb22232 | |||
a2816da654 | |||
cc8f1cfd7b | |||
d195f76d5e | |||
6bed3b086c | |||
3dd4b82710 | |||
bc41205f15 | |||
f450d1efdf | |||
6e94092a74 | |||
ece510099e | |||
1f01af4da9 | |||
c5d24d5972 | |||
48556b9519 | |||
2ac8effe04 | |||
4e58407af8 | |||
d776956c5e | |||
25233f70d5 | |||
b4584364d6 | |||
33ea062773 | |||
771b0a4839 | |||
c48003e1b2 | |||
83dec4c876 | |||
dca9ef8d08 | |||
513e02b867 | |||
99babe2b4a | |||
c17ab2c434 | |||
ec7386922e | |||
1eb81fad5d | |||
9d406e3428 | |||
5c2db22a90 | |||
164c7b2af5 | |||
cd1c93c485 | |||
15219fa1e4 | |||
206db69e6d | |||
9e67af7343 | |||
9e58b042c8 | |||
55200d31cd | |||
65967ca047 | |||
86e5f7e48a | |||
e4d1ba6c1c | |||
7ccdde8489 | |||
88598611c5 | |||
619979ab14 | |||
377f4bc146 | |||
174c48f734 | |||
7cbd34bc91 | |||
fdeb6d5b61 | |||
f7484f635e | |||
fb7dca38f0 | |||
90ce30215f | |||
|
69ae5cab5f | ||
|
5fa93f2695 | ||
|
0e00fd8b91 | ||
|
4706152022 | ||
|
accb02ab80 | ||
|
5509d98969 | ||
|
d7ee52ba1f | ||
|
a7c99d3f26 | ||
|
0a02912e4a | ||
|
89a5728cc0 | ||
|
f133a1b321 | ||
|
b95497aec2 | ||
876e9c62d8 | |||
765cb5319d | |||
9a9ed175dd | |||
a156cac18e | |||
|
5b04c2eb80 | ||
|
09f4c43f89 | ||
|
b94db80321 | ||
|
ec0e544f30 | ||
|
d815733550 | ||
|
b6d82c8afe | ||
|
afef544c66 | ||
|
97a1368747 | ||
|
6b6aec07b3 | ||
|
85a9bcf8bd | ||
|
852a2f4c69 | ||
|
c450df59aa | ||
|
4b81703e38 | ||
|
722ef421cb | ||
7ba0c4f933 | |||
b0cd731c5a | |||
d0b793a300 | |||
f022bee578 | |||
7c320a87fe | |||
3286b64836 | |||
0777b6371d | |||
739fe7e764 | |||
fcdf91071c | |||
5f8953a19d | |||
ffb1117150 | |||
c919eeb848 | |||
c317610229 | |||
43177c670e | |||
73c3457f40 | |||
1b50694f5f | |||
cf62e12aa4 | |||
48edde8557 | |||
|
b583ef71ad | ||
|
cb5d8ac10a | ||
|
b991f2ee27 | ||
|
bf48c6d4e1 | ||
|
e2e0a23f0a | ||
|
5d2d5e9ee1 | ||
|
537f452601 | ||
|
7e76612bb4 | ||
|
faaffee391 | ||
|
f038020042 | ||
|
d536181a73 | ||
|
8343fe887b | ||
e38220739d | |||
3bb6c1caf5 | |||
fc48d6e67c | |||
37df07f102 | |||
aeb61ba462 | |||
8484c1310b | |||
|
7f25f3e14f | ||
|
fd7b4c25b3 | ||
150a178038 | |||
d490b116b9 | |||
8b43f34a86 | |||
5bc6192b6f | |||
7a85ce6a04 | |||
2202d9a1aa | |||
68fa1e77a1 | |||
7dbca0ab87 | |||
9c06fe6725 | |||
24b3521f83 | |||
1cf392c1cd | |||
6c9f290bac | |||
|
69d6ba4bcb | ||
|
eab937d6ca | ||
|
21ec4e0ff3 | ||
|
27ef8399e4 | ||
c2a09b21a0 | |||
2920159f32 | |||
0937a9e9f0 | |||
2e19bccfa9 | |||
818b8387c0 | |||
859e89431e | |||
3f2beb6f54 | |||
6dc26ca51f | |||
56248ff2cb | |||
0becae7ed6 | |||
6a4945666e | |||
3d31833f50 | |||
97d1519489 | |||
599cc47443 | |||
f608c4a19b | |||
b1f7923770 | |||
aa2eb91d64 | |||
c78c20979d | |||
35d2445c6c | |||
e79c522e46 | |||
537607ed40 | |||
cf8cce72a5 | |||
7e330dca1a | |||
0b356609d1 | |||
d943d15c0a | |||
01f1e123ac | |||
a345b76cd2 | |||
012f91851e | |||
836e48bde2 | |||
9ed3c70c01 | |||
a6a8c69aee | |||
e23f3a2151 | |||
ddb4cd8e53 | |||
f73306f0b9 | |||
d45313a3ff | |||
a450d4bd1a | |||
70350e19f8 | |||
d48d994c00 | |||
7613effccf | |||
32bb85f897 | |||
7280d5be7f | |||
e9d32ee060 | |||
ada4a84942 | |||
aba28422bd | |||
e32aa6ec51 | |||
4a55620d39 | |||
04a18469b5 | |||
7b18266534 | |||
1c4d5f3874 | |||
d81acb2b61 | |||
a2eb57cd7a | |||
8c7fc159d3 | |||
1c619c5bcb | |||
476d3e82e2 | |||
2e05651f88 | |||
3c294a135f | |||
f1b803a3c1 | |||
bb751b6289 | |||
5c4ba9f0ba | |||
7fd01bd1b9 | |||
19d70301f5 | |||
fad84e12c8 | |||
4fa21d3c0e | |||
696efc4d79 | |||
f22a01127c | |||
29868e7101 | |||
92d377713d | |||
823db6157c | |||
8732c81bb9 | |||
458cc4eaf2 | |||
e7bbf45a9f | |||
83d14b32d1 | |||
a8ce964add | |||
2bf7c9fb7d | |||
|
69f01d1e57 | ||
|
ac8adca84d | ||
|
99231786a5 | ||
|
c701149c64 | ||
8536b4f8fa | |||
07a919b6ba | |||
c84c5fa734 | |||
8618b90e33 | |||
2a60e149b9 | |||
57723b5354 | |||
7e486191b7 | |||
d2be752175 | |||
610479bc5a | |||
97a050ace8 | |||
b7fa53da7e | |||
367e692c74 | |||
629b4276cf | |||
926a21249b | |||
cb15dae87e | |||
2635c19667 | |||
|
abed1c9806 | ||
|
6cc4efca0a | ||
db1f33c2b6 | |||
1484a6b0da | |||
174a58906c | |||
b5f0472af4 | |||
20cc9b196b | |||
95c1bc823c | |||
ff80b05502 | |||
491ba7f526 | |||
edfdef53f4 | |||
56d7d8596c | |||
89a3fed7a9 | |||
1044e91a0a | |||
f44173824f | |||
e459b53472 | |||
169d73bfc0 | |||
024056cb7d | |||
c78f36d21a | |||
e040a736b0 | |||
054bc43f65 | |||
72b6b349c6 | |||
31c41576ee | |||
190e952ec4 | |||
3a744bc1e6 | |||
89620a7f00 | |||
42f76fc20a | |||
536950eeb3 | |||
|
124f326670 | ||
|
fe4d2d7479 | ||
af34ce45dd | |||
43719aac41 | |||
c0cb3ff5c9 | |||
16d0079f7a | |||
4c754d84ff | |||
48b9bf7076 | |||
d2c85018f5 | |||
b6bd4cb9ad | |||
8dac49ea9e | |||
e89bb1d547 | |||
5365c0e1b9 | |||
f25789d703 | |||
|
4abe9d6d33 | ||
|
65d8cd675f | ||
e5d0334714 | |||
9dfe75ffa2 | |||
16364cbd86 | |||
68cdd8ab40 | |||
1b63ab668f | |||
1069781931 | |||
f67ef8e905 | |||
eada54eff2 | |||
a490b77860 | |||
48301ade36 | |||
52d063b619 | |||
76e0b20393 | |||
d5c005d6f7 | |||
a765b39896 | |||
68866f1632 | |||
d68f6c4a89 | |||
a4200e469d | |||
1702e2396e | |||
e5e14dfc99 | |||
af23b07df1 | |||
863d591a17 | |||
dd7ba3bf5d | |||
69675f3c06 | |||
a818dcd3c0 | |||
2ae3df1aab | |||
b5279b167a | |||
3910da9fb5 | |||
a09fd4fda4 | |||
26d53929ac | |||
e4fe08f54c | |||
64e3461264 | |||
3eac0e5ac4 | |||
bc04db91b8 | |||
24d91693d9 | |||
f500f8bd11 | |||
94c26590c8 | |||
84391faffd | |||
4559bae066 | |||
aae93de7dd | |||
cdb2d16cf9 | |||
0f847a2731 | |||
cd71949c82 | |||
aa7fc7e64b | |||
6cd280389d | |||
|
e0f5f48cc4 | ||
|
daf27820b1 | ||
|
0b79891f83 | ||
|
dd7fbe1119 | ||
|
11c5ca3383 | ||
|
dc2ddb47eb | ||
|
e3c3b3f42d | ||
|
6bb8f565e6 | ||
|
8db7765e7e | ||
|
ec60ca88f1 | ||
|
dc97740ddc | ||
|
17664acf9e | ||
|
ababe8b842 | ||
|
18f7454a76 | ||
|
62bcbb2ae8 | ||
|
e7b5917888 | ||
|
62330a3fd8 | ||
|
5c57968ba9 | ||
|
4556730c6e | ||
|
795c926c1f | ||
|
c92a8b0957 | ||
|
8735a9740c | ||
|
b08da071c2 | ||
|
02d0adc6bf | ||
|
9c949e74e8 | ||
|
24067666ed | ||
|
17fcf765fd | ||
|
9264333eda | ||
|
95a03f92e2 | ||
|
66216d57ae | ||
|
d9e69d8c14 | ||
|
5100a669b0 | ||
|
1ec09ebf3a | ||
|
4ad89b68a7 | ||
|
7ef0cc44d5 | ||
|
758acaff9f | ||
|
fe962b2bfa | ||
|
fed96b9c58 | ||
|
aec07f3c6d | ||
|
2d072ee09a | ||
|
b5cb5b17ea | ||
|
b55b6e3fd5 | ||
|
c3e7758920 | ||
|
238b928236 | ||
|
66395028a6 | ||
|
24ce0ca6df | ||
|
64403b9599 | ||
|
3cf934b920 | ||
|
ebf6688701 | ||
|
f68ea3edb0 | ||
|
073a5f4539 | ||
|
16321c9461 | ||
|
69bd5c3eb2 | ||
|
f9530f6d9a | ||
a328fbc6a6 | |||
83f2f0834d | |||
6f52cd1686 | |||
6632aa8f95 | |||
c1122ad87d | |||
8a558c3121 | |||
|
1aeced76a2 | ||
|
d434fde92e | ||
967ec35c6a | |||
5afb60df32 | |||
f60113aa83 | |||
3394e6eb01 | |||
63a7398979 | |||
3818c2c059 | |||
40cb504251 | |||
0afc543b5f | |||
41a4f8af4a | |||
adee46e3fc | |||
e122224472 | |||
1db74867e6 | |||
5d3a8d971f | |||
0f24a63d32 | |||
e62e5e7062 | |||
3640809502 | |||
726a60882d | |||
289d5357be | |||
2918b4ca77 | |||
315530d1ea | |||
955ba0f001 | |||
f36773a4c4 | |||
8cf7dc0b77 | |||
b35163936f | |||
33e4b371ed | |||
7a2c9d6d17 | |||
fd832d8808 | |||
eb2a8dc128 | |||
c0e213a4ac | |||
bec2c91331 | |||
bcc00f711b | |||
c6e727de06 | |||
78f336d5d7 | |||
3253e7d407 | |||
ee7f8ff517 | |||
bce2ad2ed8 | |||
|
0eff29ef4a | ||
492bf6cdb8 | |||
b0317f4001 | |||
58ff6458b0 |
10
.github/workflows/gitea-release.yml
vendored
@@ -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:"
|
||||
|
12
.github/workflows/github-release.yml
vendored
@@ -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
|
||||
|
42
.gitignore
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
.pio
|
||||
.vscode/
|
||||
.aider*
|
||||
.DS_Store
|
||||
._*
|
||||
**/.DS_Store
|
||||
**/.Spotlight-V100
|
||||
**/.Trashes
|
||||
**/.fseventsd
|
||||
.AppleDouble
|
||||
**/.DS_Store
|
||||
**/.Spotlight-V100
|
||||
**/.Trashes
|
||||
**/.fseventsd
|
||||
.AppleDouble
|
||||
.aider.chat.history.md
|
||||
.aider.input.history
|
||||
.DS_Store
|
||||
.gitignore
|
||||
.aider.tags.cache.v3/cache.db
|
||||
.aider.tags.cache.v3/cache.db-shm
|
||||
.aider.tags.cache.v3/cache.db-wal
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
.vscode/extensions.json
|
||||
.vscode/launch.json
|
||||
include/README
|
||||
lib/README
|
||||
test/README
|
||||
.aider*
|
||||
data/*
|
||||
!data/
|
||||
!data/.gitkeep
|
||||
# important
|
||||
html/bambu_credentials.json
|
||||
html/spoolman_url.json
|
||||
_local/*
|
||||
website/*
|
||||
release.sh
|
||||
.github/copilot-instructions.md
|
||||
data
|
1632
CHANGELOG.md
73
README.de.md
@@ -8,7 +8,8 @@ Das System integriert sich nahtlos mit der [Spoolman](https://github.com/Donkie/
|
||||
|
||||
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/my7Gvaxj2v](https://discord.gg/my7Gvaxj2v)
|
||||
|
||||
### Es gibt jetzt auch ein Wiki, dort sind nochmal alle Funktionen beschrieben: [Wiki](https://github.com/ManuelW77/Filaman/wiki)
|
||||
|
||||
@@ -18,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.
|
||||
@@ -26,6 +27,7 @@ Deutsches Erklärvideo: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaO
|
||||
- Filamentdaten auf NFC-Tags schreiben.
|
||||
- Verwendet das NFC-Tag-Format von [Openspool](https://github.com/spuder/OpenSpool)
|
||||
- Ermöglicht automatische Spulenerkennung im AMS
|
||||
- **Hersteller Tag Unterstützung:** Automatische Erstellung von Spoolman-Einträgen aus Hersteller NFC-Tags ([Mehr erfahren](README_ManufacturerTags_DE.md))
|
||||
- **Bambulab AMS-Integration:**
|
||||
- Anzeige der aktuellen AMS-Fachbelegung.
|
||||
- Zuordnung von Filamenten zu AMS-Slots.
|
||||
@@ -35,10 +37,38 @@ 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>
|
||||
|
||||
## Hersteller Tags Unterstützung
|
||||
|
||||
🎉 **Aufregende Neuigkeiten!** FilaMan unterstützt jetzt **Hersteller Tags** - NFC-Tags, die direkt von Filament-Herstellern vorprogrammiert geliefert werden!
|
||||
|
||||
### Erster Hersteller-Partner: RecyclingFabrik
|
||||
|
||||
Wir freuen uns anzukündigen, dass [**RecyclingFabrik**](https://www.recyclingfabrik.de) der **erste Filament-Hersteller** sein wird, der FilaMan unterstützt, indem sie NFC-Tags im FilaMan-Format auf ihren Spulen anbieten!
|
||||
|
||||
**Demnächst verfügbar:** RecyclingFabrik-Spulen werden NFC-Tags enthalten, die sich automatisch in Ihr FilaMan-System integrieren, manuelle Einrichtung überflüssig machen und perfekte Kompatibilität gewährleisten.
|
||||
|
||||
### Wie Hersteller Tags funktionieren
|
||||
|
||||
Wenn Sie zum ersten Mal einen Hersteller NFC-Tag scannen:
|
||||
1. **Automatische Markenerkennung:** FilaMan erkennt den Hersteller und erstellt die Marke in Spoolman
|
||||
2. **Filament-Typ Erstellung:** Alle Materialspezifikationen werden automatisch hinzugefügt
|
||||
3. **Spulen-Registrierung:** Ihre spezifische Spule wird mit korrektem Gewicht und Spezifikationen registriert
|
||||
4. **Zukünftige Schnellerkennung:** Nachfolgende Scans verwenden Fast-Path-Erkennung für sofortige Gewichtsmessung
|
||||
|
||||
**Für detaillierte technische Informationen:** [Hersteller Tags Dokumentation](README_ManufacturerTags_DE.md)
|
||||
|
||||
### Vorteile für Benutzer
|
||||
- ✅ **Null manuelle Einrichtung** - Einfach scannen und wiegen
|
||||
- ✅ **Perfekte Datengenauigkeit** - Hersteller-verifizierte Spezifikationen
|
||||
- ✅ **Sofortige Integration** - Nahtlose Spoolman-Kompatibilität
|
||||
- ✅ **Zukunftssicher** - Tags funktionieren mit jedem FilaMan-kompatiblen System
|
||||
|
||||
## Detaillierte Funktionalität
|
||||
|
||||
### ESP32-Funktionalität
|
||||
@@ -52,20 +82,23 @@ Deutsches Erklärvideo: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaO
|
||||
|
||||
## Hardware-Anforderungen
|
||||
|
||||
### Komponenten
|
||||
- **ESP32 Entwicklungsboard:** Jede ESP32-Variante.
|
||||
[Amazon Link](https://amzn.eu/d/aXThslf)
|
||||
- **HX711 5kg Wägezellen-Verstärker:** Für Gewichtsmessung.
|
||||
[Amazon Link](https://amzn.eu/d/06A0DLb)
|
||||
- **OLED 0.96 Zoll I2C weiß/gelb Display:** 128x64 SSD1306.
|
||||
[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
|
||||
[Amazon Link](https://amzn.eu/d/9Z6mXc1)
|
||||
### Komponenten (Affiliate Links)
|
||||
- **ESP32 Development Board:** Any ESP32 variant.
|
||||
[Amazon Link](https://amzn.to/3FHea6D)
|
||||
- **HX711 5kg Load Cell Amplifier:** For weight measurement.
|
||||
[Amazon Link](https://amzn.to/4ja1KTe)
|
||||
- **OLED 0.96 Zoll I2C white/yellow Display:** 128x64 SSD1306.
|
||||
[Amazon Link](https://amzn.to/445aaa9)
|
||||
- **PN532 NFC NXP RFID-Modul V3:** For NFC tag operations.
|
||||
[Amazon Link](https://amzn.eu/d/gy9vaBX)
|
||||
- **NFC Tags NTAG213 NTAG215:** RFID Tag
|
||||
[Amazon Link](https://amzn.to/3E071xO)
|
||||
- **TTP223 Touch Sensor (optional):** For reTARE per Button/Touch
|
||||
[Amazon Link](https://amzn.to/4hTChMK)
|
||||
|
||||
### Pin-Konfiguration
|
||||
| Komponente | ESP32 Pin |
|
||||
|
||||
### Pin Konfiguration
|
||||
| Component | ESP32 Pin |
|
||||
|-------------------|-----------|
|
||||
| HX711 DOUT | 16 |
|
||||
| HX711 SCK | 17 |
|
||||
@@ -75,14 +108,22 @@ Deutsches Erklärvideo: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaO
|
||||
| PN532 RESET | 33 |
|
||||
| PN532 SDA | 21 |
|
||||
| PN532 SCL | 22 |
|
||||
| TTP223 I/O | 25 |
|
||||
|
||||
**Achte darauf, dass am PN532 die DIP-Schalter auf I2C gestellt sind**
|
||||
**!! Achte darauf, dass am PN532 die DIP-Schalter auf I2C gestellt sind**
|
||||
**Nutze den 3V Pin vom ESP für den Touch Sensor**
|
||||
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
*Die Wägezelle wird bei den meisten HX711 Modulen folgendermaßen verkabelt:
|
||||
E+ rot
|
||||
E- schwarz
|
||||
A- weiß
|
||||
A+ grün*
|
||||
|
||||
## Software-Abhängigkeiten
|
||||
|
||||
### ESP32-Bibliotheken
|
||||
|
63
README.md
@@ -6,12 +6,14 @@ 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.
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
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/my7Gvaxj2v](https://discord.gg/my7Gvaxj2v)
|
||||
|
||||
### Now more detailed informations about the usage: [Wiki](https://github.com/ManuelW77/Filaman/wiki)
|
||||
|
||||
@@ -21,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.
|
||||
@@ -29,6 +31,7 @@ german explanatory video: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62z
|
||||
- Write filament data to NFC tags.
|
||||
- uses NFC-Tag Format of [Openspool](https://github.com/spuder/OpenSpool)
|
||||
- so you can use it with automatic Spool detection in AMS
|
||||
- **Manufacturer Tag Support:** Automatic creation of Spoolman entries from manufacturer NFC tags ([Learn more](README_ManufacturerTags_EN.md))
|
||||
- **Bambulab AMS Integration:**
|
||||
- Display current AMS tray contents.
|
||||
- Assign filaments to AMS slots.
|
||||
@@ -38,10 +41,38 @@ 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>
|
||||
|
||||
## Manufacturer Tags Support
|
||||
|
||||
🎉 **Exciting News!** FilaMan now supports **Manufacturer Tags** - NFC tags that come pre-programmed directly from filament manufacturers!
|
||||
|
||||
### First Manufacturer Partner: RecyclingFabrik
|
||||
|
||||
We're thrilled to announce that [**RecyclingFabrik**](https://www.recyclingfabrik.de) will be the **first filament manufacturer** to support FilaMan by offering NFC tags in the FilaMan format on their spools!
|
||||
|
||||
**Coming Soon:** RecyclingFabrik spools will include NFC tags that automatically integrate with your FilaMan system, eliminating manual setup and ensuring perfect compatibility.
|
||||
|
||||
### How Manufacturer Tags Work
|
||||
|
||||
When you scan a manufacturer NFC tag for the first time:
|
||||
1. **Automatic Brand Detection:** FilaMan recognizes the manufacturer and creates the brand in Spoolman
|
||||
2. **Filament Type Creation:** All material specifications are automatically added
|
||||
3. **Spool Registration:** Your specific spool is registered with proper weight and specifications
|
||||
4. **Future Fast Recognition:** Subsequent scans use fast-path detection for instant weight measurement
|
||||
|
||||
**For detailed technical information:** [Manufacturer Tags Documentation](README_ManufacturerTags_EN.md)
|
||||
|
||||
### Benefits for Users
|
||||
- ✅ **Zero Manual Setup** - Just scan and weigh
|
||||
- ✅ **Perfect Data Accuracy** - Manufacturer-verified specifications
|
||||
- ✅ **Instant Integration** - Seamless Spoolman compatibility
|
||||
- ✅ **Future-Proof** - Tags work with any FilaMan-compatible system
|
||||
|
||||
## Detailed Functionality
|
||||
|
||||
### ESP32 Functionality
|
||||
@@ -55,17 +86,19 @@ german explanatory video: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62z
|
||||
|
||||
## Hardware Requirements
|
||||
|
||||
### Components
|
||||
### Components (Affiliate Links)
|
||||
- **ESP32 Development Board:** Any ESP32 variant.
|
||||
[Amazon Link](https://amzn.eu/d/aXThslf)
|
||||
[Amazon Link](https://amzn.to/3FHea6D)
|
||||
- **HX711 5kg Load Cell Amplifier:** For weight measurement.
|
||||
[Amazon Link](https://amzn.eu/d/06A0DLb)
|
||||
[Amazon Link](https://amzn.to/4ja1KTe)
|
||||
- **OLED 0.96 Zoll I2C white/yellow Display:** 128x64 SSD1306.
|
||||
[Amazon Link](https://amzn.eu/d/0AuBp2c)
|
||||
[Amazon Link](https://amzn.to/445aaa9)
|
||||
- **PN532 NFC NXP RFID-Modul V3:** For NFC tag operations.
|
||||
[Amazon Link](https://amzn.eu/d/jfIuQXb)
|
||||
- **NFC Tags Ntag215:** RFID Tag
|
||||
[Amazon Link](https://amzn.eu/d/9Z6mXc1)
|
||||
[Amazon Link](https://amzn.eu/d/gy9vaBX)
|
||||
- **NFC Tags NTAG213 NTAG215:** RFID Tag
|
||||
[Amazon Link](https://amzn.to/3E071xO)
|
||||
- **TTP223 Touch Sensor (optional):** For reTARE per Button/Touch
|
||||
[Amazon Link](https://amzn.to/4hTChMK)
|
||||
|
||||
|
||||
### Pin Configuration
|
||||
@@ -79,14 +112,22 @@ german explanatory video: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62z
|
||||
| PN532 RESET | 33 |
|
||||
| PN532 SDA | 21 |
|
||||
| PN532 SCL | 22 |
|
||||
| TTP223 I/O | 25 |
|
||||
|
||||
**Make sure that the DIP switches on the PN532 are set to I2C**
|
||||
**!! Make sure that the DIP switches on the PN532 are set to I2C**
|
||||
**Use the 3V pin from the ESP for the touch sensor**
|
||||
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
*The load cell is connected to most HX711 modules as follows:
|
||||
E+ red
|
||||
E- black
|
||||
A- white
|
||||
A+ green*
|
||||
|
||||
## Software Dependencies
|
||||
|
||||
### ESP32 Libraries
|
||||
|
159
README_ManufacturerTags_DE.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# Hersteller Tags - Deutsche Dokumentation
|
||||
|
||||
## Überblick
|
||||
|
||||
Das FilaMan NFC-System unterstützt **Hersteller Tags**, die es Filament-Produzenten ermöglichen, standardisierte NFC-Tags für ihre Produkte zu erstellen. Beim Scannen dieser Tags werden automatisch die notwendigen Einträge in Spoolman (Marke, Filament-Typ und Spule) erstellt, ohne dass eine manuelle Einrichtung erforderlich ist.
|
||||
|
||||
## Funktionsweise der Hersteller Tags
|
||||
|
||||
### Ablauf
|
||||
|
||||
1. **Tag-Erkennung**: Wenn ein Tag ohne `sm_id` gescannt wird, prüft das System auf Hersteller Tag Format
|
||||
2. **Marken-Erstellung/Suche**: Das System sucht die Marke in Spoolman oder erstellt sie, falls sie nicht existiert
|
||||
3. **Filament-Typ-Erstellung/Suche**: Der Filament-Typ wird basierend auf Marke, Material und Spezifikationen erstellt oder gefunden
|
||||
4. **Spulen-Erstellung**: Ein neuer Spulen-Eintrag wird automatisch mit der Tag-UID als Referenz erstellt
|
||||
5. **Tag-Update**: Der Tag wird mit der neuen Spoolman Spulen-ID (`sm_id`) aktualisiert
|
||||
|
||||
### Warum Hersteller Tags verwenden?
|
||||
|
||||
- **Automatische Integration**: Keine manuelle Dateneingabe erforderlich
|
||||
- **Standardisiertes Format**: Konsistente Produktinformationen verschiedener Hersteller
|
||||
- **Lagerverwaltung**: Automatische Erstellung vollständiger Spoolman-Einträge
|
||||
- **Rückverfolgbarkeit**: Direkte Verbindung zwischen physischem Produkt und digitalem Inventar
|
||||
|
||||
## Tag-Format Spezifikation
|
||||
|
||||
### JSON-Struktur
|
||||
|
||||
Hersteller Tags müssen eine JSON-Payload mit spezifischen Feldern enthalten, die **kurze Schlüssel** verwenden, um die Tag-Größe zu minimieren:
|
||||
|
||||
```json
|
||||
{
|
||||
"b": "Marke/Hersteller Name",
|
||||
"an": "Artikelnummer",
|
||||
"t": "Filament Typ (PLA, PETG, etc)",
|
||||
"c": "Filament Farbe ohne # (FF5733)",
|
||||
"mc": "Optional Mehrfarben-Filament Farben ohne # (FF0000,00FF00,0000FF)",
|
||||
"mcd": "Optional Mehrfarben-Richtung als Wort (coaxial, longitudinal)",
|
||||
"cn": "Farbname (rot, Blaubeere, Arktisches Blau)",
|
||||
"et": "Extruder Temp als Zahl in C° (230)",
|
||||
"bt": "Bett Temp als Zahl in C° (60)",
|
||||
"di": "Durchmesser als Float (1.75)",
|
||||
"de": "Dichte als Float (1.24)",
|
||||
"sw": "Leeres Spulengewicht als Zahl in g (180)",
|
||||
"u": "URL zum Filament mit der Artikelnummer"
|
||||
}
|
||||
```
|
||||
|
||||
### Pflichtfelder
|
||||
|
||||
- **`b`** (brand): Hersteller/Markenname
|
||||
- **`an`** (article number): Eindeutige Produktkennung
|
||||
- **`t`** (type): Materialtyp (PLA, PETG, ABS, etc.)
|
||||
- **`c`** (color): Hex-Farbcode ohne #
|
||||
- **`cn`** (color name): Lesbare Farbbezeichnung
|
||||
- **`et`** (extruder temp): Empfohlene Extruder-Temperatur in Celsius
|
||||
- **`bt`** (bed temp): Empfohlene Bett-Temperatur in Celsius
|
||||
- **`di`** (diameter): Filamentdurchmesser in mm
|
||||
- **`de`** (density): Materialdichte in g/cm³
|
||||
- **`sw`** (spool weight): Leeres Spulengewicht in Gramm
|
||||
|
||||
### Optionale Felder
|
||||
|
||||
- **`mc`** (multicolor): Komma-getrennte Hex-Farben für Mehrfarben-Filamente
|
||||
- **`mcd`** (multicolor direction): Richtung für Mehrfarben (coaxial, longitudinal)
|
||||
- **`u`** (url): Produkt-URL mit direktem Link zum Artikel zB für Nachbestellung
|
||||
|
||||
### Beispiel Tag
|
||||
|
||||
```json
|
||||
{"b":"Recycling Fabrik","an":"FX1_PETG-S175-1000-DAEM00055","t":"PETG","c":"FF5733","cn":"Lebendiges Orange","et":"230","bt":"70","di":"1.75","de":"1.24","sw":"180","u":"https://www.recyclingfabrik.com/search?q="}
|
||||
```
|
||||
|
||||
## Implementierungsrichtlinien
|
||||
|
||||
### Für Hersteller
|
||||
|
||||
1. **Tag-Kodierung**: NDEF-Format mit MIME-Typ `application/json` verwenden
|
||||
2. **Datenminimierung**: Kompaktes JSON-Format für Tag-Größenbegrenzungen nutzen
|
||||
3. **Qualitätskontrolle**: Sicherstellen, dass alle Pflichtfelder vorhanden und korrekt formatiert sind
|
||||
4. **Testen**: Tags vor der Produktion mit dem FilaMan-System verifizieren
|
||||
|
||||
### Tag-Größe Überlegungen
|
||||
|
||||
- **NTAG213**: 144 Bytes Nutzerdaten (geeignet für einfache Tags)
|
||||
- **NTAG215**: 504 Bytes Nutzerdaten (empfohlen für umfassende Daten)
|
||||
- **NTAG216**: 888 Bytes Nutzerdaten (maximale Kompatibilität)
|
||||
|
||||
### Best Practices
|
||||
|
||||
- Markennamen über alle Produkte hinweg konsistent halten
|
||||
- Standardisierte Materialtypnamen verwenden (PLA, PETG, ABS, etc.)
|
||||
- Genaue Temperaturempfehlungen angeben
|
||||
- Aussagekräftige Farbnamen für bessere Benutzererfahrung verwenden
|
||||
- Tags vor Massenproduktion mit dem FilaMan-System testen
|
||||
|
||||
## System-Integration
|
||||
|
||||
### Spoolman Datenbankstruktur
|
||||
|
||||
Bei der Verarbeitung eines Hersteller Tags erstellt das System:
|
||||
|
||||
1. **Lieferanten-Eintrag**: Markeninformationen in der Spoolman Lieferanten-Datenbank
|
||||
2. **Filament-Eintrag**: Materialspezifikationen und Eigenschaften
|
||||
3. **Spulen-Eintrag**: Einzelne Spule mit Gewicht und NFC-Tag-Referenz
|
||||
|
||||
### Fast-Path Erkennung
|
||||
|
||||
Sobald ein Tag verarbeitet und mit `sm_id` aktualisiert wurde, nutzt er das Fast-Path-Erkennungssystem für schnelle nachfolgende Scans.
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### Häufige Probleme
|
||||
|
||||
- **Tag zu klein**: NTAG215 oder NTAG216 für größere JSON-Payloads verwenden
|
||||
- **Fehlende Felder**: Sicherstellen, dass alle Pflichtfelder vorhanden sind
|
||||
- **Ungültiges Format**: JSON-Syntax und Feldtypen überprüfen
|
||||
- **Spoolman-Verbindung**: Sicherstellen, dass FilaMan mit der Spoolman API verbinden kann
|
||||
|
||||
### Validierung
|
||||
|
||||
Das System validiert:
|
||||
|
||||
- JSON-Format Korrektheit
|
||||
- Vorhandensein der Pflichtfelder
|
||||
- Datentyp-Konformität
|
||||
- Tag-Größe Kompatibilität
|
||||
|
||||
## Technische Details
|
||||
|
||||
### Verarbeitungsalgorithmus
|
||||
|
||||
1. Tag-Scan erkennt kein `sm_id` Feld
|
||||
2. System prüft auf `b` (Marke) und `an` (Artikelnummer) Felder
|
||||
3. `checkVendor()` erstellt oder findet Marke in Spoolman
|
||||
4. `checkFilament()` erstellt oder findet Filament-Typ
|
||||
5. `createSpool()` erstellt neuen Spulen-Eintrag
|
||||
6. Tag wird mit neuer `sm_id` aktualisiert
|
||||
|
||||
### Fehlerbehandlung
|
||||
|
||||
- Graceful Fallback bei Netzwerkproblemen
|
||||
- Detaillierte Protokollierung für Debugging
|
||||
- Benutzer-Feedback bei fehlgeschlagenen Operationen
|
||||
- Wiederholungsmechanismen für temporäre Fehler
|
||||
|
||||
### Systemverhalten
|
||||
|
||||
#### Bei fehlendem sm_id:
|
||||
- System prüft auf `b` (brand) und `an` (artnr) Felder
|
||||
- Falls vorhanden → Hersteller Tag erkannt
|
||||
- Automatische Erstellung von Lieferant, Filament und Spule in Spoolman
|
||||
- Tag wird mit neuer `sm_id` beschrieben
|
||||
|
||||
#### Bei vorhandenem sm_id:
|
||||
- Fast-Path Erkennung für bekannte Spulen
|
||||
- Sofortige Gewichtsmessung ohne vollständige Tag-Analyse
|
||||
- Optimierte Performance für häufig verwendete Tags
|
||||
|
||||
Dieses System ermöglicht eine nahtlose Integration von Hersteller-Filamentprodukten in das FilaMan-Ökosystem unter Beibehaltung von Datenkonsistenz und Benutzererfahrung.
|
145
README_ManufacturerTags_EN.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# Manufacturer Tags - English Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
The FilaMan NFC system supports **Manufacturer Tags** that allow filament producers to create standardized NFC tags for their products. When scanned, these tags automatically create the necessary entries in Spoolman (brand, filament type, and spool) without requiring manual setup.
|
||||
|
||||
## How Manufacturer Tags Work
|
||||
|
||||
### Process Flow
|
||||
|
||||
1. **Tag Detection**: When a tag without `sm_id` is scanned, the system checks for manufacturer tag format
|
||||
2. **Brand Creation/Lookup**: The system searches for the brand in Spoolman or creates it if it doesn't exist
|
||||
3. **Filament Type Creation/Lookup**: The filament type is created or found based on brand, material, and specifications
|
||||
4. **Spool Creation**: A new spool entry is automatically created with the tag's UID as reference
|
||||
5. **Tag Update**: The tag is updated with the new Spoolman spool ID (`sm_id`)
|
||||
|
||||
### Why Use Manufacturer Tags?
|
||||
|
||||
- **Automatic Integration**: No manual data entry required
|
||||
- **Standardized Format**: Consistent product information across different manufacturers
|
||||
- **Inventory Management**: Automatic creation of complete Spoolman entries
|
||||
- **Traceability**: Direct link between physical product and digital inventory
|
||||
|
||||
## Tag Format Specification
|
||||
|
||||
### JSON Structure
|
||||
|
||||
Manufacturer tags must contain a JSON payload with specific fields using **short keys** to minimize tag size:
|
||||
|
||||
```json
|
||||
{
|
||||
"b": "Brand/Vendor Name",
|
||||
"an": "Article Number",
|
||||
"t": "Filament Type (PLA, PETG, etc)",
|
||||
"c": "Filament Color without # (FF5733)",
|
||||
"mc": "Optional Multicolor Filament Colors without # (FF0000,00FF00,0000FF)",
|
||||
"mcd": "Optional Multicolor Direction as Word (coaxial, longitudinal)",
|
||||
"cn": "Color Name (red, Blueberry, Arctic Blue)",
|
||||
"et": "Extruder Temp as Number in C° (230)",
|
||||
"bt": "Bed Temp as Number in C° (60)",
|
||||
"di": "Diameter as Float (1.75)",
|
||||
"de": "Density as Float (1.24)",
|
||||
"sw": "Empty Spool Weight as Number in g (180)",
|
||||
"u": "URL to get the Filament with the Article Number"
|
||||
}
|
||||
```
|
||||
|
||||
### Required Fields
|
||||
|
||||
- **`b`** (brand): Manufacturer/brand name
|
||||
- **`an`** (article number): Unique product identifier
|
||||
- **`t`** (type): Material type (PLA, PETG, ABS, etc.)
|
||||
- **`c`** (color): Hex color code without #
|
||||
- **`cn`** (color name): Human-readable color name
|
||||
- **`et`** (extruder temp): Recommended extruder temperature in Celsius
|
||||
- **`bt`** (bed temp): Recommended bed temperature in Celsius
|
||||
- **`di`** (diameter): Filament diameter in mm
|
||||
- **`de`** (density): Material density in g/cm³
|
||||
- **`sw`** (spool weight): Empty spool weight in grams
|
||||
|
||||
### Optional Fields
|
||||
|
||||
- **`mc`** (multicolor): Comma-separated hex colors for multicolor filaments
|
||||
- **`mcd`** (multicolor direction): Direction for multicolor (coaxial, longitudinal)
|
||||
- **`u`** (url): Product URL with direct link to the article e.g. for reordering
|
||||
|
||||
### Example Tag
|
||||
|
||||
```json
|
||||
{"b":"Recycling Fabrik","an":"FX1_PETG-S175-1000-DAEM00055","t":"PETG","c":"FF5733","cn":"Vibrant Orange","et":"230","bt":"70","di":"1.75","de":"1.24","sw":"180","u":"https://www.recyclingfabrik.com/search?q="}
|
||||
```
|
||||
|
||||
## Implementation Guidelines
|
||||
|
||||
### For Manufacturers
|
||||
|
||||
1. **Tag Encoding**: Use NDEF format with MIME type `application/json`
|
||||
2. **Data Minimization**: Use the compact JSON format to fit within tag size limits
|
||||
3. **Quality Control**: Ensure all required fields are present and correctly formatted
|
||||
4. **Testing**: Verify tags work with FilaMan system before production
|
||||
|
||||
### Tag Size Considerations
|
||||
|
||||
- **NTAG213**: 144 bytes user data (suitable for basic tags)
|
||||
- **NTAG215**: 504 bytes user data (recommended for comprehensive data)
|
||||
- **NTAG216**: 888 bytes user data (maximum compatibility)
|
||||
|
||||
### Best Practices
|
||||
|
||||
- Keep brand names consistent across all products
|
||||
- Use standardized material type names (PLA, PETG, ABS, etc.)
|
||||
- Provide accurate temperature recommendations
|
||||
- Include meaningful color names for user experience
|
||||
- Test tags with the FilaMan system before mass production
|
||||
|
||||
## System Integration
|
||||
|
||||
### Spoolman Database Structure
|
||||
|
||||
When a manufacturer tag is processed, the system creates:
|
||||
|
||||
1. **Vendor Entry**: Brand information in Spoolman vendor database
|
||||
2. **Filament Entry**: Material specifications and properties
|
||||
3. **Spool Entry**: Individual spool with weight and NFC tag reference
|
||||
|
||||
### Fast-Path Recognition
|
||||
|
||||
Once a tag is processed and updated with `sm_id`, it uses the fast-path recognition system for quick subsequent scans.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
- **Tag Too Small**: Use NTAG215 or NTAG216 for larger JSON payloads
|
||||
- **Missing Fields**: Ensure all required fields are present
|
||||
- **Invalid Format**: Verify JSON syntax and field types
|
||||
- **Spoolman Connection**: Ensure FilaMan can connect to Spoolman API
|
||||
|
||||
### Validation
|
||||
|
||||
The system validates:
|
||||
- JSON format correctness
|
||||
- Required field presence
|
||||
- Data type compliance
|
||||
- Tag size compatibility
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Processing Algorithm
|
||||
|
||||
1. Tag scan detects no `sm_id` field
|
||||
2. System checks for `b` (brand) and `an` (article number) fields
|
||||
3. `checkVendor()` creates or finds brand in Spoolman
|
||||
4. `checkFilament()` creates or finds filament type
|
||||
5. `createSpool()` creates new spool entry
|
||||
6. Tag is updated with new `sm_id`
|
||||
|
||||
### Error Handling
|
||||
|
||||
- Graceful fallback for network issues
|
||||
- Detailed logging for debugging
|
||||
- User feedback for failed operations
|
||||
- Retry mechanisms for temporary failures
|
||||
|
||||
This system enables seamless integration of manufacturer filament products into the FilaMan ecosystem while maintaining data consistency and user experience.
|
15297
_3D Print Files/FilaMan-Waage.step
Normal file
BIN
_3D Print Files/Filaman-Waage.f3z
Normal file
BIN
html/.DS_Store
vendored
Normal file
1
html/bambu_credentials.json
Normal file
@@ -0,0 +1 @@
|
||||
{"bambu_ip": "192.168.1.14", "bambu_accesscode": "22772584", "bambu_serialnr": "01P00C492600230","autoSendToBambu":true,"autoSendTime": 60}
|
@@ -139,17 +139,20 @@
|
||||
<p id="nfcInfo" class="nfc-status"></p>
|
||||
<button id="writeNfcButton" class="btn btn-primary hidden" onclick="writeNfcTag()">Write Tag</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rechte Spalte -->
|
||||
<div class="column">
|
||||
<div class="feature-box">
|
||||
<h2>Bambu AMS</h2>
|
||||
<div id="amsDataContainer">
|
||||
<div class="amsData" id="amsData">Wait for AMS-Data...</div>
|
||||
<h2>Spoolman Locations</h2>
|
||||
<label for="locationSelect">Location:</label>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<select id="locationSelect" class="styled-select">
|
||||
<option value="">Please choose...</option>
|
||||
</select>
|
||||
</div>
|
||||
<p id="nfcInfoLocation" class="nfc-status"></p>
|
||||
<button id="writeLocationNfcButton" class="btn btn-primary hidden" onclick="writeLocationNfcTag()">Write Location Tag</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
207
html/rfid.js
@@ -7,11 +7,12 @@ let heartbeatTimer = null;
|
||||
let lastHeartbeatResponse = Date.now();
|
||||
const HEARTBEAT_TIMEOUT = 20000;
|
||||
let reconnectTimer = null;
|
||||
let spoolDetected = false;
|
||||
|
||||
// WebSocket Funktionen
|
||||
function startHeartbeat() {
|
||||
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
||||
|
||||
|
||||
heartbeatTimer = setInterval(() => {
|
||||
// Prüfe ob zu lange keine Antwort kam
|
||||
if (Date.now() - lastHeartbeatResponse > HEARTBEAT_TIMEOUT) {
|
||||
@@ -29,7 +30,7 @@ function startHeartbeat() {
|
||||
updateConnectionStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// Sende Heartbeat
|
||||
socket.send(JSON.stringify({ type: 'heartbeat' }));
|
||||
@@ -83,7 +84,7 @@ function initWebSocket() {
|
||||
isConnected = false;
|
||||
updateConnectionStatus();
|
||||
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
||||
|
||||
|
||||
// Bei Fehler Verbindung schließen und neu aufbauen
|
||||
if (socket) {
|
||||
socket.close();
|
||||
@@ -109,7 +110,7 @@ function initWebSocket() {
|
||||
const bambuDot = document.getElementById('bambuDot');
|
||||
const spoolmanDot = document.getElementById('spoolmanDot');
|
||||
const ramStatus = document.getElementById('ramStatus');
|
||||
|
||||
|
||||
if (bambuDot) {
|
||||
bambuDot.className = 'status-dot ' + (data.bambu_connected ? 'online' : 'offline');
|
||||
// Add click handler only when offline
|
||||
@@ -208,27 +209,13 @@ document.addEventListener('spoolmanError', function(event) {
|
||||
showNotification(`Spoolman Error: ${event.detail.message}`, false);
|
||||
});
|
||||
|
||||
document.addEventListener('filamentSelected', function(event) {
|
||||
document.addEventListener('filamentSelected', function (event) {
|
||||
updateNfcInfo();
|
||||
// Zeige Spool-Buttons wenn ein Filament ausgewählt wurde
|
||||
const selectedText = document.getElementById("selected-filament").textContent;
|
||||
updateSpoolButtons(selectedText !== "Please choose...");
|
||||
});
|
||||
|
||||
// Hilfsfunktion für kontrastreiche Textfarbe
|
||||
function getContrastColor(hexcolor) {
|
||||
// Konvertiere Hex zu RGB
|
||||
const r = parseInt(hexcolor.substr(0,2),16);
|
||||
const g = parseInt(hexcolor.substr(2,2),16);
|
||||
const b = parseInt(hexcolor.substr(4,2),16);
|
||||
|
||||
// Berechne Helligkeit (YIQ Formel)
|
||||
const yiq = ((r*299)+(g*587)+(b*114))/1000;
|
||||
|
||||
// Return schwarz oder weiß basierend auf Helligkeit
|
||||
return (yiq >= 128) ? '#000000' : '#FFFFFF';
|
||||
}
|
||||
|
||||
function updateNfcInfo() {
|
||||
const selectedText = document.getElementById("selected-filament").textContent;
|
||||
const nfcInfo = document.getElementById("nfcInfo");
|
||||
@@ -490,7 +477,7 @@ function handleSpoolIn(amsId, trayId) {
|
||||
nozzle_temp_max: parseInt(maxTemp),
|
||||
type: selectedSpool.filament.material,
|
||||
brand: selectedSpool.filament.vendor.name,
|
||||
tray_info_idx: selectedSpool.filament.extra.bambu_idx.replace(/['"]+/g, '').trim(),
|
||||
tray_info_idx: selectedSpool.filament.extra.bambu_idx?.replace(/['"]+/g, '').trim() || '',
|
||||
cali_idx: "-1" // Default-Wert setzen
|
||||
}
|
||||
};
|
||||
@@ -522,12 +509,15 @@ function updateNfcStatusIndicator(data) {
|
||||
if (data.found === 0) {
|
||||
// Kein NFC Tag gefunden
|
||||
indicator.className = 'status-circle';
|
||||
spoolDetected = false;
|
||||
} else if (data.found === 1) {
|
||||
// NFC Tag erfolgreich gelesen
|
||||
indicator.className = 'status-circle success';
|
||||
spoolDetected = true;
|
||||
} else {
|
||||
// Fehler beim Lesen
|
||||
indicator.className = 'status-circle error';
|
||||
spoolDetected = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -569,7 +559,10 @@ function updateNfcData(data) {
|
||||
}
|
||||
|
||||
// HTML für die Datenanzeige erstellen
|
||||
let html = `
|
||||
let html = "";
|
||||
|
||||
if(data.sm_id){
|
||||
html = `
|
||||
<div class="nfc-card-data" style="margin-top: 10px;">
|
||||
<p><strong>Brand:</strong> ${data.brand || 'N/A'}</p>
|
||||
<p><strong>Type:</strong> ${data.type || 'N/A'} ${data.color_hex ? `<span style="
|
||||
@@ -582,10 +575,27 @@ function updateNfcData(data) {
|
||||
border-radius: 3px;
|
||||
margin-left: 5px;
|
||||
"></span>` : ''}</p>
|
||||
`;
|
||||
`;
|
||||
|
||||
// Spoolman ID anzeigen
|
||||
html += `<p><strong>Spoolman ID:</strong> ${data.sm_id || 'No Spoolman ID'}</p>`;
|
||||
// Spoolman ID anzeigen
|
||||
html += `<p><strong>Spoolman ID:</strong> ${data.sm_id} (<a href="${spoolmanUrl}/spool/show/${data.sm_id}">Open in Spoolman</a>)</p>`;
|
||||
}
|
||||
else if(data.location)
|
||||
{
|
||||
html = `
|
||||
<div class="nfc-card-data" style="margin-top: 10px;">
|
||||
<p><strong>Location:</strong> ${data.location || 'N/A'}</p>
|
||||
`;
|
||||
}
|
||||
else
|
||||
{
|
||||
html = `
|
||||
<div class="nfc-card-data" style="margin-top: 10px;">
|
||||
<p><strong>Unknown tag</strong></p>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Nur wenn eine sm_id vorhanden ist, aktualisiere die Dropdowns
|
||||
if (data.sm_id) {
|
||||
@@ -612,67 +622,112 @@ function updateNfcData(data) {
|
||||
}
|
||||
|
||||
function writeNfcTag() {
|
||||
const selectedText = document.getElementById("selected-filament").textContent;
|
||||
if (selectedText === "Please choose...") {
|
||||
alert('Please select a Spool first.');
|
||||
return;
|
||||
if(!spoolDetected || confirm("Are you sure you want to overwrite the Tag?") == true){
|
||||
const selectedText = document.getElementById("selected-filament").textContent;
|
||||
if (selectedText === "Please choose...") {
|
||||
alert('Please select a Spool first.');
|
||||
return;
|
||||
}
|
||||
|
||||
const spoolsData = window.getSpoolData();
|
||||
const selectedSpool = spoolsData.find(spool =>
|
||||
`${spool.id} | ${spool.filament.name} (${spool.filament.material})` === selectedText
|
||||
);
|
||||
|
||||
if (!selectedSpool) {
|
||||
alert('Ausgewählte Spule konnte nicht gefunden werden.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Temperaturwerte korrekt extrahieren
|
||||
let minTemp = "175";
|
||||
let maxTemp = "275";
|
||||
|
||||
if (Array.isArray(selectedSpool.filament.nozzle_temperature) &&
|
||||
selectedSpool.filament.nozzle_temperature.length >= 2) {
|
||||
minTemp = String(selectedSpool.filament.nozzle_temperature[0]);
|
||||
maxTemp = String(selectedSpool.filament.nozzle_temperature[1]);
|
||||
}
|
||||
|
||||
// Erstelle das NFC-Datenpaket mit korrekten Datentypen
|
||||
const nfcData = {
|
||||
color_hex: selectedSpool.filament.color_hex || "FFFFFF",
|
||||
type: selectedSpool.filament.material,
|
||||
min_temp: minTemp,
|
||||
max_temp: maxTemp,
|
||||
brand: selectedSpool.filament.vendor.name,
|
||||
sm_id: String(selectedSpool.id) // Konvertiere zu String
|
||||
};
|
||||
|
||||
if (socket?.readyState === WebSocket.OPEN) {
|
||||
const writeButton = document.getElementById("writeNfcButton");
|
||||
writeButton.classList.add("writing");
|
||||
writeButton.textContent = "Writing";
|
||||
socket.send(JSON.stringify({
|
||||
type: 'writeNfcTag',
|
||||
tagType: 'spool',
|
||||
payload: nfcData
|
||||
}));
|
||||
} else {
|
||||
alert('Not connected to Server. Please check connection.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const spoolsData = window.getSpoolData();
|
||||
const selectedSpool = spoolsData.find(spool =>
|
||||
`${spool.id} | ${spool.filament.name} (${spool.filament.material})` === selectedText
|
||||
);
|
||||
function writeLocationNfcTag() {
|
||||
if(!spoolDetected || confirm("Are you sure you want to overwrite the Tag?") == true){
|
||||
const selectedText = document.getElementById("locationSelect").value;
|
||||
if (selectedText === "Please choose...") {
|
||||
alert('Please select a location first.');
|
||||
return;
|
||||
}
|
||||
// Erstelle das NFC-Datenpaket mit korrekten Datentypen
|
||||
const nfcData = {
|
||||
location: String(selectedText)
|
||||
};
|
||||
|
||||
if (!selectedSpool) {
|
||||
alert('Ausgewählte Spule konnte nicht gefunden werden.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Temperaturwerte korrekt extrahieren
|
||||
let minTemp = "175";
|
||||
let maxTemp = "275";
|
||||
|
||||
if (Array.isArray(selectedSpool.filament.nozzle_temperature) &&
|
||||
selectedSpool.filament.nozzle_temperature.length >= 2) {
|
||||
minTemp = String(selectedSpool.filament.nozzle_temperature[0]);
|
||||
maxTemp = String(selectedSpool.filament.nozzle_temperature[1]);
|
||||
}
|
||||
|
||||
// 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,
|
||||
max_temp: maxTemp,
|
||||
brand: selectedSpool.filament.vendor.name,
|
||||
sm_id: String(selectedSpool.id) // Konvertiere zu String
|
||||
};
|
||||
|
||||
if (socket?.readyState === WebSocket.OPEN) {
|
||||
const writeButton = document.getElementById("writeNfcButton");
|
||||
writeButton.classList.add("writing");
|
||||
writeButton.textContent = "Writing";
|
||||
socket.send(JSON.stringify({
|
||||
type: 'writeNfcTag',
|
||||
payload: nfcData
|
||||
}));
|
||||
} else {
|
||||
alert('Not connected to Server. Please check connection.');
|
||||
if (socket?.readyState === WebSocket.OPEN) {
|
||||
const writeButton = document.getElementById("writeLocationNfcButton");
|
||||
writeButton.classList.add("writing");
|
||||
writeButton.textContent = "Writing";
|
||||
socket.send(JSON.stringify({
|
||||
type: 'writeNfcTag',
|
||||
tagType: 'location',
|
||||
payload: nfcData
|
||||
}));
|
||||
} else {
|
||||
alert('Not connected to Server. Please check connection.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleWriteNfcTagResponse(success) {
|
||||
const writeButton = document.getElementById("writeNfcButton");
|
||||
writeButton.classList.remove("writing");
|
||||
writeButton.classList.add(success ? "success" : "error");
|
||||
writeButton.textContent = success ? "Write success" : "Write failed";
|
||||
const writeLocationButton = document.getElementById("writeLocationNfcButton");
|
||||
if(writeButton.classList.contains("writing")){
|
||||
writeButton.classList.remove("writing");
|
||||
writeButton.classList.add(success ? "success" : "error");
|
||||
writeButton.textContent = success ? "Write success" : "Write failed";
|
||||
|
||||
setTimeout(() => {
|
||||
writeButton.classList.remove("success", "error");
|
||||
writeButton.textContent = "Write Tag";
|
||||
}, 5000);
|
||||
setTimeout(() => {
|
||||
writeButton.classList.remove("success", "error");
|
||||
writeButton.textContent = "Write Tag";
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
if(writeLocationButton.classList.contains("writing")){
|
||||
writeLocationButton.classList.remove("writing");
|
||||
writeLocationButton.classList.add(success ? "success" : "error");
|
||||
writeLocationButton.textContent = success ? "Write success" : "Write failed";
|
||||
|
||||
setTimeout(() => {
|
||||
writeLocationButton.classList.remove("success", "error");
|
||||
writeLocationButton.textContent = "Write Location Tag";
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
function showNotification(message, isSuccess) {
|
||||
@@ -688,4 +743,4 @@ function showNotification(message, isSuccess) {
|
||||
notification.remove();
|
||||
}, 300);
|
||||
}, 3000);
|
||||
}
|
||||
}
|
172
html/rfid_bambu.html
Normal file
@@ -0,0 +1,172 @@
|
||||
<!-- head --><!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FilaMan - Filament Management Tool</title>
|
||||
<link rel="icon" type="image/png" href="/favicon.ico">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script>
|
||||
fetch('/api/version')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const versionSpan = document.querySelector('.version');
|
||||
if (versionSpan) {
|
||||
versionSpan.textContent = 'v' + data.version;
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error fetching version:', error));
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="navbar">
|
||||
<div style="display: flex; align-items: center; gap: 2rem;">
|
||||
<img src="/logo.png" alt="FilaMan Logo" class="logo">
|
||||
<div class="logo-text">
|
||||
<h1>FilaMan<span class="version"></span></h1>
|
||||
<h4>Filament Management Tool</h4>
|
||||
</div>
|
||||
</div>
|
||||
<nav style="display: flex; gap: 1rem;">
|
||||
<a href="/">Start</a>
|
||||
<a href="/waage">Scale</a>
|
||||
<a href="/spoolman">Spoolman/Bambu</a>
|
||||
<a href="/about">About</a>
|
||||
<a href="/upgrade">Upgrade</a>
|
||||
</nav>
|
||||
<div class="status-container">
|
||||
<div class="status-item">
|
||||
<span class="status-dot" id="bambuDot"></span>B
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-dot" id="spoolmanDot"></span>S
|
||||
</div>
|
||||
<div class="ram-status" id="ramStatus"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- head -->
|
||||
|
||||
<div class="connection-status hidden">
|
||||
<div class="spinner"></div>
|
||||
<span>Connection lost. Trying to reconnect...</span>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="three-column-layout">
|
||||
<!-- Linke Spalte -->
|
||||
<div class="column">
|
||||
<div class="feature-box">
|
||||
<div class="statistics-header">
|
||||
<h2>Statistics</h2>
|
||||
<button id="refreshSpoolman" class="refresh-button">
|
||||
<span>Refresh Spoolman</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="statistics-column">
|
||||
<h3>Spools</h3>
|
||||
<div class="spool-stat" style="display: flex; justify-content: center; align-items: center;">
|
||||
<span class="stat-label">total:</span>
|
||||
<span class="stat-value" id="totalSpools"></span>
|
||||
<div style="width: auto;"></div>
|
||||
<span class="stat-label">without Tag:</span>
|
||||
<span class="stat-value" id="spoolsWithoutTag"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="statistics-grid">
|
||||
<div class="statistics-column">
|
||||
<h3>Overview</h3>
|
||||
<ul class="statistics-list">
|
||||
<li>
|
||||
<span class="stat-label">Manufacturer:</span>
|
||||
<span class="stat-value" id="totalVendors"></span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="stat-label">Weight:</span>
|
||||
<span class="stat-value"><span id="totalWeight"></span></span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="stat-label">Length:</span>
|
||||
<span class="stat-value"><span id="totalLength"></span></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="statistics-column">
|
||||
<h3>Materials</h3>
|
||||
<ul class="statistics-list" id="materialsList">
|
||||
<!-- Wird dynamisch befüllt -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature-box">
|
||||
<div class="nfc-header">
|
||||
<h2>NFC-Tag</h2>
|
||||
<span id="nfcStatusIndicator" class="status-circle"></span>
|
||||
</div>
|
||||
<div class="nfc-status-display"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mittlere Spalte -->
|
||||
<div class="column">
|
||||
<div class="feature-box">
|
||||
<h2>Spoolman Spools</h2>
|
||||
<label for="vendorSelect">Manufacturer:</label>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<select id="vendorSelect" class="styled-select">
|
||||
<option value="">Please choose...</option>
|
||||
</select>
|
||||
<label style="margin-left: 10px;">
|
||||
<input type="checkbox" id="onlyWithoutSmId" checked onchange="updateFilamentDropdown()">
|
||||
Only Spools without SM ID
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="filamentSection" class="feature-box hidden">
|
||||
<label>Spool / Filament:</label>
|
||||
<div class="custom-dropdown">
|
||||
<div class="dropdown-button" onclick="toggleFilamentDropdown()">
|
||||
<div class="selected-color" id="selected-color"></div>
|
||||
<span id="selected-filament">Please choose...</span>
|
||||
<span class="dropdown-arrow">▼</span>
|
||||
</div>
|
||||
<div class="dropdown-content" id="filament-dropdown-content">
|
||||
<!-- Optionen werden dynamisch hinzugefügt -->
|
||||
</div>
|
||||
</div>
|
||||
<p id="nfcInfo" class="nfc-status"></p>
|
||||
<button id="writeNfcButton" class="btn btn-primary hidden" onclick="writeNfcTag()">Write Tag</button>
|
||||
</div>
|
||||
|
||||
<div class="feature-box">
|
||||
<h2>Spoolman Locations</h2>
|
||||
<label for="locationSelect">Location:</label>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<select id="locationSelect" class="styled-select">
|
||||
<option value="">Please choose...</option>
|
||||
</select>
|
||||
</div>
|
||||
<p id="nfcInfoLocation" class="nfc-status"></p>
|
||||
<button id="writeLocationNfcButton" class="btn btn-primary hidden" onclick="writeLocationNfcTag()">Write Location Tag</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rechte Spalte -->
|
||||
<div class="column">
|
||||
<div class="feature-box">
|
||||
<h2>Bambu AMS</h2>
|
||||
<div id="amsDataContainer">
|
||||
<div class="amsData" id="amsData">Wait for AMS-Data...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="spoolman.js"></script>
|
||||
<script src="rfid.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
@@ -52,11 +52,43 @@
|
||||
if (spoolmanUrl && spoolmanUrl.trim() !== "") {
|
||||
document.getElementById('spoolmanUrl').value = spoolmanUrl;
|
||||
}
|
||||
|
||||
// Initialize OctoPrint fields visibility
|
||||
toggleOctoFields();
|
||||
};
|
||||
|
||||
function removeBambuCredentials() {
|
||||
fetch('/api/bambu?remove=true')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
document.getElementById('bambuIp').value = '';
|
||||
document.getElementById('bambuSerial').value = '';
|
||||
document.getElementById('bambuCode').value = '';
|
||||
document.getElementById('autoSend').checked = false;
|
||||
document.getElementById('autoSendTime').value = '';
|
||||
document.getElementById('bambuStatusMessage').innerText = 'Bambu Credentials removed!';
|
||||
// Reload with forced cache refresh after short delay
|
||||
setTimeout(() => {
|
||||
window.location.reload(true);
|
||||
window.location.href = '/';
|
||||
}, 1500);
|
||||
} else {
|
||||
document.getElementById('bambuStatusMessage').innerText = 'Error while removing Bambu Credentials.';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById('bambuStatusMessage').innerText = 'Error while removing: ' + error.message;
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -82,6 +114,11 @@
|
||||
.then(data => {
|
||||
if (data.healthy) {
|
||||
document.getElementById('bambuStatusMessage').innerText = 'Bambu Credentials saved!';
|
||||
// Reload with forced cache refresh after short delay
|
||||
setTimeout(() => {
|
||||
window.location.reload(true);
|
||||
window.location.href = '/';
|
||||
}, 1500);
|
||||
} else {
|
||||
document.getElementById('bambuStatusMessage').innerText = 'Error while saving Bambu Credentials.';
|
||||
}
|
||||
@@ -90,6 +127,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}}";
|
||||
@@ -100,9 +146,20 @@
|
||||
|
||||
<div class="card">
|
||||
<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">
|
||||
<button onclick="checkSpoolmanInstance()">Save Spoolman URL</button>
|
||||
<h5 class="card-title">Set URL/IP to your Spoolman instance</h5>
|
||||
<input type="text" id="spoolmanUrl" onkeydown="if(event.keyCode == 13) document.getElementById('btnSaveSpoolmanUrl').click()" placeholder="http://ip-or-url-of-your-spoolman-instance:port">
|
||||
<h5 class="card-title">If you want to enable sending the spool to the 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-instance:port" value="{{spoolmanOctoUrl}}">
|
||||
<input type="text" id="spoolmanOctoToken" placeholder="Your Octoprint Token" value="{{spoolmanOctoToken}}">
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button id="btnSaveSpoolmanUrl" onclick="checkSpoolmanInstance()">Save Spoolman URL</button>
|
||||
<p id="statusMessage"></p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -112,29 +169,30 @@
|
||||
<h5 class="card-title">Bambu Lab Printer Credentials</h5>
|
||||
<div class="bambu-settings">
|
||||
<div class="input-group">
|
||||
<label for="bambuIp">Bambu Drucker IP-Adresse:</label>
|
||||
<label for="bambuIp">Bambu Printer IP Address:</label>
|
||||
<input type="text" id="bambuIp" placeholder="192.168.1.xxx" value="{{bambuIp}}">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="bambuSerial">Drucker Seriennummer:</label>
|
||||
<label for="bambuSerial">Printer Serial Number:</label>
|
||||
<input type="text" id="bambuSerial" placeholder="BBLXXXXXXXX" value="{{bambuSerial}}">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="bambuCode">Access Code:</label>
|
||||
<input type="text" id="bambuCode" placeholder="Access Code vom Drucker" value="{{bambuCode}}">
|
||||
<input type="text" id="bambuCode" placeholder="Access Code of the printer" value="{{bambuCode}}">
|
||||
</div>
|
||||
<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 time in Seconds:</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="text" id="autoSendTime" placeholder="Time to wait for new Spool" value="{{autoSendTime}}" style="width: 100px;">
|
||||
<input type="number" min="60" id="autoSendTime" placeholder="Time to wait" value="{{autoSendTime}}" style="width: 100px;">
|
||||
</div>
|
||||
|
||||
<button style="margin: 0;" onclick="saveBambuCredentials()">Save Bambu Credentials</button>
|
||||
<button style="margin: 0; background-color: red;" onclick="removeBambuCredentials()">Remove Credentials</button>
|
||||
<p id="bambuStatusMessage"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
159
html/spoolman.js
@@ -1,6 +1,7 @@
|
||||
// Globale Variablen
|
||||
let spoolmanUrl = '';
|
||||
let spoolsData = [];
|
||||
let locationData = [];
|
||||
|
||||
// Hilfsfunktionen für Datenmanipulation
|
||||
function processSpoolData(data) {
|
||||
@@ -86,10 +87,10 @@ function populateVendorDropdown(data, selectedSmId = null) {
|
||||
});
|
||||
|
||||
// Nach der Schleife: Formatierung der Gesamtlänge
|
||||
console.log("Total Lenght: ", totalLength);
|
||||
const formattedLength = totalLength > 1000
|
||||
? (totalLength / 1000).toFixed(2) + " km"
|
||||
: totalLength.toFixed(2) + " m";
|
||||
const lengthInM = totalLength / 1000; // erst in m umrechnen
|
||||
const formattedLength = lengthInM > 1000
|
||||
? (lengthInM / 1000).toFixed(2) + " km"
|
||||
: lengthInM.toFixed(2) + " m";
|
||||
|
||||
// Formatierung des Gesamtgewichts (von g zu kg zu t)
|
||||
const weightInKg = totalWeight / 1000; // erst in kg umrechnen
|
||||
@@ -97,13 +98,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;
|
||||
@@ -131,6 +134,26 @@ function populateVendorDropdown(data, selectedSmId = null) {
|
||||
}
|
||||
}
|
||||
|
||||
// Dropdown-Funktionen
|
||||
function populateLocationDropdown(data) {
|
||||
const locationSelect = document.getElementById("locationSelect");
|
||||
if (!locationSelect) {
|
||||
console.error('locationSelect Element nicht gefunden');
|
||||
return;
|
||||
}
|
||||
|
||||
locationSelect.innerHTML = '<option value="">Bitte wählen...</option>';
|
||||
// Dropdown mit gefilterten Herstellern befüllen - alphabetisch sortiert
|
||||
Object.entries(data)
|
||||
.sort(([, nameA], [, nameB]) => nameA.localeCompare(nameB)) // Sort vendors alphabetically by name
|
||||
.forEach(([id, name]) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = name;
|
||||
option.textContent = name;
|
||||
locationSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
function updateFilamentDropdown(selectedSmId = null) {
|
||||
const vendorId = document.getElementById("vendorSelect").value;
|
||||
const dropdownContentInner = document.getElementById("filament-dropdown-content");
|
||||
@@ -145,6 +168,13 @@ function updateFilamentDropdown(selectedSmId = null) {
|
||||
|
||||
if (vendorId) {
|
||||
const filteredFilaments = spoolsData.filter(spool => {
|
||||
if (!spool?.filament?.vendor?.id) {
|
||||
console.log('Problem aufgetreten bei: ', spool?.filament?.vendor);
|
||||
console.log('Problematische Spulen:',
|
||||
spoolsData.filter(spool => !spool?.filament?.vendor?.id));
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasValidNfcId = spool.extra &&
|
||||
spool.extra.nfc_id &&
|
||||
spool.extra.nfc_id !== '""' &&
|
||||
@@ -160,9 +190,32 @@ function updateFilamentDropdown(selectedSmId = null) {
|
||||
option.setAttribute("data-value", spool.filament.id);
|
||||
option.setAttribute("data-nfc-id", spool.extra.nfc_id || "");
|
||||
|
||||
const colorHex = spool.filament.color_hex || 'FFFFFF';
|
||||
|
||||
// Generate color representation based on filament type (single or multi color)
|
||||
let colorHTML = '';
|
||||
|
||||
// Check if this is a multicolor filament
|
||||
if (spool.filament.multi_color_hexes) {
|
||||
// Parse multi color hexes from comma-separated string
|
||||
const colors = spool.filament.multi_color_hexes.replace(/#/g, '').split(',');
|
||||
|
||||
// Determine the display style based on direction
|
||||
const direction = spool.filament.multi_color_direction || 'coaxial';
|
||||
|
||||
// Generate color circles for each color
|
||||
colorHTML = '<div class="option-colors">';
|
||||
colors.forEach(color => {
|
||||
colorHTML += `<div class="option-color multi-color ${direction}" style="background-color: #${color}"></div>`;
|
||||
});
|
||||
colorHTML += '</div>';
|
||||
} else {
|
||||
// Single color filament
|
||||
const colorHex = spool.filament.color_hex || 'FFFFFF';
|
||||
colorHTML = `<div class="option-color" style="background-color: #${colorHex}"></div>`;
|
||||
}
|
||||
|
||||
option.innerHTML = `
|
||||
<div class="option-color" style="background-color: #${colorHex}"></div>
|
||||
${colorHTML}
|
||||
<span>${spool.id} | ${spool.filament.name} (${spool.filament.material})</span>
|
||||
`;
|
||||
|
||||
@@ -176,12 +229,41 @@ function updateFilamentDropdown(selectedSmId = null) {
|
||||
}
|
||||
}
|
||||
|
||||
function updateLocationSelect(){
|
||||
const writeLocationNfcButton = document.getElementById('writeLocationNfcButton');
|
||||
if(writeLocationNfcButton){
|
||||
writeLocationNfcButton.classList.remove("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
function selectFilament(spool) {
|
||||
const selectedColor = document.getElementById("selected-color");
|
||||
const selectedText = document.getElementById("selected-filament");
|
||||
const dropdownContent = document.getElementById("filament-dropdown-content");
|
||||
|
||||
selectedColor.style.backgroundColor = `#${spool.filament.color_hex || 'FFFFFF'}`;
|
||||
// Update the selected color display
|
||||
if (spool.filament.multi_color_hexes) {
|
||||
// Handle multicolor filament display in the selection header
|
||||
const colors = spool.filament.multi_color_hexes.replace(/#/g, '').split(',');
|
||||
const direction = spool.filament.multi_color_direction || 'coaxial';
|
||||
|
||||
// Replace the single color div with multiple color divs
|
||||
selectedColor.innerHTML = '';
|
||||
colors.forEach(color => {
|
||||
const colorDiv = document.createElement('div');
|
||||
colorDiv.className = `color-segment multi-color ${direction}`;
|
||||
colorDiv.style.backgroundColor = `#${color}`;
|
||||
selectedColor.appendChild(colorDiv);
|
||||
});
|
||||
// Add multiple color class to the container
|
||||
selectedColor.classList.add('multi-color-container');
|
||||
} else {
|
||||
// Single color filament - reset to default display
|
||||
selectedColor.innerHTML = '';
|
||||
selectedColor.classList.remove('multi-color-container');
|
||||
selectedColor.style.backgroundColor = `#${spool.filament.color_hex || 'FFFFFF'}`;
|
||||
}
|
||||
|
||||
selectedText.textContent = `${spool.id} | ${spool.filament.name} (${spool.filament.material})`;
|
||||
dropdownContent.classList.remove("show");
|
||||
|
||||
@@ -207,10 +289,18 @@ async function initSpoolman() {
|
||||
|
||||
const fetchedData = await fetchSpoolData();
|
||||
spoolsData = processSpoolData(fetchedData);
|
||||
|
||||
|
||||
document.dispatchEvent(new CustomEvent('spoolDataLoaded', {
|
||||
detail: spoolsData
|
||||
}));
|
||||
|
||||
locationData = await fetchLocationData();
|
||||
|
||||
document.dispatchEvent(new CustomEvent('locationDataLoaded', {
|
||||
detail: locationData
|
||||
}));
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Initialisieren von Spoolman:', error);
|
||||
document.dispatchEvent(new CustomEvent('spoolmanError', {
|
||||
@@ -238,17 +328,24 @@ async function fetchSpoolData() {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// Exportiere Funktionen
|
||||
window.getSpoolData = () => spoolsData;
|
||||
window.reloadSpoolData = initSpoolman;
|
||||
window.populateVendorDropdown = populateVendorDropdown;
|
||||
window.updateFilamentDropdown = updateFilamentDropdown;
|
||||
window.toggleFilamentDropdown = () => {
|
||||
const content = document.getElementById("filament-dropdown-content");
|
||||
content.classList.toggle("show");
|
||||
};
|
||||
*/
|
||||
async function fetchLocationData() {
|
||||
try {
|
||||
if (!spoolmanUrl) {
|
||||
throw new Error('Spoolman URL ist nicht initialisiert');
|
||||
}
|
||||
|
||||
const response = await fetch(`${spoolmanUrl}/api/v1/location`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen der Location-Daten:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Event Listener
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
@@ -258,6 +355,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (vendorSelect) {
|
||||
vendorSelect.addEventListener('change', () => updateFilamentDropdown());
|
||||
}
|
||||
|
||||
const locationSelect = document.getElementById('locationSelect');
|
||||
if (locationSelect) {
|
||||
locationSelect.addEventListener('change', () => updateLocationSelect());
|
||||
}
|
||||
|
||||
const onlyWithoutSmId = document.getElementById('onlyWithoutSmId');
|
||||
if (onlyWithoutSmId) {
|
||||
@@ -270,6 +372,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
document.addEventListener('spoolDataLoaded', (event) => {
|
||||
populateVendorDropdown(event.detail);
|
||||
});
|
||||
|
||||
document.addEventListener('locationDataLoaded', (event) => {
|
||||
populateLocationDropdown(event.detail);
|
||||
});
|
||||
|
||||
window.onclick = function(event) {
|
||||
if (!event.target.closest('.custom-dropdown')) {
|
||||
@@ -300,6 +406,7 @@ window.getSpoolData = () => spoolsData;
|
||||
window.setSpoolData = (data) => { spoolsData = data; };
|
||||
window.reloadSpoolData = initSpoolman;
|
||||
window.populateVendorDropdown = populateVendorDropdown;
|
||||
window.populateLocationDropdown = populateLocationDropdown;
|
||||
window.updateFilamentDropdown = updateFilamentDropdown;
|
||||
window.toggleFilamentDropdown = () => {
|
||||
const content = document.getElementById("filament-dropdown-content");
|
||||
|
1
html/spoolman_url.json
Normal file
@@ -0,0 +1 @@
|
||||
{"url": "http://192.168.1.5:7912", "octoEnabled": true, "octoUrl": "http://192.168.1.17:5001", "octoToken": "O5zZ58mXRAyeGpVEj2ZZj-UPAPqJ2N7JgtD36mw1M4g"}
|
@@ -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;
|
||||
}
|
||||
@@ -755,6 +759,50 @@ a:hover {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Multi-color filament styles */
|
||||
.option-colors {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.multi-color {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
/* Coaxial pattern (horizontal stripes) */
|
||||
.multi-color.coaxial {
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Longitudinal pattern (vertical stripes) */
|
||||
.multi-color.longitudinal {
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Container for multiple colors in selected display */
|
||||
.multi-color-container {
|
||||
display: flex !important;
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
gap: 2px;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
.color-segment {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
.notification {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
@@ -923,31 +971,35 @@ input[type="submit"]:disabled,
|
||||
}
|
||||
|
||||
/* Schreib-Button */
|
||||
#writeNfcButton {
|
||||
#writeNfcButton, #writeLocationNfcButton {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
#writeNfcButton.writing {
|
||||
#writeNfcButton.writing, #writeLocationNfcButton.writing {
|
||||
background-color: #ffc107;
|
||||
color: black;
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
#writeNfcButton.success {
|
||||
#writeNfcButton.success, #writeLocationNfcButton.success {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
#writeNfcButton.error {
|
||||
#writeNfcButton.error, #writeLocationNfcButton.error {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
#writeLocationNfcButton{
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
@keyframes dots {
|
||||
0% { content: ""; }
|
||||
33% { content: "."; }
|
||||
@@ -955,7 +1007,7 @@ input[type="submit"]:disabled,
|
||||
100% { content: "..."; }
|
||||
}
|
||||
|
||||
#writeNfcButton.writing::after {
|
||||
#writeNfcButton.writing::after, #writeLocationNfcButton.writing::after {
|
||||
content: "...";
|
||||
animation: dots 1s steps(3, end) infinite;
|
||||
}
|
||||
|
@@ -56,8 +56,8 @@
|
||||
|
||||
<div class="update-options">
|
||||
<div class="update-section">
|
||||
<h2>Firmware Update</h2>
|
||||
<p>Upload a new firmware file (filaman_*.bin)</p>
|
||||
<h2>1) Firmware Update</h2>
|
||||
<p>Upload a new firmware file (upgrade_filaman_firmware_*.bin)</p>
|
||||
<div class="update-form">
|
||||
<form id="firmwareForm" enctype='multipart/form-data' data-type="firmware">
|
||||
<input type='file' name='update' accept='.bin' required>
|
||||
@@ -67,8 +67,8 @@
|
||||
</div>
|
||||
|
||||
<div class="update-section">
|
||||
<h2>Webpage Update</h2>
|
||||
<p>Upload a new webpage file (webpage_*.bin)</p>
|
||||
<h2>2) Webpage Update</h2>
|
||||
<p>Upload a new webpage file (upgrade_filaman_website_*.bin)</p>
|
||||
<div class="update-form">
|
||||
<form id="webpageForm" enctype='multipart/form-data' data-type="webpage">
|
||||
<input type='file' name='update' accept='.bin' required>
|
||||
@@ -129,6 +129,7 @@
|
||||
if (data.status === 'success' || lastReceivedProgress >= 98) {
|
||||
clearTimeout(wsReconnectTimer);
|
||||
setTimeout(() => {
|
||||
window.location.reload(true);
|
||||
window.location.href = '/';
|
||||
}, 30000);
|
||||
}
|
||||
@@ -148,6 +149,7 @@
|
||||
status.style.display = 'block';
|
||||
clearTimeout(wsReconnectTimer);
|
||||
setTimeout(() => {
|
||||
window.location.reload(true);
|
||||
window.location.href = '/';
|
||||
}, 30000);
|
||||
} else {
|
||||
|
@@ -55,6 +55,7 @@
|
||||
<h5 class="card-title">Sacle Calibration</h5>
|
||||
<button id="calibrateBtn" class="btn btn-primary">Calibrate Scale</button>
|
||||
<button id="tareBtn" class="btn btn-secondary">Tare Scale</button>
|
||||
Enable Auto-TARE <input type="checkbox" id="autoTareCheckbox" onchange="setAutoTare(this.checked);" {{autoTare}}>
|
||||
<div id="statusMessage" class="mt-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -140,6 +141,15 @@
|
||||
}));
|
||||
});
|
||||
|
||||
// Add auto-tare function
|
||||
function setAutoTare(enabled) {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'scale',
|
||||
payload: 'setAutoTare',
|
||||
enabled: enabled
|
||||
}));
|
||||
}
|
||||
|
||||
// WebSocket-Verbindung beim Laden der Seite initiieren
|
||||
connectWebSocket();
|
||||
</script>
|
||||
|
BIN
img/7-enable.png
Normal file
After Width: | Height: | Size: 52 KiB |
@@ -9,21 +9,21 @@
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[common]
|
||||
version = "1.3.98"
|
||||
version = "2.0.0-beta13"
|
||||
to_old_version = "1.5.10"
|
||||
|
||||
##
|
||||
[env:esp32dev]
|
||||
platform = espressif32
|
||||
board = esp32dev
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
#monitor_port = /dev/cu.usbmodem01
|
||||
|
||||
lib_deps =
|
||||
tzapu/WiFiManager @ ^2.0.17
|
||||
https://github.com/me-no-dev/ESPAsyncWebServer.git#master
|
||||
#me-no-dev/AsyncTCP @ ^1.1.1
|
||||
https://github.com/esphome/AsyncTCP.git
|
||||
#mathieucarbou/ESPAsyncWebServer @ ^3.6.0
|
||||
#esp32async/AsyncTCP @ ^3.3.5
|
||||
bogde/HX711 @ ^0.7.5
|
||||
adafruit/Adafruit SSD1306 @ ^2.5.13
|
||||
adafruit/Adafruit GFX Library @ ^1.11.11
|
||||
@@ -33,7 +33,6 @@ lib_deps =
|
||||
digitaldragon/SSLClient @ ^1.3.2
|
||||
|
||||
; Enable SPIFFS upload
|
||||
#board_build.filesystem = spiffs
|
||||
board_build.filesystem = littlefs
|
||||
; Update partition settings
|
||||
board_build.partitions = partitions.csv
|
||||
@@ -48,6 +47,8 @@ build_flags =
|
||||
#-DNDEBUG
|
||||
-mtext-section-literals
|
||||
-DVERSION=\"${common.version}\"
|
||||
-DTOOLDVERSION=\"${common.to_old_version}\"
|
||||
#-DENABLE_HEAP_DEBUGGING
|
||||
-DASYNCWEBSERVER_REGEX
|
||||
#-DCORE_DEBUG_LEVEL=3
|
||||
-DCONFIG_ARDUHAL_LOG_COLORS=1
|
||||
|
@@ -14,7 +14,7 @@ def copy_file(input_file, output_file):
|
||||
|
||||
def should_compress(file):
|
||||
# Skip compression for spoolman.html
|
||||
if file == 'spoolman.html':
|
||||
if file == 'spoolman.html' or file == 'waage.html':
|
||||
return False
|
||||
# Komprimiere nur bestimmte Dateitypen
|
||||
return file.endswith(('.js', '.png', '.css', '.html'))
|
||||
|
@@ -14,17 +14,39 @@ def get_version():
|
||||
return version_match.group(1) if version_match else None
|
||||
|
||||
def get_last_tag():
|
||||
"""Get the last non-beta tag for changelog generation"""
|
||||
try:
|
||||
result = subprocess.run(['git', 'describe', '--tags', '--abbrev=0'],
|
||||
# Get all tags sorted by version
|
||||
result = subprocess.run(['git', 'tag', '-l', '--sort=-version:refname'],
|
||||
capture_output=True, text=True)
|
||||
return result.stdout.strip()
|
||||
if result.returncode != 0:
|
||||
return None
|
||||
|
||||
tags = result.stdout.strip().split('\n')
|
||||
|
||||
# Find the first (newest) non-beta tag
|
||||
for tag in tags:
|
||||
if tag and not '-beta' in tag.lower():
|
||||
print(f"Using last stable tag for changelog: {tag}")
|
||||
return tag
|
||||
|
||||
# Fallback: if no non-beta tags found, use the newest tag
|
||||
print("No stable tags found, using newest tag")
|
||||
if tags and tags[0]:
|
||||
return tags[0]
|
||||
return None
|
||||
except subprocess.CalledProcessError:
|
||||
return None
|
||||
|
||||
def categorize_commit(commit_msg):
|
||||
"""Categorize commit messages based on conventional commits"""
|
||||
lower_msg = commit_msg.lower()
|
||||
if any(x in lower_msg for x in ['feat', 'add', 'new']):
|
||||
|
||||
# Check for breaking changes first
|
||||
if ('!' in commit_msg and any(x in lower_msg for x in ['feat!', 'fix!', 'chore!', 'refactor!'])) or \
|
||||
'breaking change' in lower_msg or 'breaking:' in lower_msg:
|
||||
return 'Breaking Changes'
|
||||
elif any(x in lower_msg for x in ['feat', 'add', 'new']):
|
||||
return 'Added'
|
||||
elif any(x in lower_msg for x in ['fix', 'bug']):
|
||||
return 'Fixed'
|
||||
@@ -34,6 +56,7 @@ def categorize_commit(commit_msg):
|
||||
def get_changes_from_git():
|
||||
"""Get changes from git commits since last tag"""
|
||||
changes = {
|
||||
'Breaking Changes': [],
|
||||
'Added': [],
|
||||
'Changed': [],
|
||||
'Fixed': []
|
||||
@@ -55,7 +78,9 @@ def get_changes_from_git():
|
||||
if commit:
|
||||
category = categorize_commit(commit)
|
||||
# Clean up commit message
|
||||
clean_msg = re.sub(r'^(feat|fix|chore|docs|style|refactor|perf|test)(\(.*\))?:', '', commit).strip()
|
||||
clean_msg = re.sub(r'^(feat|fix|chore|docs|style|refactor|perf|test)(\(.*\))?!?:', '', commit).strip()
|
||||
# Remove BREAKING CHANGE prefix if present
|
||||
clean_msg = re.sub(r'^breaking change:\s*', '', clean_msg, flags=re.IGNORECASE).strip()
|
||||
changes[category].append(clean_msg)
|
||||
|
||||
except subprocess.CalledProcessError:
|
||||
|
1180
src/api.cpp
31
src/api.h
@@ -6,18 +6,45 @@
|
||||
#include "website.h"
|
||||
#include "display.h"
|
||||
#include <ArduinoJson.h>
|
||||
typedef enum {
|
||||
API_INIT,
|
||||
API_IDLE,
|
||||
API_TRANSMITTING
|
||||
} spoolmanApiStateType;
|
||||
|
||||
typedef enum {
|
||||
API_REQUEST_OCTO_SPOOL_UPDATE,
|
||||
API_REQUEST_BAMBU_UPDATE,
|
||||
API_REQUEST_SPOOL_TAG_ID_UPDATE,
|
||||
API_REQUEST_SPOOL_WEIGHT_UPDATE,
|
||||
API_REQUEST_SPOOL_LOCATION_UPDATE,
|
||||
API_REQUEST_VENDOR_CREATE,
|
||||
API_REQUEST_VENDOR_CHECK,
|
||||
API_REQUEST_FILAMENT_CHECK,
|
||||
API_REQUEST_FILAMENT_CREATE,
|
||||
API_REQUEST_SPOOL_CREATE
|
||||
} SpoolmanApiRequestType;
|
||||
|
||||
extern volatile spoolmanApiStateType spoolmanApiState;
|
||||
extern bool spoolman_connected;
|
||||
extern String spoolmanUrl;
|
||||
extern bool octoEnabled;
|
||||
extern bool sendOctoUpdate;
|
||||
extern String octoUrl;
|
||||
extern String octoToken;
|
||||
extern bool spoolmanConnected;
|
||||
|
||||
bool checkSpoolmanInstance(const String& url);
|
||||
bool saveSpoolmanUrl(const String& url);
|
||||
bool checkSpoolmanInstance();
|
||||
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
|
||||
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
|
||||
uint8_t updateSpoolLocation(String spoolId, String location);
|
||||
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
|
||||
bool createBrandFilament(JsonDocument& payload, String uidString);
|
||||
|
||||
#endif
|
||||
|
196
src/bambu.cpp
@@ -10,6 +10,7 @@
|
||||
#include "esp_task_wdt.h"
|
||||
#include "config.h"
|
||||
#include "display.h"
|
||||
#include <Preferences.h>
|
||||
|
||||
WiFiClient espClient;
|
||||
SSLClient sslClient(&espClient);
|
||||
@@ -17,49 +18,69 @@ PubSubClient client(sslClient);
|
||||
|
||||
TaskHandle_t BambuMqttTask;
|
||||
|
||||
String report_topic = "";
|
||||
//String request_topic = "";
|
||||
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 bambuDisabled = false;
|
||||
|
||||
bool bambu_connected = false;
|
||||
bool autoSendToBambu = false;
|
||||
int autoSetToBambuSpoolId = 0;
|
||||
|
||||
BambuCredentials bambuCredentials;
|
||||
|
||||
// Globale Variablen für AMS-Daten
|
||||
int ams_count = 0;
|
||||
String amsJsonData; // Speichert das fertige JSON für WebSocket-Clients
|
||||
AMSData ams_data[MAX_AMS]; // Definition des Arrays;
|
||||
|
||||
bool removeBambuCredentials() {
|
||||
if (BambuMqttTask) {
|
||||
vTaskDelete(BambuMqttTask);
|
||||
BambuMqttTask = NULL;
|
||||
}
|
||||
|
||||
Preferences preferences;
|
||||
preferences.begin(NVS_NAMESPACE_BAMBU, false); // false = readwrite
|
||||
preferences.remove(NVS_KEY_BAMBU_IP);
|
||||
preferences.remove(NVS_KEY_BAMBU_SERIAL);
|
||||
preferences.remove(NVS_KEY_BAMBU_ACCESSCODE);
|
||||
preferences.remove(NVS_KEY_BAMBU_AUTOSEND_ENABLE);
|
||||
preferences.remove(NVS_KEY_BAMBU_AUTOSEND_TIME);
|
||||
preferences.end();
|
||||
|
||||
// Löschen der globalen Variablen
|
||||
bambuCredentials.ip = "";
|
||||
bambuCredentials.serial = "";
|
||||
bambuCredentials.accesscode = "";
|
||||
bambuCredentials.autosend_enable = false;
|
||||
bambuCredentials.autosend_time = BAMBU_DEFAULT_AUTOSEND_TIME;
|
||||
|
||||
autoSetToBambuSpoolId = 0;
|
||||
ams_count = 0;
|
||||
amsJsonData = "";
|
||||
|
||||
bambuDisabled = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool saveBambuCredentials(const String& ip, const String& serialnr, const String& accesscode, bool autoSend, const String& autoSendTime) {
|
||||
if (BambuMqttTask) {
|
||||
vTaskDelete(BambuMqttTask);
|
||||
}
|
||||
|
||||
JsonDocument doc;
|
||||
doc["bambu_ip"] = ip;
|
||||
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.");
|
||||
return false;
|
||||
BambuMqttTask = NULL;
|
||||
}
|
||||
|
||||
// Dynamische Speicherallokation für die globalen Pointer
|
||||
bambu_ip = ip.c_str();
|
||||
bambu_accesscode = accesscode.c_str();
|
||||
bambu_serialnr = serialnr.c_str();
|
||||
autoSendToBambu = autoSend;
|
||||
autoSetBambuAmsCounter = autoSendTime.toInt();
|
||||
bambuCredentials.ip = ip.c_str();
|
||||
bambuCredentials.serial = serialnr.c_str();
|
||||
bambuCredentials.accesscode = accesscode.c_str();
|
||||
bambuCredentials.autosend_enable = autoSend;
|
||||
bambuCredentials.autosend_time = autoSendTime.toInt();
|
||||
|
||||
Preferences preferences;
|
||||
preferences.begin(NVS_NAMESPACE_BAMBU, false); // false = readwrite
|
||||
preferences.putString(NVS_KEY_BAMBU_IP, bambuCredentials.ip);
|
||||
preferences.putString(NVS_KEY_BAMBU_SERIAL, bambuCredentials.serial);
|
||||
preferences.putString(NVS_KEY_BAMBU_ACCESSCODE, bambuCredentials.accesscode);
|
||||
preferences.putBool(NVS_KEY_BAMBU_AUTOSEND_ENABLE, bambuCredentials.autosend_enable);
|
||||
preferences.putInt(NVS_KEY_BAMBU_AUTOSEND_TIME, bambuCredentials.autosend_time);
|
||||
preferences.end();
|
||||
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
if (!setupMqtt()) return false;
|
||||
@@ -68,35 +89,36 @@ bool saveBambuCredentials(const String& ip, const String& serialnr, const String
|
||||
}
|
||||
|
||||
bool loadBambuCredentials() {
|
||||
JsonDocument doc;
|
||||
if (loadJsonValue("/bambu_credentials.json", doc) && doc["bambu_ip"].is<String>()) {
|
||||
// Temporäre Strings für die Werte
|
||||
String ip = doc["bambu_ip"].as<String>();
|
||||
String code = doc["bambu_accesscode"].as<String>();
|
||||
String serial = doc["bambu_serialnr"].as<String>();
|
||||
Preferences preferences;
|
||||
preferences.begin(NVS_NAMESPACE_BAMBU, true);
|
||||
String ip = preferences.getString(NVS_KEY_BAMBU_IP, "");
|
||||
String serial = preferences.getString(NVS_KEY_BAMBU_SERIAL, "");
|
||||
String code = preferences.getString(NVS_KEY_BAMBU_ACCESSCODE, "");
|
||||
bool autosendEnable = preferences.getBool(NVS_KEY_BAMBU_AUTOSEND_ENABLE, false);
|
||||
int autosendTime = preferences.getInt(NVS_KEY_BAMBU_AUTOSEND_TIME, BAMBU_DEFAULT_AUTOSEND_TIME);
|
||||
preferences.end();
|
||||
|
||||
g_bambu_ip = ip;
|
||||
g_bambu_accesscode = code;
|
||||
g_bambu_serialnr = serial;
|
||||
if(ip != ""){
|
||||
bambuCredentials.ip = ip.c_str();
|
||||
bambuCredentials.serial = serial.c_str();
|
||||
bambuCredentials.accesscode = code.c_str();
|
||||
bambuCredentials.autosend_enable = autosendEnable;
|
||||
bambuCredentials.autosend_time = autosendTime;
|
||||
|
||||
if (doc["autoSendToBambu"].is<bool>()) autoSendToBambu = doc["autoSendToBambu"].as<bool>();
|
||||
if (doc["autoSendTime"].is<int>()) autoSetBambuAmsCounter = doc["autoSendTime"].as<int>();
|
||||
Serial.println("credentials loaded loadCredentials!");
|
||||
Serial.println(bambuCredentials.ip);
|
||||
Serial.println(bambuCredentials.serial);
|
||||
Serial.println(bambuCredentials.accesscode);
|
||||
Serial.println(String(bambuCredentials.autosend_enable));
|
||||
Serial.println(String(bambuCredentials.autosend_time));
|
||||
|
||||
ip.trim();
|
||||
code.trim();
|
||||
serial.trim();
|
||||
|
||||
// Dynamische Speicherallokation für die globalen Pointer
|
||||
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";
|
||||
return true;
|
||||
}
|
||||
Serial.println("Keine gültigen Bambu-Credentials gefunden.");
|
||||
return false;
|
||||
else
|
||||
{
|
||||
Serial.println("Keine gültigen Bambu-Credentials gefunden.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
struct FilamentResult {
|
||||
@@ -199,7 +221,7 @@ FilamentResult findFilamentIdx(String brand, String type) {
|
||||
bool sendMqttMessage(const String& payload) {
|
||||
Serial.println("Sending MQTT message");
|
||||
Serial.println(payload);
|
||||
if (client.publish(report_topic.c_str(), payload.c_str()))
|
||||
if (client.publish(("device/"+bambuCredentials.serial+"/request").c_str(), payload.c_str()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -341,7 +363,7 @@ void updateAmsWsData(JsonDocument& doc, JsonArray& amsArray, int& ams_count, Jso
|
||||
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>();
|
||||
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>();
|
||||
}
|
||||
}
|
||||
@@ -425,16 +447,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>())
|
||||
@@ -443,7 +457,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;
|
||||
|
||||
@@ -479,6 +493,12 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
|
||||
(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 (bambuCredentials.autosend_enable && autoSetToBambuSpoolId > 0 && hasChanges)
|
||||
{
|
||||
autoSetSpool(autoSetToBambuSpoolId, ams_data[storedIndex].trays[j].id);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -497,6 +517,11 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
|
||||
(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 (bambuCredentials.autosend_enable && autoSetToBambuSpoolId > 0 && hasChanges)
|
||||
{
|
||||
autoSetSpool(autoSetToBambuSpoolId, 254);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -550,10 +575,11 @@ void reconnect() {
|
||||
oledShowTopRow();
|
||||
|
||||
// Attempt to connect
|
||||
if (client.connect(bambu_serialnr, bambu_username, bambu_accesscode)) {
|
||||
String clientId = bambuCredentials.serial + "_" + String(random(0, 100));
|
||||
if (client.connect(clientId.c_str(), BAMBU_USERNAME, bambuCredentials.accesscode.c_str())) {
|
||||
Serial.println("MQTT re/connected");
|
||||
|
||||
client.subscribe(report_topic.c_str());
|
||||
client.subscribe(("device/"+bambuCredentials.serial+"/report").c_str());
|
||||
bambu_connected = true;
|
||||
oledShowTopRow();
|
||||
} else {
|
||||
@@ -569,6 +595,7 @@ void reconnect() {
|
||||
Serial.println("Disable Bambu MQTT Task after 5 retries");
|
||||
//vTaskSuspend(BambuMqttTask);
|
||||
vTaskDelete(BambuMqttTask);
|
||||
BambuMqttTask = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -599,31 +626,24 @@ void mqtt_loop(void * parameter) {
|
||||
|
||||
bool setupMqtt() {
|
||||
// Wenn Bambu Daten vorhanden
|
||||
bool success = loadBambuCredentials();
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
//bool success = loadBambuCredentials();
|
||||
|
||||
if (!success) {
|
||||
Serial.println("Failed to load Bambu credentials");
|
||||
oledShowMessage("Bambu Credentials Missing");
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (success && bambu_ip != "" && bambu_accesscode != "" && bambu_serialnr != "")
|
||||
if (bambuCredentials.ip != "" && bambuCredentials.accesscode != "" && bambuCredentials.serial != "")
|
||||
{
|
||||
oledShowProgressBar(4, 7, DISPLAY_BOOT_TEXT, "Bambu init");
|
||||
bambuDisabled = false;
|
||||
sslClient.setCACert(root_ca);
|
||||
sslClient.setInsecure();
|
||||
client.setServer(bambu_ip, 8883);
|
||||
client.setServer(bambuCredentials.ip.c_str(), 8883);
|
||||
|
||||
// Verbinden mit dem MQTT-Server
|
||||
bool connected = true;
|
||||
if (client.connect(bambu_serialnr, bambu_username, bambu_accesscode))
|
||||
String clientId = String(bambuCredentials.serial) + "_" + String(random(0, 100));
|
||||
if (client.connect(bambuCredentials.ip.c_str(), BAMBU_USERNAME, bambuCredentials.accesscode.c_str()))
|
||||
{
|
||||
client.setCallback(mqtt_callback);
|
||||
client.setBufferSize(5120);
|
||||
// Optional: Topic abonnieren
|
||||
client.subscribe(report_topic.c_str());
|
||||
//client.subscribe(request_topic.c_str());
|
||||
client.setBufferSize(15488);
|
||||
client.subscribe(("device/"+bambuCredentials.serial+"/report").c_str());
|
||||
Serial.println("MQTT-Client initialisiert");
|
||||
|
||||
oledShowMessage("Bambu Connected");
|
||||
@@ -646,24 +666,26 @@ bool setupMqtt() {
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
connected = false;
|
||||
oledShowTopRow();
|
||||
autoSetToBambuSpoolId = 0;
|
||||
}
|
||||
|
||||
if (!connected) return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("Fehler: Keine MQTT-Daten vorhanden");
|
||||
oledShowMessage("Bambu Credentials Missing");
|
||||
oledShowTopRow();
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
bambuDisabled = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void bambu_restart() {
|
||||
Serial.println("Bambu restart");
|
||||
|
||||
if (BambuMqttTask) {
|
||||
vTaskDelete(BambuMqttTask);
|
||||
BambuMqttTask = NULL;
|
||||
delay(10);
|
||||
}
|
||||
setupMqtt();
|
||||
}
|
13
src/bambu.h
@@ -16,6 +16,14 @@ struct TrayData {
|
||||
String cali_idx;
|
||||
};
|
||||
|
||||
struct BambuCredentials {
|
||||
String ip;
|
||||
String serial;
|
||||
String accesscode;
|
||||
bool autosend_enable;
|
||||
int autosend_time;
|
||||
};
|
||||
|
||||
#define MAX_AMS 17 // 16 normale AMS + 1 externe Spule
|
||||
extern String amsJsonData; // Für die vorbereiteten JSON-Daten
|
||||
|
||||
@@ -28,9 +36,12 @@ extern bool bambu_connected;
|
||||
|
||||
extern int ams_count;
|
||||
extern AMSData ams_data[MAX_AMS];
|
||||
extern bool autoSendToBambu;
|
||||
//extern bool autoSendToBambu;
|
||||
extern int autoSetToBambuSpoolId;
|
||||
extern bool bambuDisabled;
|
||||
extern BambuCredentials bambuCredentials;
|
||||
|
||||
bool removeBambuCredentials();
|
||||
bool loadBambuCredentials();
|
||||
bool saveBambuCredentials(const String& bambu_ip, const String& bambu_serialnr, const String& bambu_accesscode, const bool autoSend, const String& autoSendTime);
|
||||
bool setupMqtt();
|
||||
|
@@ -1,6 +1,20 @@
|
||||
#include "commonFS.h"
|
||||
#include <LittleFS.h>
|
||||
|
||||
bool removeJsonValue(const char* filename) {
|
||||
File file = LittleFS.open(filename, "r");
|
||||
if (!file) {
|
||||
return true;
|
||||
}
|
||||
file.close();
|
||||
if (!LittleFS.remove(filename)) {
|
||||
Serial.print("Fehler beim Löschen der Datei: ");
|
||||
Serial.println(filename);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool saveJsonValue(const char* filename, const JsonDocument& doc) {
|
||||
File file = LittleFS.open(filename, "w");
|
||||
if (!file) {
|
||||
|
@@ -5,6 +5,7 @@
|
||||
#include <ArduinoJson.h>
|
||||
#include <LittleFS.h>
|
||||
|
||||
bool removeJsonValue(const char* filename);
|
||||
bool saveJsonValue(const char* filename, const JsonDocument& doc);
|
||||
bool loadJsonValue(const char* filename, JsonDocument& doc);
|
||||
void initializeFileSystem();
|
||||
|
@@ -16,20 +16,20 @@ const uint8_t LOADCELL_DOUT_PIN = 16; //16;
|
||||
const uint8_t LOADCELL_SCK_PIN = 17; //17;
|
||||
const uint8_t calVal_eepromAdress = 0;
|
||||
const uint16_t SCALE_LEVEL_WEIGHT = 500;
|
||||
uint16_t defaultScaleCalibrationValue = 430;
|
||||
// ***** HX711
|
||||
|
||||
// ***** TTP223 (Touch Sensor)
|
||||
// TTP223 circuit wiring
|
||||
const uint8_t TTP223_PIN = 25;
|
||||
// ***** TTP223
|
||||
|
||||
|
||||
// ***** Display
|
||||
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
|
||||
// On an ESP32: 21(SDA), 22(SCL)
|
||||
const int8_t OLED_RESET = -1; // Reset pin # (or -1 if sharing Arduino reset pin)
|
||||
const uint8_t SCREEN_ADDRESS = 0x3C; ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
|
||||
const uint8_t SCREEN_WIDTH = 128; // OLED display width, in pixels
|
||||
const uint8_t SCREEN_HEIGHT = 64; // OLED display height, in pixels
|
||||
const uint8_t OLED_TOP_START = 0;
|
||||
const uint8_t OLED_TOP_END = 16;
|
||||
const uint8_t OLED_DATA_START = 17;
|
||||
const uint8_t OLED_DATA_END = SCREEN_HEIGHT;
|
||||
|
||||
// ***** Display
|
||||
|
||||
// ***** Webserver
|
||||
@@ -40,8 +40,6 @@ const uint8_t webserverPort = 80;
|
||||
const char* apiUrl = "/api/v1";
|
||||
// ***** API
|
||||
|
||||
// ***** Bambu Auto Set Spool
|
||||
uint8_t autoSetBambuAmsCounter = 60;
|
||||
// ***** Bambu Auto Set Spool
|
||||
|
||||
// ***** Task Prios
|
||||
|
42
src/config.h
@@ -3,6 +3,40 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#define BAMBU_DEFAULT_AUTOSEND_TIME 60
|
||||
|
||||
#define NVS_NAMESPACE_API "api"
|
||||
#define NVS_KEY_SPOOLMAN_URL "spoolmanUrl"
|
||||
#define NVS_KEY_OCTOPRINT_ENABLED "octoEnabled"
|
||||
#define NVS_KEY_OCTOPRINT_URL "octoUrl"
|
||||
#define NVS_KEY_OCTOPRINT_TOKEN "octoToken"
|
||||
|
||||
#define NVS_NAMESPACE_BAMBU "bambu"
|
||||
#define NVS_KEY_BAMBU_IP "bambuIp"
|
||||
#define NVS_KEY_BAMBU_ACCESSCODE "bambuCode"
|
||||
#define NVS_KEY_BAMBU_SERIAL "bambuSerial"
|
||||
#define NVS_KEY_BAMBU_AUTOSEND_ENABLE "autosendEnable"
|
||||
#define NVS_KEY_BAMBU_AUTOSEND_TIME "autosendTime"
|
||||
|
||||
#define NVS_NAMESPACE_SCALE "scale"
|
||||
#define NVS_KEY_CALIBRATION "cal_value"
|
||||
#define NVS_KEY_AUTOTARE "auto_tare"
|
||||
#define SCALE_DEFAULT_CALIBRATION_VALUE 430.0f;
|
||||
|
||||
#define BAMBU_USERNAME "bblp"
|
||||
|
||||
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
|
||||
#define SCREEN_ADDRESS 0x3CU // See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
|
||||
#define SCREEN_WIDTH 128U
|
||||
#define SCREEN_HEIGHT 64U
|
||||
#define SCREEN_TOP_BAR_HEIGHT 16U
|
||||
#define SCREEN_PROGRESS_BAR_HEIGHT 12U
|
||||
#define DISPLAY_BOOT_TEXT "FilaMan"
|
||||
|
||||
#define WIFI_CHECK_INTERVAL 60000U
|
||||
#define DISPLAY_UPDATE_INTERVAL 1000U
|
||||
#define SPOOLMAN_HEALTHCHECK_INTERVAL 60000U
|
||||
|
||||
extern const uint8_t PN532_IRQ;
|
||||
extern const uint8_t PN532_RESET;
|
||||
|
||||
@@ -11,10 +45,8 @@ extern const uint8_t LOADCELL_SCK_PIN;
|
||||
extern const uint8_t calVal_eepromAdress;
|
||||
extern const uint16_t SCALE_LEVEL_WEIGHT;
|
||||
|
||||
extern const int8_t OLED_RESET;
|
||||
extern const uint8_t SCREEN_ADDRESS;
|
||||
extern const uint8_t SCREEN_WIDTH;
|
||||
extern const uint8_t SCREEN_HEIGHT;
|
||||
extern const uint8_t TTP223_PIN;
|
||||
|
||||
extern const uint8_t OLED_TOP_START;
|
||||
extern const uint8_t OLED_TOP_END;
|
||||
extern const uint8_t OLED_DATA_START;
|
||||
@@ -23,7 +55,7 @@ extern const uint8_t OLED_DATA_END;
|
||||
extern const char* apiUrl;
|
||||
extern const uint8_t webserverPort;
|
||||
|
||||
extern uint8_t autoSetBambuAmsCounter;
|
||||
|
||||
|
||||
extern const unsigned char wifi_on[];
|
||||
extern const unsigned char wifi_off[];
|
||||
|
12
src/debug.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
|
||||
#ifdef ENABLE_HEAP_DEBUGGING
|
||||
#define HEAP_DEBUG_MESSAGE(location) printHeapDebugData(location);
|
||||
#else
|
||||
#define HEAP_DEBUG_MESSAGE(location)
|
||||
#endif
|
||||
|
||||
inline void printHeapDebugData(const char *location){
|
||||
Serial.println("Heap: " + String(ESP.getMinFreeHeap()/1024) + "\t" + String(ESP.getFreeHeap()/1024) + "\t" + String(ESP.getMaxAllocHeap()/1024) + "\t" + location);
|
||||
}
|
@@ -2,10 +2,12 @@
|
||||
#include "api.h"
|
||||
#include <vector>
|
||||
#include "icons.h"
|
||||
#include "main.h"
|
||||
|
||||
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
|
||||
|
||||
bool wifiOn = false;
|
||||
bool iconToggle = false;
|
||||
|
||||
void setupDisplay() {
|
||||
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
|
||||
@@ -14,15 +16,10 @@ void setupDisplay() {
|
||||
}
|
||||
display.setTextColor(WHITE);
|
||||
display.clearDisplay();
|
||||
display.display();
|
||||
|
||||
// Show initial display buffer contents on the screen --
|
||||
// the library initializes this with an Adafruit splash screen.
|
||||
display.setTextColor(WHITE);
|
||||
display.display();
|
||||
|
||||
oledShowTopRow();
|
||||
oledShowMessage("FilaMan v" + String(VERSION));
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
oledShowProgressBar(0, 7, DISPLAY_BOOT_TEXT, "Display init");
|
||||
}
|
||||
|
||||
void oledclearline() {
|
||||
@@ -45,14 +42,14 @@ void oledcleardata() {
|
||||
//display.display();
|
||||
}
|
||||
|
||||
int oled_center_h(String text) {
|
||||
int oled_center_h(const String &text) {
|
||||
int16_t x1, y1;
|
||||
uint16_t w, h;
|
||||
display.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
|
||||
return (SCREEN_WIDTH - w) / 2;
|
||||
}
|
||||
|
||||
int oled_center_v(String text) {
|
||||
int oled_center_v(const String &text) {
|
||||
int16_t x1, y1;
|
||||
uint16_t w, h;
|
||||
display.getTextBounds(text, 0, OLED_DATA_START, &x1, &y1, &w, &h);
|
||||
@@ -60,7 +57,7 @@ int oled_center_v(String text) {
|
||||
return OLED_DATA_START + ((OLED_DATA_END - OLED_DATA_START - h) / 2);
|
||||
}
|
||||
|
||||
std::vector<String> splitTextIntoLines(String text, uint8_t textSize) {
|
||||
std::vector<String> splitTextIntoLines(const String &text, uint8_t textSize) {
|
||||
std::vector<String> lines;
|
||||
display.setTextSize(textSize);
|
||||
|
||||
@@ -120,7 +117,7 @@ std::vector<String> splitTextIntoLines(String text, uint8_t textSize) {
|
||||
return lines;
|
||||
}
|
||||
|
||||
void oledShowMultilineMessage(String message, uint8_t size) {
|
||||
void oledShowMultilineMessage(const String &message, uint8_t size) {
|
||||
std::vector<String> lines;
|
||||
int maxLines = 3; // Maximale Anzahl Zeilen für size 2
|
||||
|
||||
@@ -148,7 +145,7 @@ void oledShowMultilineMessage(String message, uint8_t size) {
|
||||
display.display();
|
||||
}
|
||||
|
||||
void oledShowMessage(String message, uint8_t size) {
|
||||
void oledShowMessage(const String &message, uint8_t size) {
|
||||
oledcleardata();
|
||||
display.setTextSize(size);
|
||||
display.setTextWrap(false);
|
||||
@@ -171,22 +168,46 @@ void oledShowMessage(String message, uint8_t size) {
|
||||
void oledShowTopRow() {
|
||||
oledclearline();
|
||||
|
||||
if (bambu_connected == 1) {
|
||||
display.drawBitmap(50, 0, bitmap_bambu_on , 16, 16, WHITE);
|
||||
} else {
|
||||
display.drawBitmap(50, 0, bitmap_off , 16, 16, WHITE);
|
||||
}
|
||||
display.setTextSize(1);
|
||||
display.setCursor(0, 4);
|
||||
display.print("v");
|
||||
display.print(VERSION);
|
||||
|
||||
if (spoolman_connected == 1) {
|
||||
display.drawBitmap(80, 0, bitmap_spoolman_on , 16, 16, WHITE);
|
||||
} else {
|
||||
display.drawBitmap(80, 0, bitmap_off , 16, 16, WHITE);
|
||||
}
|
||||
iconToggle = !iconToggle;
|
||||
|
||||
if (wifiOn == 1) {
|
||||
display.drawBitmap(107, 0, wifi_on , 16, 16, WHITE);
|
||||
} else {
|
||||
display.drawBitmap(107, 0, wifi_off , 16, 16, WHITE);
|
||||
// Do not show status indicators during boot
|
||||
if(!booting){
|
||||
if(bambuDisabled == false) {
|
||||
if (bambu_connected == 1) {
|
||||
display.drawBitmap(50, 0, bitmap_bambu_on , 16, 16, WHITE);
|
||||
} else {
|
||||
if(iconToggle){
|
||||
display.drawBitmap(50, 0, bitmap_bambu_on , 16, 16, WHITE);
|
||||
display.drawLine(50, 15, 66, 0, WHITE);
|
||||
display.drawLine(51, 15, 67, 0, WHITE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (spoolmanConnected) {
|
||||
display.drawBitmap(80, 0, bitmap_spoolman_on , 16, 16, WHITE);
|
||||
} else {
|
||||
if(iconToggle){
|
||||
display.drawBitmap(80, 0, bitmap_spoolman_on , 16, 16, WHITE);
|
||||
display.drawLine(80, 15, 96, 0, WHITE);
|
||||
display.drawLine(81, 15, 97, 0, WHITE);
|
||||
}
|
||||
}
|
||||
|
||||
if (wifiOn == 1) {
|
||||
display.drawBitmap(107, 0, wifi_on , 16, 16, WHITE);
|
||||
} else {
|
||||
if(iconToggle){
|
||||
display.drawBitmap(107, 0, wifi_on , 16, 16, WHITE);
|
||||
display.drawLine(107, 15, 123, 0, WHITE);
|
||||
display.drawLine(108, 15, 124, 0, WHITE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
display.display();
|
||||
@@ -214,6 +235,27 @@ void oledShowIcon(const char* icon) {
|
||||
display.display();
|
||||
}
|
||||
|
||||
void oledShowProgressBar(const uint8_t step, const uint8_t numSteps, const char* largeText, const char* statusMessage) {
|
||||
assert(step <= numSteps);
|
||||
|
||||
// clear data and bar area
|
||||
display.fillRect(0, OLED_DATA_START, SCREEN_WIDTH, SCREEN_HEIGHT-16, BLACK);
|
||||
|
||||
|
||||
display.setTextWrap(false);
|
||||
display.setTextSize(2);
|
||||
display.setCursor(0, OLED_DATA_START+4);
|
||||
display.print(largeText);
|
||||
display.setTextSize(1);
|
||||
display.setCursor(0, OLED_DATA_END-SCREEN_PROGRESS_BAR_HEIGHT-10);
|
||||
display.print(statusMessage);
|
||||
|
||||
const int barLength = ((SCREEN_WIDTH-2)*step)/numSteps;
|
||||
display.drawRoundRect(0, SCREEN_HEIGHT-SCREEN_PROGRESS_BAR_HEIGHT, SCREEN_WIDTH, 12, 6, WHITE);
|
||||
display.fillRoundRect(1, SCREEN_HEIGHT-SCREEN_PROGRESS_BAR_HEIGHT+1, barLength, 10, 6, WHITE);
|
||||
display.display();
|
||||
}
|
||||
|
||||
void oledShowWeight(uint16_t weight) {
|
||||
// Display Gewicht
|
||||
oledcleardata();
|
||||
|
@@ -13,11 +13,13 @@ extern bool wifiOn;
|
||||
void setupDisplay();
|
||||
void oledclearline();
|
||||
void oledcleardata();
|
||||
int oled_center_h(String text);
|
||||
int oled_center_v(String text);
|
||||
int oled_center_h(const String &text);
|
||||
int oled_center_v(const String &text);
|
||||
|
||||
void oledShowProgressBar(const uint8_t step, const uint8_t numSteps, const char* largeText, const char* statusMessage);
|
||||
|
||||
void oledShowWeight(uint16_t weight);
|
||||
void oledShowMessage(String message, uint8_t size = 2);
|
||||
void oledShowMessage(const String &message, uint8_t size = 2);
|
||||
void oledShowTopRow();
|
||||
void oledShowIcon(const char* icon);
|
||||
|
||||
|
236
src/main.cpp
@@ -13,6 +13,11 @@
|
||||
#include "esp_task_wdt.h"
|
||||
#include "commonFS.h"
|
||||
|
||||
bool mainTaskWasPaused = 0;
|
||||
uint8_t scaleTareCounter = 0;
|
||||
bool touchSensorConnected = false;
|
||||
bool booting = true;
|
||||
|
||||
// ##### SETUP #####
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
@@ -36,7 +41,6 @@ void setup() {
|
||||
setupWebserver(server);
|
||||
|
||||
// Spoolman API
|
||||
// api.cpp
|
||||
initSpoolman();
|
||||
|
||||
// Bambu MQTT
|
||||
@@ -45,32 +49,25 @@ void setup() {
|
||||
// NFC Reader
|
||||
startNfc();
|
||||
|
||||
uint8_t scaleCalibrated = start_scale();
|
||||
if (scaleCalibrated == 3) {
|
||||
oledShowMessage("Scale not calibrated!");
|
||||
for (uint16_t i = 0; i < 50000; i++) {
|
||||
yield();
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
} else if (scaleCalibrated == 0) {
|
||||
oledShowMessage("HX711 not found");
|
||||
for (uint16_t i = 0; i < 50000; i++) {
|
||||
yield();
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
// Touch Sensor
|
||||
pinMode(TTP223_PIN, INPUT_PULLUP);
|
||||
if (digitalRead(TTP223_PIN) == LOW)
|
||||
{
|
||||
Serial.println("Touch Sensor is connected");
|
||||
touchSensorConnected = true;
|
||||
}
|
||||
|
||||
|
||||
// Scale
|
||||
start_scale(touchSensorConnected);
|
||||
scale.tare();
|
||||
|
||||
// WDT initialisieren mit 10 Sekunden Timeout
|
||||
bool panic = true; // Wenn true, löst ein WDT-Timeout einen System-Panik aus
|
||||
esp_task_wdt_init(10, panic);
|
||||
esp_task_wdt_init(10, panic);
|
||||
|
||||
booting = false;
|
||||
// Aktuellen Task (loopTask) zum Watchdog hinzufügen
|
||||
esp_task_wdt_add(NULL);
|
||||
|
||||
// Optional: Andere Tasks zum Watchdog hinzufügen, falls nötig
|
||||
// esp_task_wdt_add(task_handle);
|
||||
}
|
||||
|
||||
|
||||
@@ -99,29 +96,61 @@ uint8_t autoAmsCounter = 0;
|
||||
uint8_t weightSend = 0;
|
||||
int16_t lastWeight = 0;
|
||||
|
||||
// WIFI check variables
|
||||
unsigned long lastWifiCheckTime = 0;
|
||||
const unsigned long wifiCheckInterval = 60000; // Überprüfe alle 60 Sekunden (60000 ms)
|
||||
unsigned long lastTopRowUpdateTime = 0;
|
||||
unsigned long lastSpoolmanHealcheckTime = 0;
|
||||
|
||||
// Button debounce variables
|
||||
unsigned long lastButtonPress = 0;
|
||||
const unsigned long debounceDelay = 500; // 500 ms debounce delay
|
||||
|
||||
// ##### PROGRAM START #####
|
||||
void loop() {
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
// Überprüfe den Status des Touch Sensors
|
||||
if (touchSensorConnected && digitalRead(TTP223_PIN) == HIGH && currentMillis - lastButtonPress > debounceDelay)
|
||||
{
|
||||
lastButtonPress = currentMillis;
|
||||
scaleTareRequest = true;
|
||||
}
|
||||
|
||||
// Überprüfe regelmäßig die WLAN-Verbindung
|
||||
if (intervalElapsed(currentMillis, lastWifiCheckTime, wifiCheckInterval)) {
|
||||
if (intervalElapsed(currentMillis, lastWifiCheckTime, WIFI_CHECK_INTERVAL))
|
||||
{
|
||||
checkWiFiConnection();
|
||||
}
|
||||
|
||||
// Periodic display update
|
||||
if (intervalElapsed(currentMillis, lastTopRowUpdateTime, DISPLAY_UPDATE_INTERVAL))
|
||||
{
|
||||
oledShowTopRow();
|
||||
}
|
||||
|
||||
// Periodic spoolman health check
|
||||
if (intervalElapsed(currentMillis, lastSpoolmanHealcheckTime, SPOOLMAN_HEALTHCHECK_INTERVAL))
|
||||
{
|
||||
checkSpoolmanInstance();
|
||||
}
|
||||
|
||||
// Wenn Bambu auto set Spool aktiv
|
||||
if (autoSendToBambu && autoSetToBambuSpoolId > 0) {
|
||||
if (bambuCredentials.autosend_enable && autoSetToBambuSpoolId > 0)
|
||||
{
|
||||
if (!bambuDisabled && !bambu_connected)
|
||||
{
|
||||
bambu_restart();
|
||||
}
|
||||
|
||||
if (intervalElapsed(currentMillis, lastAutoSetBambuAmsTime, autoSetBambuAmsInterval))
|
||||
{
|
||||
if (hasReadRfidTag == 0)
|
||||
if (nfcReaderState == NFC_IDLE)
|
||||
{
|
||||
lastAutoSetBambuAmsTime = currentMillis;
|
||||
oledShowMessage("Auto Set " + String(autoSetBambuAmsCounter - autoAmsCounter) + "s");
|
||||
oledShowMessage("Auto Set " + String(bambuCredentials.autosend_time - autoAmsCounter) + "s");
|
||||
autoAmsCounter++;
|
||||
|
||||
if (autoAmsCounter >= autoSetBambuAmsCounter)
|
||||
if (autoAmsCounter >= bambuCredentials.autosend_time)
|
||||
{
|
||||
autoSetToBambuSpoolId = 0;
|
||||
autoAmsCounter = 0;
|
||||
@@ -135,76 +164,119 @@ void loop() {
|
||||
}
|
||||
}
|
||||
|
||||
// Wenn Waage nicht Kalibriert
|
||||
if (scaleCalibrated == 3)
|
||||
// If scale is not calibrated, only show a warning
|
||||
if (!scaleCalibrated)
|
||||
{
|
||||
oledShowMessage("Scale not calibrated!");
|
||||
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
||||
yield();
|
||||
esp_task_wdt_reset();
|
||||
|
||||
return;
|
||||
// Do not show the warning if the calibratin process is onging
|
||||
if(!scaleCalibrationActive){
|
||||
oledShowMessage("Scale not calibrated");
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
// Ausgabe der Waage auf Display
|
||||
if (pauseMainTask == 0 && weight != lastWeight && hasReadRfidTag == 0 && (!autoSendToBambu || autoSetToBambuSpoolId == 0))
|
||||
else
|
||||
{
|
||||
(weight < 2) ? ((weight < -2) ? oledShowMessage("!! -0") : oledShowWeight(0)) : oledShowWeight(weight);
|
||||
}
|
||||
|
||||
|
||||
// Wenn Timer abgelaufen und nicht gerade ein RFID-Tag geschrieben wird
|
||||
if (currentMillis - lastWeightReadTime >= weightReadInterval && hasReadRfidTag < 3)
|
||||
{
|
||||
lastWeightReadTime = currentMillis;
|
||||
|
||||
// Prüfen ob die Waage korrekt genullt ist
|
||||
if ((weight > 0 && weight < 5) || weight < 0)
|
||||
// Ausgabe der Waage auf Display
|
||||
if(pauseMainTask == 0)
|
||||
{
|
||||
scale_tare_counter++;
|
||||
// Use filtered weight for smooth display, but still check API weight for significant changes
|
||||
int16_t displayWeight = getFilteredDisplayWeight();
|
||||
if (mainTaskWasPaused || (weight != lastWeight && nfcReaderState == NFC_IDLE && (!bambuCredentials.autosend_enable || autoSetToBambuSpoolId == 0)))
|
||||
{
|
||||
(displayWeight < 2) ? ((displayWeight < -2) ? oledShowMessage("!! -0") : oledShowWeight(0)) : oledShowWeight(displayWeight);
|
||||
}
|
||||
mainTaskWasPaused = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
scale_tare_counter = 0;
|
||||
mainTaskWasPaused = true;
|
||||
}
|
||||
|
||||
// Prüfen ob das Gewicht gleich bleibt und dann senden
|
||||
if (weight == lastWeight && weight > 5)
|
||||
|
||||
// Wenn Timer abgelaufen und nicht gerade ein RFID-Tag geschrieben wird
|
||||
if (currentMillis - lastWeightReadTime >= weightReadInterval && nfcReaderState < NFC_WRITING)
|
||||
{
|
||||
weigthCouterToApi++;
|
||||
}
|
||||
else
|
||||
lastWeightReadTime = currentMillis;
|
||||
|
||||
// Prüfen ob die Waage korrekt genullt ist
|
||||
// Abweichung von 2g ignorieren
|
||||
if (autoTare && (weight > 2 && weight < 7) || weight < -2)
|
||||
{
|
||||
scale_tare_counter++;
|
||||
}
|
||||
else
|
||||
{
|
||||
scale_tare_counter = 0;
|
||||
}
|
||||
|
||||
// Prüfen ob das Gewicht gleich bleibt und dann senden
|
||||
if (abs(weight - lastWeight) <= 2 && weight > 5)
|
||||
{
|
||||
weigthCouterToApi++;
|
||||
}
|
||||
else
|
||||
{
|
||||
weigthCouterToApi = 0;
|
||||
weightSend = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// reset weight counter after writing tag
|
||||
// TBD: what exactly is the logic behind this?
|
||||
if (currentMillis - lastWeightReadTime >= weightReadInterval && nfcReaderState != NFC_IDLE && nfcReaderState != NFC_READ_SUCCESS)
|
||||
{
|
||||
weigthCouterToApi = 0;
|
||||
weightSend = 0;
|
||||
}
|
||||
}
|
||||
|
||||
lastWeight = weight;
|
||||
|
||||
// reset weight counter after writing tag
|
||||
if (currentMillis - lastWeightReadTime >= weightReadInterval && hasReadRfidTag > 1)
|
||||
{
|
||||
weigthCouterToApi = 0;
|
||||
// Wenn ein Tag mit SM id erkannte wurde und der Waage Counter anspricht an SM Senden
|
||||
if (activeSpoolId != "" && weigthCouterToApi > 3 && weightSend == 0 && nfcReaderState == NFC_READ_SUCCESS && tagProcessed == false && spoolmanApiState == API_IDLE)
|
||||
{
|
||||
// set the current tag as processed to prevent it beeing processed again
|
||||
tagProcessed = true;
|
||||
|
||||
if (updateSpoolWeight(activeSpoolId, weight))
|
||||
{
|
||||
weightSend = 1;
|
||||
|
||||
// Set Bambu spool ID for auto-send if enabled
|
||||
if (bambuCredentials.autosend_enable)
|
||||
{
|
||||
autoSetToBambuSpoolId = activeSpoolId.toInt();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
oledShowIcon("failed");
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle successful tag write: Send weight to Spoolman but NEVER auto-send to Bambu
|
||||
if (activeSpoolId != "" && weigthCouterToApi > 3 && weightSend == 0 && nfcReaderState == NFC_WRITE_SUCCESS && tagProcessed == false && spoolmanApiState == API_IDLE)
|
||||
{
|
||||
// set the current tag as processed to prevent it beeing processed again
|
||||
tagProcessed = true;
|
||||
|
||||
if (updateSpoolWeight(activeSpoolId, weight))
|
||||
{
|
||||
weightSend = 1;
|
||||
Serial.println("Tag written: Weight sent to Spoolman, but NO auto-send to Bambu");
|
||||
// INTENTIONALLY do NOT set autoSetToBambuSpoolId here to prevent Bambu auto-send
|
||||
}
|
||||
else
|
||||
{
|
||||
oledShowIcon("failed");
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
if(octoEnabled && sendOctoUpdate && spoolmanApiState == API_IDLE)
|
||||
{
|
||||
updateSpoolOcto(autoSetToBambuSpoolId);
|
||||
sendOctoUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
lastWeight = weight;
|
||||
|
||||
// Wenn ein Tag mit SM id erkannte wurde und der Waage Counter anspricht an SM Senden
|
||||
if (spoolId != "" && weigthCouterToApi > 3 && weightSend == 0 && hasReadRfidTag == 1) {
|
||||
oledShowIcon("loading");
|
||||
if (updateSpoolWeight(spoolId, weight))
|
||||
{
|
||||
oledShowIcon("success");
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
weightSend = 1;
|
||||
autoSetToBambuSpoolId = spoolId.toInt();
|
||||
}
|
||||
else
|
||||
{
|
||||
oledShowIcon("failed");
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
yield();
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
|
9
src/main.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#ifndef MAIN_H
|
||||
#define MAIN_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
|
||||
extern bool booting;
|
||||
|
||||
#endif
|
1939
src/nfc.cpp
23
src/nfc.h
@@ -3,14 +3,31 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
typedef enum{
|
||||
NFC_IDLE,
|
||||
NFC_READING,
|
||||
NFC_READ_SUCCESS,
|
||||
NFC_READ_ERROR,
|
||||
NFC_WRITING,
|
||||
NFC_WRITE_SUCCESS,
|
||||
NFC_WRITE_ERROR
|
||||
} nfcReaderStateType;
|
||||
|
||||
void startNfc();
|
||||
void scanRfidTask(void * parameter);
|
||||
void startWriteJsonToTag(const char* payload);
|
||||
void startWriteJsonToTag(const bool isSpoolTag, const char* payload);
|
||||
bool quickSpoolIdCheck(String uidString);
|
||||
bool readCompleteJsonForFastPath(); // Read complete JSON data for fast-path web interface display
|
||||
|
||||
extern TaskHandle_t RfidReaderTask;
|
||||
extern String nfcJsonData;
|
||||
extern String spoolId;
|
||||
extern volatile uint8_t hasReadRfidTag;
|
||||
extern String activeSpoolId;
|
||||
extern String lastSpoolId;
|
||||
extern volatile nfcReaderStateType nfcReaderState;
|
||||
extern volatile bool pauseBambuMqttTask;
|
||||
extern volatile bool nfcWriteInProgress;
|
||||
extern bool tagProcessed;
|
||||
|
||||
|
||||
|
||||
#endif
|
73
src/ota.cpp
@@ -1,6 +1,10 @@
|
||||
#include <Arduino.h>
|
||||
#include <website.h>
|
||||
#include <commonFS.h>
|
||||
#include "scale.h"
|
||||
#include "bambu.h"
|
||||
#include "nfc.h"
|
||||
|
||||
|
||||
// Globale Variablen für Config Backups hinzufügen
|
||||
String bambuCredentialsBackup;
|
||||
@@ -14,6 +18,34 @@ 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
|
||||
@@ -111,8 +143,37 @@ void handleUpdate(AsyncWebServer &server) {
|
||||
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) {
|
||||
|
||||
// Disable all Tasks
|
||||
if (BambuMqttTask != NULL)
|
||||
{
|
||||
Serial.println("Delete BambuMqttTask");
|
||||
vTaskDelete(BambuMqttTask);
|
||||
BambuMqttTask = NULL;
|
||||
}
|
||||
if (ScaleTask) {
|
||||
Serial.println("Delete ScaleTask");
|
||||
vTaskDelete(ScaleTask);
|
||||
ScaleTask = NULL;
|
||||
}
|
||||
if (RfidReaderTask) {
|
||||
Serial.println("Delete RfidReaderTask");
|
||||
vTaskDelete(RfidReaderTask);
|
||||
RfidReaderTask = NULL;
|
||||
}
|
||||
|
||||
if (!index) {
|
||||
updateTotalSize = request->contentLength();
|
||||
updateWritten = 0;
|
||||
@@ -121,9 +182,9 @@ void handleUpdate(AsyncWebServer &server) {
|
||||
if (isSpiffsUpdate) {
|
||||
// Backup vor dem Update
|
||||
sendUpdateProgress(0, "backup", "Backing up configurations...");
|
||||
delay(200);
|
||||
vTaskDelay(200 / portTICK_PERIOD_MS);
|
||||
backupJsonConfigs();
|
||||
delay(200);
|
||||
vTaskDelay(200 / portTICK_PERIOD_MS);
|
||||
|
||||
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)) {
|
||||
@@ -131,14 +192,14 @@ void handleUpdate(AsyncWebServer &server) {
|
||||
return;
|
||||
}
|
||||
sendUpdateProgress(5, "starting", "Starting SPIFFS update...");
|
||||
delay(200);
|
||||
vTaskDelay(200 / portTICK_PERIOD_MS);
|
||||
} 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);
|
||||
vTaskDelay(200 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,8 +224,8 @@ void handleUpdate(AsyncWebServer &server) {
|
||||
static int lastProgress = -1;
|
||||
if (currentProgress != lastProgress && (currentProgress % 10 == 0 || final)) {
|
||||
sendUpdateProgress(currentProgress, "uploading");
|
||||
oledShowMessage("Update: " + String(currentProgress) + "%");
|
||||
delay(50);
|
||||
oledShowProgressBar(currentProgress, 100, "Update", "Download");
|
||||
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||
lastProgress = currentProgress;
|
||||
}
|
||||
}
|
||||
|
324
src/scale.cpp
@@ -13,19 +13,135 @@ TaskHandle_t ScaleTask;
|
||||
|
||||
int16_t weight = 0;
|
||||
|
||||
// Weight stabilization variables
|
||||
#define MOVING_AVERAGE_SIZE 8 // Reduced from 20 to 8 for faster response
|
||||
#define LOW_PASS_ALPHA 0.3f // Increased from 0.15 to 0.3 for faster tracking
|
||||
#define DISPLAY_THRESHOLD 0.3f // Reduced from 0.5 to 0.3g for more responsive display
|
||||
#define API_THRESHOLD 1.5f // Reduced from 2.0 to 1.5g for faster API actions
|
||||
#define MEASUREMENT_INTERVAL_MS 30 // Reduced from 50ms to 30ms for faster updates
|
||||
|
||||
float weightBuffer[MOVING_AVERAGE_SIZE];
|
||||
uint8_t bufferIndex = 0;
|
||||
bool bufferFilled = false;
|
||||
float filteredWeight = 0.0f;
|
||||
int16_t lastDisplayedWeight = 0;
|
||||
int16_t lastStableWeight = 0; // For API/action triggering
|
||||
unsigned long lastMeasurementTime = 0;
|
||||
|
||||
uint8_t weigthCouterToApi = 0;
|
||||
uint8_t scale_tare_counter = 0;
|
||||
bool scaleTareRequest = false;
|
||||
uint8_t pauseMainTask = 0;
|
||||
uint8_t scaleCalibrated = 1;
|
||||
bool scaleCalibrated;
|
||||
bool autoTare = true;
|
||||
bool scaleCalibrationActive = false;
|
||||
|
||||
Preferences preferences;
|
||||
const char* NVS_NAMESPACE = "scale";
|
||||
const char* NVS_KEY_CALIBRATION = "cal_value";
|
||||
// ##### Weight stabilization functions #####
|
||||
|
||||
/**
|
||||
* Reset weight filter buffer - call after tare or calibration
|
||||
*/
|
||||
void resetWeightFilter() {
|
||||
bufferIndex = 0;
|
||||
bufferFilled = false;
|
||||
filteredWeight = 0.0f;
|
||||
lastDisplayedWeight = 0;
|
||||
lastStableWeight = 0; // Reset stable weight for API actions
|
||||
|
||||
// Initialize buffer with zeros
|
||||
for (int i = 0; i < MOVING_AVERAGE_SIZE; i++) {
|
||||
weightBuffer[i] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate moving average from weight buffer
|
||||
*/
|
||||
float calculateMovingAverage() {
|
||||
float sum = 0.0f;
|
||||
int count = bufferFilled ? MOVING_AVERAGE_SIZE : bufferIndex;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
sum += weightBuffer[i];
|
||||
}
|
||||
|
||||
return (count > 0) ? sum / count : 0.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply low-pass filter to smooth weight readings
|
||||
* Uses exponential smoothing: y_new = alpha * x_new + (1-alpha) * y_old
|
||||
*/
|
||||
float applyLowPassFilter(float newValue) {
|
||||
filteredWeight = LOW_PASS_ALPHA * newValue + (1.0f - LOW_PASS_ALPHA) * filteredWeight;
|
||||
return filteredWeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process new weight reading with stabilization
|
||||
* Returns stabilized weight value
|
||||
*/
|
||||
int16_t processWeightReading(float rawWeight) {
|
||||
// Add to moving average buffer
|
||||
weightBuffer[bufferIndex] = rawWeight;
|
||||
bufferIndex = (bufferIndex + 1) % MOVING_AVERAGE_SIZE;
|
||||
|
||||
if (bufferIndex == 0) {
|
||||
bufferFilled = true;
|
||||
}
|
||||
|
||||
// Calculate moving average
|
||||
float avgWeight = calculateMovingAverage();
|
||||
|
||||
// Apply low-pass filter
|
||||
float smoothedWeight = applyLowPassFilter(avgWeight);
|
||||
|
||||
// Round to nearest gram
|
||||
int16_t newWeight = round(smoothedWeight);
|
||||
|
||||
// Update displayed weight if display threshold is reached
|
||||
if (abs(newWeight - lastDisplayedWeight) >= DISPLAY_THRESHOLD) {
|
||||
lastDisplayedWeight = newWeight;
|
||||
}
|
||||
|
||||
// Update global weight for API actions only if stable threshold is reached
|
||||
int16_t weightToReturn = weight; // Default: keep current weight
|
||||
|
||||
if (abs(newWeight - lastStableWeight) >= API_THRESHOLD) {
|
||||
lastStableWeight = newWeight;
|
||||
weightToReturn = newWeight;
|
||||
}
|
||||
|
||||
return weightToReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current filtered weight for display purposes
|
||||
* This returns the smoothed weight even if it hasn't triggered API actions
|
||||
*/
|
||||
int16_t getFilteredDisplayWeight() {
|
||||
return lastDisplayedWeight;
|
||||
}
|
||||
|
||||
// ##### Funktionen für Waage #####
|
||||
uint8_t setAutoTare(bool autoTareValue) {
|
||||
Serial.print("Set AutoTare to ");
|
||||
Serial.println(autoTareValue);
|
||||
autoTare = autoTareValue;
|
||||
|
||||
// Speichern mit NVS
|
||||
Preferences preferences;
|
||||
preferences.begin(NVS_NAMESPACE_SCALE, false); // false = readwrite
|
||||
preferences.putBool(NVS_KEY_AUTOTARE, autoTare);
|
||||
preferences.end();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint8_t tareScale() {
|
||||
Serial.println("Tare scale");
|
||||
scale.tare();
|
||||
resetWeightFilter(); // Reset stabilization filter after tare
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -34,30 +150,86 @@ void scale_loop(void * parameter) {
|
||||
Serial.println("++++++++++++++++++++++++++++++");
|
||||
Serial.println("Scale Loop started");
|
||||
Serial.println("++++++++++++++++++++++++++++++");
|
||||
for(;;) {
|
||||
if (scale.is_ready())
|
||||
{
|
||||
// Waage nochmal Taren, wenn zu lange Abweichung
|
||||
if (scale_tare_counter >= 5)
|
||||
{
|
||||
scale.tare();
|
||||
scale_tare_counter = 0;
|
||||
}
|
||||
|
||||
weight = round(scale.get_units());
|
||||
// Initialize weight filter
|
||||
resetWeightFilter();
|
||||
lastMeasurementTime = millis();
|
||||
|
||||
for(;;) {
|
||||
unsigned long currentTime = millis();
|
||||
|
||||
// Only measure at defined intervals to reduce noise
|
||||
if (currentTime - lastMeasurementTime >= MEASUREMENT_INTERVAL_MS) {
|
||||
if (scale.is_ready())
|
||||
{
|
||||
// Waage automatisch Taren, wenn zu lange Abweichung
|
||||
if (autoTare && scale_tare_counter >= 5)
|
||||
{
|
||||
Serial.println("Auto Tare scale");
|
||||
scale.tare();
|
||||
resetWeightFilter(); // Reset filter after auto tare
|
||||
scale_tare_counter = 0;
|
||||
}
|
||||
|
||||
// Waage manuell Taren
|
||||
if (scaleTareRequest == true)
|
||||
{
|
||||
Serial.println("Re-Tare scale");
|
||||
oledShowMessage("TARE Scale");
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
scale.tare();
|
||||
resetWeightFilter(); // Reset filter after manual tare
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
oledShowWeight(0);
|
||||
scaleTareRequest = false;
|
||||
}
|
||||
|
||||
// Get raw weight reading
|
||||
float rawWeight = scale.get_units();
|
||||
|
||||
// Process weight with stabilization
|
||||
int16_t stabilizedWeight = processWeightReading(rawWeight);
|
||||
|
||||
// Update global weight variable only if it changed significantly (for API actions)
|
||||
if (stabilizedWeight != weight) {
|
||||
weight = stabilizedWeight;
|
||||
}
|
||||
|
||||
// Debug output for monitoring (can be removed in production)
|
||||
static unsigned long lastDebugTime = 0;
|
||||
if (currentTime - lastDebugTime > 2000) { // Print every 2 seconds
|
||||
lastDebugTime = currentTime;
|
||||
}
|
||||
|
||||
lastMeasurementTime = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(100)); // Verzögerung, um die CPU nicht zu überlasten
|
||||
vTaskDelay(pdMS_TO_TICKS(10)); // Shorter delay for more responsive loop
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t start_scale() {
|
||||
void start_scale(bool touchSensorConnected) {
|
||||
Serial.println("Prüfe Calibration Value");
|
||||
long calibrationValue;
|
||||
float calibrationValue;
|
||||
|
||||
// NVS lesen
|
||||
preferences.begin(NVS_NAMESPACE, true); // true = readonly
|
||||
calibrationValue = preferences.getLong(NVS_KEY_CALIBRATION, defaultScaleCalibrationValue);
|
||||
Preferences preferences;
|
||||
preferences.begin(NVS_NAMESPACE_SCALE, true); // true = readonly
|
||||
if(preferences.isKey(NVS_KEY_CALIBRATION)){
|
||||
calibrationValue = preferences.getFloat(NVS_KEY_CALIBRATION);
|
||||
scaleCalibrated = true;
|
||||
}else{
|
||||
calibrationValue = SCALE_DEFAULT_CALIBRATION_VALUE;
|
||||
scaleCalibrated = false;
|
||||
}
|
||||
|
||||
// auto Tare
|
||||
// Wenn Touch Sensor verbunden, dann autoTare auf false setzen
|
||||
// Danach prüfen was in NVS gespeichert ist
|
||||
autoTare = (touchSensorConnected) ? false : true;
|
||||
autoTare = preferences.getBool(NVS_KEY_AUTOTARE, autoTare);
|
||||
|
||||
preferences.end();
|
||||
|
||||
Serial.print("Read Scale Calibration Value ");
|
||||
@@ -65,24 +237,24 @@ uint8_t start_scale() {
|
||||
|
||||
scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
|
||||
|
||||
if (isnan(calibrationValue) || calibrationValue < 1) {
|
||||
calibrationValue = defaultScaleCalibrationValue;
|
||||
scaleCalibrated = 0;
|
||||
}
|
||||
|
||||
oledShowMessage("Scale Tare Please remove all");
|
||||
for (uint16_t i = 0; i < 2000; i++) {
|
||||
oledShowProgressBar(6, 7, DISPLAY_BOOT_TEXT, "Tare scale");
|
||||
for (uint16_t i = 0; i < 3000; i++) {
|
||||
yield();
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
|
||||
if (scale.wait_ready_timeout(1000))
|
||||
{
|
||||
scale.set_scale(calibrationValue); // this value is obtained by calibrating the scale with known weights; see the README for details
|
||||
scale.tare();
|
||||
while(!scale.is_ready()) {
|
||||
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||
}
|
||||
|
||||
scale.set_scale(calibrationValue);
|
||||
//vTaskDelay(pdMS_TO_TICKS(5000));
|
||||
//scale.tare();
|
||||
|
||||
// Initialize weight stabilization filter
|
||||
resetWeightFilter();
|
||||
|
||||
// Display Gewicht
|
||||
oledShowWeight(0);
|
||||
|
||||
@@ -90,7 +262,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. */
|
||||
@@ -101,22 +273,25 @@ uint8_t start_scale() {
|
||||
} else {
|
||||
Serial.println("ScaleLoop-Task erfolgreich erstellt");
|
||||
}
|
||||
|
||||
return (scaleCalibrated == 1) ? 1 : 3;
|
||||
}
|
||||
|
||||
uint8_t calibrate_scale() {
|
||||
long newCalibrationValue;
|
||||
uint8_t returnState = 0;
|
||||
float newCalibrationValue;
|
||||
|
||||
scaleCalibrationActive = true;
|
||||
|
||||
vTaskSuspend(RfidReaderTask);
|
||||
vTaskSuspend(ScaleTask);
|
||||
|
||||
//vTaskSuspend(RfidReaderTask);
|
||||
vTaskDelete(RfidReaderTask);
|
||||
pauseBambuMqttTask = true;
|
||||
pauseMainTask = 1;
|
||||
|
||||
|
||||
if (scale.wait_ready_timeout(1000))
|
||||
{
|
||||
|
||||
scale.set_scale();
|
||||
oledShowMessage("Step 1 empty Scale");
|
||||
oledShowProgressBar(0, 3, "Scale Cal.", "Empty Scale");
|
||||
|
||||
for (uint16_t i = 0; i < 5000; i++) {
|
||||
yield();
|
||||
@@ -128,7 +303,7 @@ uint8_t calibrate_scale() {
|
||||
Serial.println("Tare done...");
|
||||
Serial.print("Place a known weight on the scale...");
|
||||
|
||||
oledShowMessage("Step 2 Place the weight");
|
||||
oledShowProgressBar(1, 3, "Scale Cal.", "Place the weight");
|
||||
|
||||
for (uint16_t i = 0; i < 5000; i++) {
|
||||
yield();
|
||||
@@ -136,7 +311,7 @@ uint8_t calibrate_scale() {
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
|
||||
long newCalibrationValue = scale.get_units(10);
|
||||
float newCalibrationValue = scale.get_units(10);
|
||||
Serial.print("Result: ");
|
||||
Serial.println(newCalibrationValue);
|
||||
|
||||
@@ -148,21 +323,34 @@ uint8_t calibrate_scale() {
|
||||
Serial.println(newCalibrationValue);
|
||||
|
||||
// Speichern mit NVS
|
||||
preferences.begin(NVS_NAMESPACE, false); // false = readwrite
|
||||
preferences.putLong(NVS_KEY_CALIBRATION, newCalibrationValue);
|
||||
Preferences preferences;
|
||||
preferences.begin(NVS_NAMESPACE_SCALE, false); // false = readwrite
|
||||
preferences.putFloat(NVS_KEY_CALIBRATION, newCalibrationValue);
|
||||
preferences.end();
|
||||
|
||||
// Verifizieren
|
||||
preferences.begin(NVS_NAMESPACE, true);
|
||||
long verifyValue = preferences.getLong(NVS_KEY_CALIBRATION, 0);
|
||||
preferences.begin(NVS_NAMESPACE_SCALE, true);
|
||||
float verifyValue = preferences.getFloat(NVS_KEY_CALIBRATION, 0);
|
||||
preferences.end();
|
||||
|
||||
Serial.print("Verified stored value: ");
|
||||
Serial.println(verifyValue);
|
||||
|
||||
Serial.println("End calibration, revome weight");
|
||||
oledShowProgressBar(2, 3, "Scale Cal.", "Remove weight");
|
||||
|
||||
oledShowMessage("Remove weight");
|
||||
scale.set_scale(newCalibrationValue);
|
||||
resetWeightFilter(); // Reset filter after calibration
|
||||
for (uint16_t i = 0; i < 2000; i++) {
|
||||
yield();
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
|
||||
oledShowProgressBar(3, 3, "Scale Cal.", "Completed");
|
||||
|
||||
// For some reason it is not possible to re-tare the scale here, it will result in a wdt timeout. Instead let the scale loop do the taring
|
||||
//scale.tare();
|
||||
scaleTareRequest = true;
|
||||
|
||||
for (uint16_t i = 0; i < 2000; i++) {
|
||||
yield();
|
||||
@@ -170,32 +358,23 @@ uint8_t calibrate_scale() {
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
|
||||
oledShowMessage("Calibration done");
|
||||
|
||||
for (uint16_t i = 0; i < 2000; i++) {
|
||||
yield();
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
|
||||
//ESP.restart();
|
||||
scaleCalibrated = true;
|
||||
returnState = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
Serial.println("Calibration value is invalid. Please recalibrate.");
|
||||
Serial.println("Calibration value is invalid. Please recalibrate.");
|
||||
|
||||
oledShowMessage("Calibration ERROR Try again");
|
||||
oledShowProgressBar(3, 3, "Failure", "Calibration error");
|
||||
|
||||
for (uint16_t i = 0; i < 50000; i++) {
|
||||
yield();
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
return 0;
|
||||
for (uint16_t i = 0; i < 50000; i++) {
|
||||
yield();
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
returnState = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("HX711 not found.");
|
||||
@@ -207,17 +386,14 @@ uint8_t calibrate_scale() {
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
return 0;
|
||||
returnState = 0;
|
||||
}
|
||||
|
||||
oledShowMessage("Scale Ready");
|
||||
|
||||
|
||||
Serial.println("starte Scale Task");
|
||||
start_scale();
|
||||
|
||||
vTaskResume(RfidReaderTask);
|
||||
vTaskResume(ScaleTask);
|
||||
pauseBambuMqttTask = false;
|
||||
pauseMainTask = 0;
|
||||
scaleCalibrationActive = false;
|
||||
|
||||
return 1;
|
||||
return returnState;
|
||||
}
|
||||
|
16
src/scale.h
@@ -4,17 +4,27 @@
|
||||
#include <Arduino.h>
|
||||
#include "HX711.h"
|
||||
|
||||
|
||||
uint8_t start_scale();
|
||||
uint8_t setAutoTare(bool autoTareValue);
|
||||
uint8_t start_scale(bool touchSensorConnected);
|
||||
uint8_t calibrate_scale();
|
||||
uint8_t tareScale();
|
||||
|
||||
// Weight stabilization functions
|
||||
void resetWeightFilter();
|
||||
float calculateMovingAverage();
|
||||
float applyLowPassFilter(float newValue);
|
||||
int16_t processWeightReading(float rawWeight);
|
||||
int16_t getFilteredDisplayWeight();
|
||||
|
||||
extern HX711 scale;
|
||||
extern int16_t weight;
|
||||
extern uint8_t weigthCouterToApi;
|
||||
extern uint8_t scale_tare_counter;
|
||||
extern uint8_t scaleTareRequest;
|
||||
extern uint8_t pauseMainTask;
|
||||
extern uint8_t scaleCalibrated;
|
||||
extern bool scaleCalibrated;
|
||||
extern bool autoTare;
|
||||
extern bool scaleCalibrationActive;
|
||||
|
||||
extern TaskHandle_t ScaleTask;
|
||||
|
||||
|
180
src/website.cpp
@@ -10,6 +10,9 @@
|
||||
#include <Update.h>
|
||||
#include "display.h"
|
||||
#include "ota.h"
|
||||
#include "config.h"
|
||||
#include "debug.h"
|
||||
|
||||
|
||||
#ifndef VERSION
|
||||
#define VERSION "1.1.0"
|
||||
@@ -22,17 +25,22 @@ AsyncWebServer server(webserverPort);
|
||||
AsyncWebSocket ws("/ws");
|
||||
|
||||
uint8_t lastSuccess = 0;
|
||||
uint8_t lastHasReadRfidTag = 0;
|
||||
nfcReaderStateType lastnfcReaderState = NFC_IDLE;
|
||||
|
||||
|
||||
void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
|
||||
HEAP_DEBUG_MESSAGE("onWsEvent begin");
|
||||
if (type == WS_EVT_CONNECT) {
|
||||
Serial.println("Neuer Client verbunden!");
|
||||
// Sende die AMS-Daten an den neuen Client
|
||||
sendAmsData(client);
|
||||
sendNfcData(client);
|
||||
if (!bambuDisabled) sendAmsData(client);
|
||||
sendNfcData();
|
||||
foundNfcTag(client, 0);
|
||||
sendWriteResult(client, 3);
|
||||
|
||||
// Clean up dead connections
|
||||
(*server).cleanupClients();
|
||||
Serial.println("Currently connected number of clients: " + String((*server).getClients().size()));
|
||||
} else if (type == WS_EVT_DISCONNECT) {
|
||||
Serial.println("Client getrennt.");
|
||||
} else if (type == WS_EVT_ERROR) {
|
||||
@@ -40,9 +48,15 @@ void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventTyp
|
||||
} else if (type == WS_EVT_PONG) {
|
||||
Serial.printf("WebSocket Client #%u pong\n", client->id());
|
||||
} else if (type == WS_EVT_DATA) {
|
||||
String message = String((char*)data);
|
||||
JsonDocument doc;
|
||||
deserializeJson(doc, message);
|
||||
DeserializationError error = deserializeJson(doc, (char*)data, len);
|
||||
//String message = String((char*)data);
|
||||
//deserializeJson(doc, message);
|
||||
|
||||
if (error) {
|
||||
Serial.println("JSON deserialization failed: " + String(error.c_str()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (doc["type"] == "heartbeat") {
|
||||
// Sende Heartbeat-Antwort
|
||||
@@ -50,7 +64,7 @@ void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventTyp
|
||||
"\"type\":\"heartbeat\","
|
||||
"\"freeHeap\":" + String(ESP.getFreeHeap()/1024) + ","
|
||||
"\"bambu_connected\":" + String(bambu_connected) + ","
|
||||
"\"spoolman_connected\":" + String(spoolman_connected) + ""
|
||||
"\"spoolman_connected\":" + String(spoolmanConnected) + ""
|
||||
"}");
|
||||
}
|
||||
|
||||
@@ -59,7 +73,8 @@ void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventTyp
|
||||
// Versuche NFC-Daten zu schreiben
|
||||
String payloadString;
|
||||
serializeJson(doc["payload"], payloadString);
|
||||
startWriteJsonToTag(payloadString.c_str());
|
||||
|
||||
startWriteJsonToTag((doc["tagType"] == "spool") ? true : false, payloadString.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +88,10 @@ void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventTyp
|
||||
success = calibrate_scale();
|
||||
}
|
||||
|
||||
if (doc["payload"] == "setAutoTare") {
|
||||
success = setAutoTare(doc["enabled"].as<bool>());
|
||||
}
|
||||
|
||||
if (success) {
|
||||
ws.textAll("{\"type\":\"scale\",\"payload\":\"success\"}");
|
||||
} else {
|
||||
@@ -107,7 +126,9 @@ void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventTyp
|
||||
else {
|
||||
Serial.println("Unbekannter WebSocket-Typ: " + doc["type"].as<String>());
|
||||
}
|
||||
doc.clear();
|
||||
}
|
||||
HEAP_DEBUG_MESSAGE("onWsEvent end");
|
||||
}
|
||||
|
||||
// Funktion zum Laden und Ersetzen des Headers in einer HTML-Datei
|
||||
@@ -134,39 +155,36 @@ void sendWriteResult(AsyncWebSocketClient *client, uint8_t success) {
|
||||
void foundNfcTag(AsyncWebSocketClient *client, uint8_t success) {
|
||||
if (success == lastSuccess) return;
|
||||
ws.textAll("{\"type\":\"nfcTag\", \"payload\":{\"found\": " + String(success) + "}}");
|
||||
sendNfcData(nullptr);
|
||||
sendNfcData();
|
||||
lastSuccess = success;
|
||||
}
|
||||
|
||||
void sendNfcData(AsyncWebSocketClient *client) {
|
||||
if (lastHasReadRfidTag == hasReadRfidTag) return;
|
||||
if (hasReadRfidTag == 0) {
|
||||
ws.textAll("{\"type\":\"nfcData\", \"payload\":{}}");
|
||||
void sendNfcData() {
|
||||
if (lastnfcReaderState == nfcReaderState) return;
|
||||
// TBD: Why is there no status for reading the tag?
|
||||
switch(nfcReaderState){
|
||||
case NFC_IDLE:
|
||||
ws.textAll("{\"type\":\"nfcData\", \"payload\":{}}");
|
||||
break;
|
||||
case NFC_READ_SUCCESS:
|
||||
ws.textAll("{\"type\":\"nfcData\", \"payload\":" + nfcJsonData + "}");
|
||||
break;
|
||||
case NFC_READ_ERROR:
|
||||
ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"error\":\"Empty Tag or Data not readable\"}}");
|
||||
break;
|
||||
case NFC_WRITING:
|
||||
ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"info\":\"Schreibe Tag...\"}}");
|
||||
break;
|
||||
case NFC_WRITE_SUCCESS:
|
||||
ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"info\":\"Tag erfolgreich geschrieben\"}}");
|
||||
break;
|
||||
case NFC_WRITE_ERROR:
|
||||
ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"error\":\"Error writing to Tag\"}}");
|
||||
break;
|
||||
case DEFAULT:
|
||||
ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"error\":\"Something went wrong\"}}");
|
||||
}
|
||||
else if (hasReadRfidTag == 1) {
|
||||
ws.textAll("{\"type\":\"nfcData\", \"payload\":" + nfcJsonData + "}");
|
||||
}
|
||||
else if (hasReadRfidTag == 2)
|
||||
{
|
||||
ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"error\":\"Empty Tag or Data not readable\"}}");
|
||||
}
|
||||
else if (hasReadRfidTag == 3)
|
||||
{
|
||||
ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"info\":\"Schreibe Tag...\"}}");
|
||||
}
|
||||
else if (hasReadRfidTag == 4)
|
||||
{
|
||||
ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"error\":\"Error writing to Tag\"}}");
|
||||
}
|
||||
else if (hasReadRfidTag == 5)
|
||||
{
|
||||
ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"info\":\"Tag erfolgreich geschrieben\"}}");
|
||||
}
|
||||
else
|
||||
{
|
||||
ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"error\":\"Something went wrong\"}}");
|
||||
}
|
||||
lastHasReadRfidTag = hasReadRfidTag;
|
||||
lastnfcReaderState = nfcReaderState;
|
||||
}
|
||||
|
||||
void sendAmsData(AsyncWebSocketClient *client) {
|
||||
@@ -176,6 +194,7 @@ void sendAmsData(AsyncWebSocketClient *client) {
|
||||
}
|
||||
|
||||
void setupWebserver(AsyncWebServer &server) {
|
||||
oledShowProgressBar(2, 7, DISPLAY_BOOT_TEXT, "Webserver init");
|
||||
// Deaktiviere alle Debug-Ausgaben
|
||||
Serial.setDebugOutput(false);
|
||||
|
||||
@@ -192,6 +211,9 @@ void setupWebserver(AsyncWebServer &server) {
|
||||
Serial.print("Geladene Spoolman-URL: ");
|
||||
Serial.println(spoolmanUrl);
|
||||
|
||||
// Load Bamb credentials:
|
||||
loadBambuCredentials();
|
||||
|
||||
// Route für about
|
||||
server.on("/about", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
Serial.println("Anfrage für /about erhalten");
|
||||
@@ -204,16 +226,23 @@ 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(LittleFS, "/waage.html.gz", "text/html");
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
response->addHeader("Cache-Control", CACHE_CONTROL);
|
||||
request->send(response);
|
||||
//AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/waage.html.gz", "text/html");
|
||||
//response->addHeader("Content-Encoding", "gzip");
|
||||
//response->addHeader("Cache-Control", CACHE_CONTROL);
|
||||
|
||||
String html = loadHtmlWithHeader("/waage.html");
|
||||
html.replace("{{autoTare}}", (autoTare) ? "checked" : "");
|
||||
|
||||
request->send(200, "text/html", html);
|
||||
});
|
||||
|
||||
// Route für RFID
|
||||
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
Serial.println("Anfrage für /rfid erhalten");
|
||||
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/rfid.html.gz", "text/html");
|
||||
|
||||
String page = (bambuDisabled) ? "/rfid.html.gz" : "/rfid_bambu.html.gz";
|
||||
AsyncWebServerResponse *response = request->beginResponse(LittleFS, page, "text/html");
|
||||
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
response->addHeader("Cache-Control", CACHE_CONTROL);
|
||||
request->send(response);
|
||||
@@ -239,33 +268,16 @@ 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>())
|
||||
{
|
||||
String bambuIp = doc["bambu_ip"].as<String>();
|
||||
String bambuSerial = doc["bambu_serialnr"].as<String>();
|
||||
String bambuCode = doc["bambu_accesscode"].as<String>();
|
||||
autoSendToBambu = doc["autoSendToBambu"].as<bool>();
|
||||
bambuIp.trim();
|
||||
bambuSerial.trim();
|
||||
bambuCode.trim();
|
||||
|
||||
html.replace("{{bambuIp}}", bambuIp ? bambuIp : "");
|
||||
html.replace("{{bambuSerial}}", bambuSerial ? bambuSerial : "");
|
||||
html.replace("{{bambuCode}}", bambuCode ? bambuCode : "");
|
||||
html.replace("{{autoSendToBambu}}", autoSendToBambu ? "checked" : "");
|
||||
html.replace("{{autoSendTime}}", String(autoSetBambuAmsCounter));
|
||||
}
|
||||
else
|
||||
{
|
||||
html.replace("{{bambuIp}}", "");
|
||||
html.replace("{{bambuSerial}}", "");
|
||||
html.replace("{{bambuCode}}", "");
|
||||
html.replace("{{autoSendToBambu}}", "");
|
||||
html.replace("{{autoSendTime}}", String(autoSetBambuAmsCounter));
|
||||
}
|
||||
html.replace("{{bambuIp}}", bambuCredentials.ip);
|
||||
html.replace("{{bambuSerial}}", bambuCredentials.serial);
|
||||
html.replace("{{bambuCode}}", bambuCredentials.accesscode ? bambuCredentials.accesscode : "");
|
||||
html.replace("{{autoSendToBambu}}", bambuCredentials.autosend_enable ? "checked" : "");
|
||||
html.replace("{{autoSendTime}}", (bambuCredentials.autosend_time != 0) ? String(bambuCredentials.autosend_time) : String(BAMBU_DEFAULT_AUTOSEND_TIME));
|
||||
|
||||
request->send(200, "text/html", html);
|
||||
});
|
||||
@@ -277,17 +289,45 @@ 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();
|
||||
url.trim();
|
||||
if (url.indexOf("http://") == -1 && url.indexOf("https://") == -1) {
|
||||
url = "http://" + url;
|
||||
}
|
||||
// Remove trailing slash if exists
|
||||
if (url.length() > 0 && url.charAt(url.length()-1) == '/') {
|
||||
url = url.substring(0, url.length()-1);
|
||||
}
|
||||
|
||||
bool healthy = saveSpoolmanUrl(url);
|
||||
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, octoEnabled, octoUrl, octoToken);
|
||||
String jsonResponse = "{\"healthy\": " + String(healthy ? "true" : "false") + "}";
|
||||
|
||||
request->send(200, "application/json", jsonResponse);
|
||||
});
|
||||
|
||||
// Route für das Überprüfen der Spoolman-Instanz
|
||||
// Route für das Überprüfen der Bambu-Instanz
|
||||
server.on("/api/bambu", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
if (request->hasParam("remove")) {
|
||||
if (removeBambuCredentials()) {
|
||||
request->send(200, "application/json", "{\"success\": true}");
|
||||
} else {
|
||||
request->send(500, "application/json", "{\"success\": false, \"error\": \"Fehler beim Löschen der Bambu-Credentials\"}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!request->hasParam("bambu_ip") || !request->hasParam("bambu_serialnr") || !request->hasParam("bambu_accesscode")) {
|
||||
request->send(400, "application/json", "{\"success\": false, \"error\": \"Missing parameter\"}");
|
||||
return;
|
||||
|
@@ -24,7 +24,7 @@ void setupWebserver(AsyncWebServer &server);
|
||||
|
||||
// WebSocket-Funktionen
|
||||
void sendAmsData(AsyncWebSocketClient *client);
|
||||
void sendNfcData(AsyncWebSocketClient *client);
|
||||
void sendNfcData();
|
||||
void foundNfcTag(AsyncWebSocketClient *client, uint8_t success);
|
||||
void sendWriteResult(AsyncWebSocketClient *client, uint8_t success);
|
||||
|
||||
|
@@ -59,10 +59,9 @@ void initWiFi() {
|
||||
if(wm_nonblocking) wm.setConfigPortalBlocking(false);
|
||||
//wm.setConfigPortalTimeout(320); // Portal nach 5min schließen
|
||||
wm.setWiFiAutoReconnect(true);
|
||||
wm.setConnectTimeout(5);
|
||||
wm.setConnectTimeout(10);
|
||||
|
||||
oledShowTopRow();
|
||||
oledShowMessage("WiFi Setup");
|
||||
oledShowProgressBar(1, 7, DISPLAY_BOOT_TEXT, "WiFi init");
|
||||
|
||||
//bool res = wm.autoConnect("FilaMan"); // anonymous ap
|
||||
if(!wm.autoConnect("FilaMan")) {
|
||||
@@ -80,9 +79,6 @@ void initWiFi() {
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
oledShowTopRow();
|
||||
display.display();
|
||||
|
||||
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||
|
||||
// mDNS
|
||||
startMDNS();
|
||||
|
6432
usermod/spitzbirne32/CAD/Base_usermod_spitzbirne32.stp
Normal file
16385
usermod/spitzbirne32/CAD/FilaMan-Scale_usermod_spitzbirne32.stp
Normal file
5278
usermod/spitzbirne32/CAD/Housing_usermod_spitzbirne32.stp
Normal file
0
usermod/spitzbirne32/CAD/README.md
Normal file
4888
usermod/spitzbirne32/CAD/ScaleTop_usermod_spitzbirne32.stp
Normal file
After Width: | Height: | Size: 540 KiB |
After Width: | Height: | Size: 525 KiB |
After Width: | Height: | Size: 7.9 MiB |
After Width: | Height: | Size: 183 KiB |
12
usermod/spitzbirne32/Images/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
## **Heat insert location**
|
||||
|
||||
Housing:
|
||||
- every hole is made to fit a heat insert
|
||||

|
||||
|
||||
---
|
||||
Scale top:
|
||||
- two heat inserts for the NFC Reader
|
||||

|
||||
|
||||
|
After Width: | Height: | Size: 491 KiB |
After Width: | Height: | Size: 834 KiB |
BIN
usermod/spitzbirne32/Images/Showcase_usermod_spitzbirne32.gif
Normal file
After Width: | Height: | Size: 1.9 MiB |
69
usermod/spitzbirne32/README.md
Normal file
@@ -0,0 +1,69 @@
|
||||
## Modifications
|
||||
|
||||
To reduce costs, components were sourced from AliExpress instead of Amazon. However, differences in dimensions and mounting hole spacing necessitated adjustments to the 3D-printed parts. Additionally M3 heat inserts were used to limit M4 screws to a minimum.
|
||||

|
||||
---
|
||||
|
||||
List of parts that were used:
|
||||
- Display: https://aliexpress.com/item/1005007389730469.html
|
||||
- Scale(5KG with HX711): https://aliexpress.com/item/1005006827930173.html
|
||||
- NFC Reader: https://aliexpress.com/item/1005005973913526.html
|
||||
- NFC Chips: https://aliexpress.com/item/1005006332360160.html
|
||||
- [VORON](https://vorondesign.com/) Heat Inserts M3 OD5mm L4mm: https://aliexpress.com/item/1005003582355741.html - make sure to select the correct size
|
||||
|
||||
---
|
||||
|
||||
- **Parts are designed to be printed in ABS/ASA.** Shrinking compensation not needed.
|
||||
|
||||
- **Display and Scale Adjustments:** The AliExpress-sourced display and scale had different dimensions and hole spacings compared to the Amazon versions. The 3D models were modified to accommodate these differences, ensuring proper fit and functionality.
|
||||
- measurement of my Display & Scale to check if your parts will fit can be found in the images folder
|
||||
|
||||
- **Screw Size and Heat Inserts:** All holes originally designed for M4 screws were resized to fit M3 screws. Standard VORON heat inserts were incorporated to provide durable threading. This change standardizes the hardware and simplifies assembly.
|
||||
|
||||
- **Display Mounting:** The display is now mounted using M3 screws with VORON heat inserts. The display's mounting holes need to be drilled to 3mm to accommodate the M3 screws.
|
||||
|
||||
- **Scale Top Surface:** The top surface of the scale was modified to allow M3 socket head cap screws to sit flush with the 3D-printed part. This design ensures that the filament spool rests flat without interference.
|
||||
|
||||
- **NFC Reader Mounting:** The NFC reader is also secured using M3 screws and VORON heat inserts, maintaining consistency across all components.
|
||||
|
||||
- **Scale Base Mounting:** The only M4 screws required are for attaching the metal part of the scale to its base.
|
||||
|
||||
## Benefits of Modifications
|
||||
|
||||
- **Cost Reduction:** Sourcing components from AliExpress offers a more affordable alternative to Amazon, making the project more accessible.
|
||||
|
||||
- **Standardized Hardware:** Using M3 screws and [VORON](https://vorondesign.com/) heat inserts throughout the assembly simplifies the build process and reduces the variety of required hardware.
|
||||
|
||||
- **Enhanced Compatibility:** Adjustments to the 3D models ensure compatibility with readily available components, accommodating variations in part dimensions.
|
||||
|
||||
## Assembly Instructions
|
||||
|
||||
1. **Component Preparation:**
|
||||
- Carefully drill the display's mounting holes to 3mm to fit M3 screws.
|
||||
|
||||
2. **Heat Insert Installation:**
|
||||
- install VORON M3 heat inserts into the designated holes in the 3D-printed housing/case for the ESP32 and Scale top → [heat insert location pictures](./Images/README.md)
|
||||
|
||||
3. **Component Mounting:**
|
||||
- Attach the display, scale, and NFC reader to their respective mounts using M3 screws.
|
||||
- Secure the metal part of the scale to its base using M4 screws.
|
||||
|
||||
4. **Final Assembly:**
|
||||
- Assemble all components according to the original FilaMan instructions, ensuring that all modified parts fit correctly and function as intended.
|
||||
|
||||
For detailed assembly guides and additional resources, refer to the [original FilaMan documentation](https://github.com/ManuelW77/Filaman).
|
||||
|
||||
## Conclusion
|
||||
|
||||
These modifications to the FilaMan project provide a cost-effective and standardized approach to building a filament management system. By sourcing components from AliExpress and adjusting the 3D models accordingly, users can achieve the same functionality at a reduced cost, with the added benefit of using uniform hardware throughout the assembly.
|
||||
|
||||
## Changelog
|
||||
|
||||
### Version 1.0 - 2025-03-04
|
||||
- Initial release of modifications for AliExpress-sourced components.
|
||||
- Adjusted 3D models to fit different display and scale dimensions.
|
||||
- Replaced M4 screws with M3 screws and integrated VORON heat inserts.
|
||||
- Modified display mounting, requiring drilling to 3mm for M3 screws.
|
||||
- Adjusted scale top surface for flush screw placement.
|
||||
- Standardized NFC reader mounting with M3 screws and VORON heat inserts.
|
||||
- Retained M4 screws only for metal scale attachment.
|