Compare commits
715 Commits
Author | SHA1 | Date | |
---|---|---|---|
01f1e123ac | |||
012f91851e | |||
9ed3c70c01 | |||
e23f3a2151 | |||
f73306f0b9 | |||
a450d4bd1a | |||
d48d994c00 | |||
32bb85f897 | |||
e9d32ee060 | |||
aba28422bd | |||
4a55620d39 | |||
7b18266534 | |||
d81acb2b61 | |||
8c7fc159d3 | |||
476d3e82e2 | |||
3c294a135f | |||
bb751b6289 | |||
7fd01bd1b9 | |||
fad84e12c8 | |||
696efc4d79 | |||
29868e7101 | |||
823db6157c | |||
458cc4eaf2 | |||
83d14b32d1 | |||
2bf7c9fb7d | |||
|
ac8adca84d | ||
|
c701149c64 | ||
07a919b6ba | |||
8618b90e33 | |||
57723b5354 | |||
d2be752175 | |||
97a050ace8 | |||
367e692c74 | |||
926a21249b | |||
2635c19667 | |||
|
6cc4efca0a | ||
1484a6b0da | |||
b5f0472af4 | |||
95c1bc823c | |||
491ba7f526 | |||
56d7d8596c | |||
1044e91a0a | |||
e459b53472 | |||
024056cb7d | |||
e040a736b0 | |||
72b6b349c6 | |||
190e952ec4 | |||
89620a7f00 | |||
536950eeb3 | |||
|
fe4d2d7479 | ||
43719aac41 | |||
16d0079f7a | |||
48b9bf7076 | |||
b6bd4cb9ad | |||
e89bb1d547 | |||
f25789d703 | |||
|
65d8cd675f | ||
9dfe75ffa2 | |||
68cdd8ab40 | |||
1069781931 | |||
eada54eff2 | |||
48301ade36 | |||
76e0b20393 | |||
a765b39896 | |||
d68f6c4a89 | |||
1702e2396e | |||
af23b07df1 | |||
dd7ba3bf5d | |||
a818dcd3c0 | |||
b5279b167a | |||
a09fd4fda4 | |||
e4fe08f54c | |||
3eac0e5ac4 | |||
24d91693d9 | |||
94c26590c8 | |||
4559bae066 | |||
cdb2d16cf9 | |||
cd71949c82 | |||
6cd280389d | |||
|
daf27820b1 | ||
|
dd7fbe1119 | ||
|
dc2ddb47eb | ||
|
6bb8f565e6 | ||
|
ec60ca88f1 | ||
|
17664acf9e | ||
|
18f7454a76 | ||
|
e7b5917888 | ||
|
5c57968ba9 | ||
|
795c926c1f | ||
|
8735a9740c | ||
|
02d0adc6bf | ||
|
24067666ed | ||
|
9264333eda | ||
|
66216d57ae | ||
|
5100a669b0 | ||
|
4ad89b68a7 | ||
|
758acaff9f | ||
|
fed96b9c58 | ||
|
2d072ee09a | ||
|
b55b6e3fd5 | ||
|
238b928236 | ||
|
24ce0ca6df | ||
|
3cf934b920 | ||
|
f68ea3edb0 | ||
|
16321c9461 | ||
|
f9530f6d9a | ||
83f2f0834d | |||
6632aa8f95 | |||
8a558c3121 | |||
|
d434fde92e | ||
5afb60df32 | |||
3394e6eb01 | |||
3818c2c059 | |||
0afc543b5f | |||
adee46e3fc | |||
1db74867e6 | |||
0f24a63d32 | |||
3640809502 | |||
289d5357be | |||
315530d1ea | |||
f36773a4c4 | |||
b35163936f | |||
7a2c9d6d17 | |||
eb2a8dc128 | |||
bec2c91331 | |||
c6e727de06 | |||
3253e7d407 | |||
bce2ad2ed8 | |||
|
0eff29ef4a | ||
492bf6cdb8 | |||
b0317f4001 | |||
58ff6458b0 | |||
d9c40f5124 | |||
68bc31e29a | |||
9b23ac5fd2 | |||
d31bff14c3 | |||
150f92484a | |||
fa74832fb9 | |||
2eab3db77d | |||
0a1bf22f7e | |||
d58244c1f8 | |||
db626ea516 | |||
fd8f7685a1 | |||
944b156528 | |||
76100593cc | |||
732d590344 | |||
46cd953b80 | |||
c645035bbe | |||
9e76620cd3 | |||
faddda6201 | |||
de9c1706c0 | |||
9f7ee13e78 | |||
cf3f6f6741 | |||
b87d43c64e | |||
3d0411e3c1 | |||
9c61b708aa | |||
90f800d042 | |||
a7b1721e1d | |||
e4825d2905 | |||
c1733848d3 | |||
484c95523d | |||
8499613215 | |||
08f37186b4 | |||
2948a35fa8 | |||
730724fe58 | |||
714b7065e7 | |||
2d8aec515d | |||
b245a206ce | |||
f1489e75cc | |||
d9ae829503 | |||
2247b8ed6c | |||
d70b187bf9 | |||
1ade007473 | |||
0af14e2f7d | |||
de67cdbff3 | |||
98fce15ccc | |||
ab417ba64b | |||
320057bc49 | |||
9007a65fc2 | |||
2214f5f5de | |||
5c5846c52c | |||
517fa37a3d | |||
aaa7a6ee9c | |||
a0b8639488 | |||
a16c05287e | |||
ecb35a97bd | |||
ba968611ec | |||
6bd11ddce3 | |||
3eb313e61a | |||
aad35dc296 | |||
85ac636b1e | |||
6f1804c3fe | |||
89716920dc | |||
78b5078651 | |||
6098c3b052 | |||
e7537f94d4 | |||
37717392d0 | |||
c6da28ad6f | |||
d6e38a4e73 | |||
4e0d9353c8 | |||
7059826659 | |||
41faa8bb1c | |||
b38e3fa5ef | |||
5280d7e341 | |||
2f95c66d39 | |||
df1b87465c | |||
84f1420999 | |||
b14dd5475d | |||
975845421b | |||
044ddbe0eb | |||
c385544d67 | |||
c6cfd85687 | |||
84632322e2 | |||
86e55a8696 | |||
d2b40daaca | |||
9d58cbc31c | |||
d09aeaf47c | |||
9fb82fe51e | |||
5e0e2c5f6b | |||
a8460503ff | |||
6700a1761f | |||
7207f36e06 | |||
e79bee3381 | |||
c3918f075b | |||
0c384219c5 | |||
42b9daf4be | |||
13a771682f | |||
f79f87bf09 | |||
9fe3f6c0ff | |||
55e89948bb | |||
6c5e8c4d07 | |||
4f79700d74 | |||
1b4fecf409 | |||
89a6101d97 | |||
ee45a74fee | |||
db365aba3c | |||
63cdfaee6c | |||
eb2e360c35 | |||
7d578640e2 | |||
b006533a91 | |||
9fa7526623 | |||
dfbb2fbd9b | |||
0302158449 | |||
68c385f9d7 | |||
9a8bd58cb3 | |||
0d8b8918c1 | |||
a892b854b5 | |||
0f02f6c848 | |||
96c054827e | |||
f93eedf775 | |||
68a10dfeb2 | |||
632b7a089e | |||
c0e3650bf4 | |||
8e3dfc93f7 | |||
5016285dce | |||
9b1a232fde | |||
37e79b7a49 | |||
6bd23f31c1 | |||
3099e9ded9 | |||
4952ad3150 | |||
2055da9962 | |||
459a31cad3 | |||
4b1930209b | |||
7dde07b5ab | |||
33a5406248 | |||
b016a31ff0 | |||
19bc4927e4 | |||
cd55cb86ba | |||
8ab16b351b | |||
400a37d3ac | |||
eb4f809435 | |||
1148947b8e | |||
3b01336999 | |||
44614b58dc | |||
ed8d618272 | |||
cd2ac54e98 | |||
92f675b24c | |||
c342877558 | |||
f5743cbd7b | |||
8a62597705 | |||
374721d1e5 | |||
ea6f708c6e | |||
78169dfdb1 | |||
074bfb658d | |||
989076e794 | |||
aa0d056d10 | |||
cd619b8f2a | |||
6d8358cbb9 | |||
1f3a67634f | |||
09969b644e | |||
deb7abd102 | |||
1b059c35f1 | |||
e098d71f6f | |||
4b25b72b2e | |||
5c59016f94 | |||
d2da501b94 | |||
4135073623 | |||
fe7b57fe0e | |||
c1ae6b7295 | |||
9eee89fac7 | |||
8c5e7e26ac | |||
7b52066378 | |||
d5afa38ded | |||
cf50baba2d | |||
aa9e7da94b | |||
71cd3ba4fc | |||
73e240e879 | |||
0d34e1d718 | |||
84cc8beb9b | |||
fd70e3179d | |||
c553640ad8 | |||
807eca3c43 | |||
b52730bf67 | |||
9a59b91e88 | |||
a5af4013d8 | |||
e54ce58ec4 | |||
142eafd232 | |||
63ab9e0993 | |||
aaa5506d40 | |||
8037adc045 | |||
6e7c728cd8 | |||
3fe8271344 | |||
f2bc6eab92 | |||
37df492339 | |||
c4b425403f | |||
73244689dd | |||
27296104d2 | |||
5f99773897 | |||
7416285fb9 | |||
85928e358d | |||
092b4fd8ec | |||
399645a2b3 | |||
164bb241b7 | |||
e564c6eeae | |||
4288dd0cd4 | |||
37d43b2d7d | |||
adb354ddcd | |||
15d5e5edce | |||
c6edf30245 | |||
65ac207f36 | |||
698abbd669 | |||
04a7c2cce3 | |||
78f54b72fd | |||
f4eee9af91 | |||
cad14b3bc2 | |||
312f75fc5f | |||
b8714e93e2 | |||
cd9da0fe4f | |||
2b620ef5ed | |||
3f63a01b8b | |||
22bb16b6a4 | |||
53ceee7816 | |||
d48b002806 | |||
dd905b6c6e | |||
77b9eda110 | |||
32a6e9dcd3 | |||
6cd5539e60 | |||
903b697912 | |||
72c2fb70c2 | |||
f2f3f0ab9f | |||
c07692c218 | |||
a184903b66 | |||
af1640383d | |||
c00e54b145 | |||
f6c92c686b | |||
b8db01529b | |||
55db6d76ab | |||
a18749a1ff | |||
1811fd9159 | |||
b550760427 | |||
c5033acadc | |||
7de4189c83 | |||
f43f2a15b2 | |||
858192c6cb | |||
e2bd39922d | |||
c86cc7173e | |||
16362e66a3 | |||
48d9ba8f71 | |||
e2bea5a0c3 | |||
3e11f65188 | |||
df59c42c8a | |||
abe1d7c930 | |||
ca614c3cc4 | |||
5153374093 | |||
66db4d7a85 | |||
90e71922b1 | |||
e8e5c0bd3d | |||
7e53e1ccb0 | |||
e49e812b13 | |||
b1e0fcfadf | |||
31ef3ac8df | |||
8cf3f87c89 | |||
c446188311 | |||
8e2a8d597d | |||
7d3b1c34f6 | |||
b95c61118b | |||
0dfb158959 | |||
75c774bb24 | |||
cf80adb43c | |||
36d50cbe7f | |||
9148d207c7 | |||
5f6fef9448 | |||
946202de0e | |||
41a3717347 | |||
255c820439 | |||
aef3ba77ba | |||
2592c3a497 | |||
a48c5dfef0 | |||
00554d0b09 | |||
05a91cd8d8 | |||
7cf113eaff | |||
44d27adab2 | |||
e0a2dff5fe | |||
519a089684 | |||
ef053bb2b6 | |||
0a91c7b269 | |||
875d9d2b70 | |||
52840b9b0b | |||
da1fc7678f | |||
982bb5aa21 | |||
007737db13 | |||
17e5949201 | |||
6a57186091 | |||
babd3f47a0 | |||
5372fe10fe | |||
e0c9d90892 | |||
e5f5d1961b | |||
31a960fb9e | |||
3c2e75b77a | |||
367143c456 | |||
fbde4b764f | |||
e57f4216d4 | |||
b8beb992d6 | |||
4234b2254e | |||
b8faf79163 | |||
d35afaff46 | |||
a8a00372b5 | |||
72f4eab588 | |||
afa4eddc00 | |||
b0888e7e63 | |||
238a84a8a2 | |||
59cc00ca13 | |||
ab083f5f57 | |||
c111573206 | |||
52b2494e52 | |||
069ec2d7a1 | |||
94e35ae86e | |||
d71e3d8184 | |||
bb166aa29f | |||
0d718023f8 | |||
b16781043f | |||
dff184ff25 | |||
0ce281221d | |||
bc26c160e8 | |||
c25f41db75 | |||
e107c17f50 | |||
85b9d03ebd | |||
17b188626a | |||
a534c5f872 | |||
93f7582790 | |||
46acc63756 | |||
67a9e1bdce | |||
2b75b64b4a | |||
8d003295e7 | |||
f89500946a | |||
14e745ff06 | |||
d058397fa2 | |||
622f5403a7 | |||
92b78a86dd | |||
ec399390e8 | |||
909c4e9b5e | |||
f4b20bfffd | |||
78464215a9 | |||
4365f0463a | |||
727bc0e760 | |||
04604013eb | |||
cf5fc5f6f1 | |||
945a4ccce6 | |||
7cf9e2d145 | |||
9db4e338ea | |||
dea6ca2c66 | |||
e224e72e41 | |||
306c517da7 | |||
0337bbabe0 | |||
bde14e50e0 | |||
9c656a9bd0 | |||
eae552017d | |||
a77918da41 | |||
262dad38a6 | |||
cfc9f103cf | |||
0117302672 | |||
1de283b62f | |||
f1eb78eb38 | |||
8a65b86475 | |||
a3aef819c8 | |||
a62b5ec933 | |||
1a8cf7a58f | |||
b0b3d41c84 | |||
38b68aecfc | |||
4992f5f433 | |||
5cbbe1d231 | |||
9b29460d64 | |||
dd14d475b7 | |||
9e6cd3b451 | |||
c1be6ca582 | |||
265ff0c787 | |||
67eca82ac5 | |||
568db90db0 | |||
2dfd53d64a | |||
262a2fcbd4 | |||
3770de15d3 | |||
75a74ec9bd | |||
979adcbb14 | |||
2dd563a178 | |||
767c217c25 | |||
c07689e15a | |||
d6ca69fd19 | |||
60553255b8 | |||
8199b283c0 | |||
d774ce0d09 | |||
4a44eda5c4 | |||
c43ca20d8d | |||
21ba35cd19 | |||
62273320e9 | |||
b8e4af4e4d | |||
513d74fdb0 | |||
df884e7668 | |||
8182b5f684 | |||
4477537cec | |||
44ba7df34f | |||
54744a06dd | |||
cefa81030b | |||
62052927d2 | |||
933a84f8ce | |||
db3c19ff2e | |||
ae9eb4cc6b | |||
89d40832c5 | |||
c161216c04 | |||
6a016b6ac4 | |||
44dd485e17 | |||
d41f0f3e67 | |||
484058515e | |||
f552b492cf | |||
c3040b3c29 | |||
d7ba67085d | |||
48efb9e21a | |||
e983ba6e44 | |||
37171d6eca | |||
ebb08a7a66 | |||
b5330af351 | |||
4919d34484 | |||
2da641d604 | |||
ce413965c7 | |||
3fafed930e | |||
e1c604ee8d | |||
11bbfb7db6 | |||
71d8f7ec5a | |||
f4518e4a36 | |||
62d9596d08 | |||
e27e95d291 | |||
b7651ad50d | |||
f1937e2977 | |||
ad5ddf713c | |||
ccb494f843 | |||
17307d8f03 | |||
e5240a9572 | |||
06ebf105cf | |||
118e099fc5 | |||
8edd50f786 | |||
b85325a747 | |||
e1e0352beb | |||
8a93cccfce | |||
c374069f36 | |||
59cd7c177d | |||
45088b5838 | |||
1b9c79b559 | |||
37e1e861d3 | |||
cce39319d9 | |||
6391054c23 | |||
52cf46d7f8 | |||
84b05e48ce | |||
5c41d864c1 | |||
5dc3563da6 | |||
9e1b2943d6 | |||
7b89b04621 | |||
e140f8e003 | |||
3d0bdde476 | |||
3ac7d6b4f7 | |||
5f52775984 | |||
463eaf4b6f | |||
4bf6b11d3a | |||
b0c4af7c4e | |||
249e896ea4 | |||
c74f587fff | |||
7a7ee72585 | |||
3dd5fbc585 | |||
ed9c1487ed | |||
d8756421a1 | |||
d92c78f9d0 | |||
2d19ea745f | |||
13779cc9d7 | |||
b6d5a8a00b | |||
f6319e79f0 | |||
6f24630a7d | |||
4475d21218 | |||
01a926a38d | |||
6b966c02b3 | |||
1450e1ad2e | |||
3102a6c217 | |||
d5b2b2746d | |||
7e776d4816 | |||
e84b2973c5 | |||
5793dc1a1f | |||
1732491c48 | |||
0500bb6951 | |||
ef9ef7257a | |||
e86fd229dc | |||
b940a166da | |||
c857e16de2 | |||
8b2a537b72 | |||
88ec151c4c | |||
0a203f02eb | |||
04b59f4809 | |||
b31861af67 | |||
ce3b423dc1 | |||
05f275142f | |||
72af54bd20 | |||
a1e30a3b7f | |||
8f8322e629 | |||
1e386c49af | |||
3070d75d07 | |||
34ee9badea | |||
6594af9540 | |||
c21bd2d4ec | |||
e5f895b560 | |||
bb0f50ce1d | |||
499a08aa75 | |||
745d960f62 | |||
531d0fe9a2 | |||
2af23e8084 | |||
cd204fdaa8 | |||
d04ad42b5b | |||
8d80a8fb5a | |||
77032bf9ae | |||
3967366ae6 | |||
cd391378c2 | |||
3fc3a689cb | |||
bd5b32a3b0 | |||
a5c999234c | |||
f64d3e51ce | |||
a650b7d860 | |||
14e98072aa | |||
0bc4c0565f | |||
4aa8e844ac | |||
d7135fddc8 | |||
6e3e978839 | |||
33b0e79c18 | |||
787d878e83 | |||
1e24179b82 | |||
67027840f4 | |||
2755e9c863 | |||
7ae26fb0a9 | |||
98bcf98f1e | |||
bc8ac32fee | |||
ba74eca21d | |||
d4e1cf1322 | |||
5170784e44 | |||
ac323167d0 | |||
003de5bc00 | |||
83595cfadf | |||
cfe21d63d7 | |||
63e72076ed | |||
2a011ee244 | |||
84dd2bd40b | |||
1449cacc0e | |||
f1c2b2eb87 | |||
0f1a3b1d5b | |||
6190cf04db | |||
680aed0e10 | |||
fadb122d28 | |||
4347f89c16 | |||
9856757e49 | |||
afb7b5f42b | |||
ba8506247b | |||
61f3f90d6d | |||
a143527dd0 | |||
f2c9818e61 | |||
9e23af29ff | |||
095885442a | |||
359c9b5a6d | |||
b16a7a4c17 | |||
b4ea175757 | |||
ca4671a7dd | |||
7a4b1a934f | |||
77d90bbe83 | |||
c99d802184 | |||
ca15d2abe4 | |||
bddb48ba9a | |||
f4b67e52b8 | |||
00f6a4b0ae | |||
ab3937a69d | |||
0c4bae48d4 | |||
b4b17cb999 | |||
5ff3864d9d | |||
9b8736d35f | |||
cf0ba20637 | |||
e06c0b9a76 | |||
4cb370ddff | |||
0b9c1711da | |||
e2449030c5 | |||
5a91e87afa | |||
5e66c3bd45 | |||
61c82f796f | |||
7661e0eb20 | |||
c06e6de89a |
208
.github/workflows/gitea-release.yml
vendored
Normal file
@ -0,0 +1,208 @@
|
||||
name: Gitea Release
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
GITEA_TOKEN:
|
||||
description: 'Token für Gitea API-Zugriff'
|
||||
required: true
|
||||
|
||||
outputs:
|
||||
version:
|
||||
description: 'The version that was released'
|
||||
value: ${{ jobs.create-release.outputs.version }}
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.get_version.outputs.VERSION }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install PlatformIO
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --upgrade platformio esptool
|
||||
|
||||
- name: Install xxd
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install xxd
|
||||
|
||||
- name: Build Firmware
|
||||
run: |
|
||||
VERSION=$(grep '^version = ' platformio.ini | cut -d'"' -f2)
|
||||
|
||||
# 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 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 &&
|
||||
esptool.py --chip esp32 merge_bin \
|
||||
--fill-flash-size 4MB \
|
||||
--flash_mode dio \
|
||||
--flash_freq 40m \
|
||||
--flash_size 4MB \
|
||||
-o filaman_full_${VERSION}.bin \
|
||||
0x1000 bootloader.bin \
|
||||
0x8000 partitions.bin \
|
||||
0x10000 firmware.bin \
|
||||
0x3D0000 littlefs.bin)
|
||||
|
||||
# Verify file sizes
|
||||
echo "File sizes:"
|
||||
(cd .pio/build/esp32dev && ls -lh *.bin)
|
||||
|
||||
- name: Get version from platformio.ini
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=$(grep '^version = ' platformio.ini | cut -d'"' -f2)
|
||||
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Generate Release Notes
|
||||
id: release_notes
|
||||
run: |
|
||||
# Get the latest tag
|
||||
LATEST_TAG=$(git for-each-ref --sort=-creatordate --format '%(refname:short)' refs/tags | sed -n '2p')
|
||||
|
||||
if [ -n "$LATEST_TAG" ]; then
|
||||
echo "CHANGES<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "Changes since ${LATEST_TAG}:" >> $GITHUB_OUTPUT
|
||||
echo "" >> $GITHUB_OUTPUT
|
||||
|
||||
# Get all commits since last release with commit hash and author
|
||||
echo "### Added" >> $GITHUB_OUTPUT
|
||||
git log ${LATEST_TAG}..HEAD --pretty=format:"%h - %s (%an)" | grep -iE '^[a-f0-9]+ - (feat|add|new)' | sed 's/^[a-f0-9]* - feat: /- /' >> $GITHUB_OUTPUT || true
|
||||
echo "" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "### Fixed" >> $GITHUB_OUTPUT
|
||||
git log ${LATEST_TAG}..HEAD --pretty=format:"%h - %s (%an)" | grep -iE '^[a-f0-9]+ - fix' | sed 's/^[a-f0-9]* - fix: /- /' >> $GITHUB_OUTPUT || true
|
||||
echo "" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "### Changed" >> $GITHUB_OUTPUT
|
||||
git log ${LATEST_TAG}..HEAD --pretty=format:"%h - %s (%an)" | grep -ivE '^[a-f0-9]+ - (feat|fix|add|new)' | sed 's/^[a-f0-9]* - /- /' >> $GITHUB_OUTPUT || true
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
else
|
||||
# First release
|
||||
echo "CHANGES<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "Initial Release" >> $GITHUB_OUTPUT
|
||||
echo "" >> $GITHUB_OUTPUT
|
||||
|
||||
# Add all commits for initial release
|
||||
echo "### Added" >> $GITHUB_OUTPUT
|
||||
git log --pretty=format:"%h - %s (%an)" | grep -iE '^[a-f0-9]+ - (feat|add|new)' | sed 's/^[a-f0-9]* - feat: /- /' >> $GITHUB_OUTPUT || true
|
||||
echo "" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "### Fixed" >> $GITHUB_OUTPUT
|
||||
git log --pretty=format:"%h - %s (%an)" | grep -iE '^[a-f0-9]+ - fix' | sed 's/^[a-f0-9]* - fix: /- /' >> $GITHUB_OUTPUT || true
|
||||
echo "" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "### Changed" >> $GITHUB_OUTPUT
|
||||
git log --pretty=format:"%h - %s (%an)" | grep -ivE '^[a-f0-9]+ - (feat|fix|add|new)' | sed 's/^[a-f0-9]* - /- /' >> $GITHUB_OUTPUT || true
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Determine Gitea URL
|
||||
id: gitea_url
|
||||
run: |
|
||||
echo "Debug Environment:"
|
||||
echo "GITHUB_SERVER_URL=${GITHUB_SERVER_URL:-not set}"
|
||||
echo "GITEA_SERVER_URL=${GITEA_SERVER_URL:-not set}"
|
||||
echo "GITHUB_REPOSITORY=${GITHUB_REPOSITORY:-not set}"
|
||||
echo "GITEA_REPOSITORY=${GITEA_REPOSITORY:-not set}"
|
||||
echo "RUNNER_NAME=${RUNNER_NAME:-not set}"
|
||||
|
||||
# Set API URL based on environment
|
||||
if [ -n "${GITEA_ACTIONS}" ] || [ -n "${GITEA_REPOSITORY}" ] || [[ "${RUNNER_NAME}" == *"gitea"* ]]; then
|
||||
GITEA_API_URL="${GITHUB_SERVER_URL}"
|
||||
GITEA_REPO=$(echo "${GITHUB_REPOSITORY}" | cut -d'/' -f2)
|
||||
GITEA_OWNER=$(echo "${GITHUB_REPOSITORY}" | cut -d'/' -f1)
|
||||
else
|
||||
echo "Error: This workflow is only for Gitea"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "GITEA_API_URL=${GITEA_API_URL}" >> $GITHUB_OUTPUT
|
||||
echo "GITEA_REPO=${GITEA_REPO}" >> $GITHUB_OUTPUT
|
||||
echo "GITEA_OWNER=${GITEA_OWNER}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create Gitea Release
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
GITEA_API_URL: ${{ steps.gitea_url.outputs.GITEA_API_URL }}
|
||||
GITEA_REPO: ${{ steps.gitea_url.outputs.GITEA_REPO }}
|
||||
GITEA_OWNER: ${{ steps.gitea_url.outputs.GITEA_OWNER }}
|
||||
run: |
|
||||
# Debug Token (nur Länge ausgeben für Sicherheit)
|
||||
echo "Debug: Token length: ${#GITEA_TOKEN}"
|
||||
if [ -z "$GITEA_TOKEN" ]; then
|
||||
echo "Error: GITEA_TOKEN is empty"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION=${{ steps.get_version.outputs.VERSION }}
|
||||
cd .pio/build/esp32dev
|
||||
|
||||
# Debug-Ausgaben
|
||||
echo "Debug: API URL: ${GITEA_API_URL}"
|
||||
echo "Debug: Repository: ${GITEA_OWNER}/${GITEA_REPO}"
|
||||
|
||||
# Erstelle zuerst den Release ohne Dateien
|
||||
echo "Debug: Creating release..."
|
||||
RELEASE_DATA="{\"tag_name\":\"v${VERSION}\",\"name\":\"v${VERSION}\",\"body\":\"${{ steps.release_notes.outputs.CHANGES }}\"}"
|
||||
|
||||
RELEASE_RESPONSE=$(curl -s -w "\n%{http_code}" \
|
||||
-X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "${RELEASE_DATA}" \
|
||||
"${GITEA_API_URL}/api/v1/repos/${GITEA_OWNER}/${GITEA_REPO}/releases")
|
||||
|
||||
RELEASE_STATUS=$(echo "$RELEASE_RESPONSE" | tail -n1)
|
||||
RELEASE_BODY=$(echo "$RELEASE_RESPONSE" | head -n -1)
|
||||
|
||||
if [ "$RELEASE_STATUS" != "201" ]; then
|
||||
echo "Error: Failed to create release"
|
||||
echo "Response: $RELEASE_BODY"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extrahiere die Release-ID aus der Antwort
|
||||
RELEASE_ID=$(echo "$RELEASE_BODY" | grep -o '"id":[0-9]*' | cut -d':' -f2)
|
||||
|
||||
# Lade die Dateien einzeln hoch
|
||||
for file in upgrade_filaman_firmware_v${VERSION}.bin upgrade_filaman_website_v${VERSION}.bin filaman_full_${VERSION}.bin; do
|
||||
if [ -f "$file" ]; then
|
||||
echo "Debug: Uploading $file..."
|
||||
UPLOAD_RESPONSE=$(curl -s -w "\n%{http_code}" \
|
||||
-X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
--data-binary @"$file" \
|
||||
"${GITEA_API_URL}/api/v1/repos/${GITEA_OWNER}/${GITEA_REPO}/releases/${RELEASE_ID}/assets?name=${file}")
|
||||
|
||||
UPLOAD_STATUS=$(echo "$UPLOAD_RESPONSE" | tail -n1)
|
||||
if [ "$UPLOAD_STATUS" != "201" ]; then
|
||||
echo "Warning: Failed to upload $file"
|
||||
echo "Response: $(echo "$UPLOAD_RESPONSE" | head -n -1)"
|
||||
else
|
||||
echo "Successfully uploaded $file"
|
||||
fi
|
||||
fi
|
||||
done
|
185
.github/workflows/github-release.yml
vendored
Normal file
@ -0,0 +1,185 @@
|
||||
name: GitHub Release
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
RELEASE_TOKEN:
|
||||
description: 'GitHub token for release creation'
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install PlatformIO
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --upgrade platformio esptool
|
||||
|
||||
- name: Install xxd
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install xxd
|
||||
|
||||
- name: Build Firmware
|
||||
run: |
|
||||
VERSION=$(grep '^version = ' platformio.ini | cut -d'"' -f2)
|
||||
|
||||
# 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 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 &&
|
||||
esptool.py --chip esp32 merge_bin \
|
||||
--fill-flash-size 4MB \
|
||||
--flash_mode dio \
|
||||
--flash_freq 40m \
|
||||
--flash_size 4MB \
|
||||
-o filaman_full_${VERSION}.bin \
|
||||
0x1000 bootloader.bin \
|
||||
0x8000 partitions.bin \
|
||||
0x10000 firmware.bin \
|
||||
0x3D0000 littlefs.bin)
|
||||
|
||||
# Verify file sizes
|
||||
echo "File sizes:"
|
||||
(cd .pio/build/esp32dev && ls -lh *.bin)
|
||||
|
||||
- name: Get version from platformio.ini
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=$(grep '^version = ' platformio.ini | cut -d'"' -f2)
|
||||
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Generate Release Notes
|
||||
id: release_notes
|
||||
run: |
|
||||
# Get the latest tag
|
||||
LATEST_TAG=$(git for-each-ref --sort=-creatordate --format '%(refname:short)' refs/tags | sed -n '2p')
|
||||
|
||||
if [ -n "$LATEST_TAG" ]; then
|
||||
echo "CHANGES<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "Changes since ${LATEST_TAG}:" >> $GITHUB_OUTPUT
|
||||
echo "" >> $GITHUB_OUTPUT
|
||||
|
||||
# Get all commits since last release with commit hash and author
|
||||
echo "### Added" >> $GITHUB_OUTPUT
|
||||
git log ${LATEST_TAG}..HEAD --pretty=format:"%h - %s (%an)" | grep -iE '^[a-f0-9]+ - (feat|add|new)' | sed 's/^[a-f0-9]* - feat: /- /' >> $GITHUB_OUTPUT || true
|
||||
echo "" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "### Fixed" >> $GITHUB_OUTPUT
|
||||
git log ${LATEST_TAG}..HEAD --pretty=format:"%h - %s (%an)" | grep -iE '^[a-f0-9]+ - fix' | sed 's/^[a-f0-9]* - fix: /- /' >> $GITHUB_OUTPUT || true
|
||||
echo "" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "### Changed" >> $GITHUB_OUTPUT
|
||||
git log ${LATEST_TAG}..HEAD --pretty=format:"%h - %s (%an)" | grep -ivE '^[a-f0-9]+ - (feat|fix|add|new)' | sed 's/^[a-f0-9]* - /- /' >> $GITHUB_OUTPUT || true
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
else
|
||||
# First release
|
||||
echo "CHANGES<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "Initial Release" >> $GITHUB_OUTPUT
|
||||
echo "" >> $GITHUB_OUTPUT
|
||||
|
||||
# Add all commits for initial release
|
||||
echo "### Added" >> $GITHUB_OUTPUT
|
||||
git log --pretty=format:"%h - %s (%an)" | grep -iE '^[a-f0-9]+ - (feat|add|new)' | sed 's/^[a-f0-9]* - feat: /- /' >> $GITHUB_OUTPUT || true
|
||||
echo "" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "### Fixed" >> $GITHUB_OUTPUT
|
||||
git log --pretty=format:"%h - %s (%an)" | grep -iE '^[a-f0-9]+ - fix' | sed 's/^[a-f0-9]* - fix: /- /' >> $GITHUB_OUTPUT || true
|
||||
echo "" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "### Changed" >> $GITHUB_OUTPUT
|
||||
git log --pretty=format:"%h - %s (%an)" | grep -ivE '^[a-f0-9]+ - (feat|fix|add|new)' | sed 's/^[a-f0-9]* - /- /' >> $GITHUB_OUTPUT || true
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Create GitHub Release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
run: |
|
||||
VERSION=${{ steps.get_version.outputs.VERSION }}
|
||||
cd .pio/build/esp32dev
|
||||
|
||||
# Create release with available files
|
||||
FILES_TO_UPLOAD=""
|
||||
|
||||
# Always add firmware
|
||||
if [ -f "upgrade_filaman_firmware_v${VERSION}.bin" ]; then
|
||||
FILES_TO_UPLOAD="$FILES_TO_UPLOAD upgrade_filaman_firmware_v${VERSION}.bin"
|
||||
fi
|
||||
|
||||
# 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
|
||||
|
||||
if [ -f "filaman_full_${VERSION}.bin" ]; then
|
||||
FILES_TO_UPLOAD="$FILES_TO_UPLOAD filaman_full_${VERSION}.bin"
|
||||
fi
|
||||
|
||||
# Create release with available files
|
||||
if [ -n "$FILES_TO_UPLOAD" ]; then
|
||||
gh release create "v${VERSION}" \
|
||||
--title "Release ${VERSION}" \
|
||||
--notes "${{ steps.release_notes.outputs.CHANGES }}" \
|
||||
$FILES_TO_UPLOAD
|
||||
else
|
||||
echo "Error: No files found to upload"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Install lftp
|
||||
run: sudo apt-get install -y lftp
|
||||
|
||||
- name: Upload Firmware via FTP
|
||||
if: success()
|
||||
env:
|
||||
FTP_PASSWORD: ${{ vars.FTP_PASSWORD }}
|
||||
FTP_USER: ${{ vars.FTP_USER }}
|
||||
FTP_HOST: ${{ vars.FTP_HOST }}
|
||||
VERSION: ${{ steps.get_version.outputs.VERSION }}
|
||||
run: |
|
||||
echo "Environment variables:"
|
||||
env | grep -E '^FTP_' | while read -r line; do
|
||||
var_name=$(echo "$line" | cut -d= -f1)
|
||||
var_value=$(echo "$line" | cut -d= -f2-)
|
||||
echo "$var_name is $(if [ -n "$var_value" ]; then echo "set"; else echo "empty"; fi)"
|
||||
done
|
||||
|
||||
cd .pio/build/esp32dev
|
||||
if [ -n "$FTP_USER" ] && [ -n "$FTP_PASSWORD" ] && [ -n "$FTP_HOST" ]; then
|
||||
echo "All FTP credentials are present, attempting upload..."
|
||||
lftp -c "set ssl:verify-certificate no; \
|
||||
set ftp:ssl-protect-data true; \
|
||||
set ftp:ssl-force true; \
|
||||
set ssl:check-hostname false; \
|
||||
set ftp:ssl-auth TLS; \
|
||||
open -u $FTP_USER,$FTP_PASSWORD $FTP_HOST; \
|
||||
put -O / filaman_full_${VERSION}.bin -o filaman_full.bin"
|
||||
else
|
||||
echo "Error: Some FTP credentials are missing"
|
||||
exit 1
|
||||
fi
|
63
.github/workflows/providers/gitea-release.yml
vendored
@ -1,63 +0,0 @@
|
||||
name: Gitea Release
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
version:
|
||||
type: string
|
||||
required: true
|
||||
changelog:
|
||||
type: string
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: gitea-runner
|
||||
steps:
|
||||
- name: Debug Info
|
||||
run: |
|
||||
echo "Version: ${{ inputs.version }}"
|
||||
echo "Changelog: ${{ inputs.changelog }}"
|
||||
echo "Tag: ${GITHUB_REF#refs/tags/}"
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install platformio esptool
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
pio run -t buildfs
|
||||
pio run
|
||||
|
||||
- name: Create full firmware
|
||||
run: |
|
||||
esptool.py --chip esp32 merge_bin \
|
||||
--flash_mode dio \
|
||||
--flash_freq 40m \
|
||||
--flash_size 4MB \
|
||||
-o .pio/build/esp32dev/filaman_full.bin \
|
||||
0x1000 .pio/build/esp32dev/bootloader.bin \
|
||||
0x8000 .pio/build/esp32dev/partitions.bin \
|
||||
0x10000 .pio/build/esp32dev/firmware.bin \
|
||||
0x290000 .pio/build/esp32dev/spiffs.bin
|
||||
cp .pio/build/esp32dev/firmware.bin .pio/build/esp32dev/filaman_ota.bin
|
||||
|
||||
- name: Create Release
|
||||
run: |
|
||||
TAG="${GITHUB_REF#refs/tags/}"
|
||||
curl -X POST "${GITEA_API_URL}/repos/${GITEA_REPOSITORY}/releases" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\":\"$TAG\",\"name\":\"Release ${{ inputs.version }}\",\"body\":\"${{ inputs.changelog }}\",\"draft\":false,\"prerelease\":false}"
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
GITEA_API_URL: ${{ secrets.GITEA_API_URL }}
|
||||
GITEA_REPOSITORY: ${{ secrets.GITEA_REPOSITORY }}
|
60
.github/workflows/providers/github-release.yml
vendored
@ -1,60 +0,0 @@
|
||||
name: GitHub Release
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
version:
|
||||
required: true
|
||||
type: string
|
||||
changelog:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --upgrade platformio esptool
|
||||
|
||||
- name: Build Firmware
|
||||
run: |
|
||||
pio run -t buildfs
|
||||
pio run
|
||||
|
||||
- name: Merge firmware and SPIFFS
|
||||
run: |
|
||||
esptool.py --chip esp32 merge_bin \
|
||||
--flash_mode dio \
|
||||
--flash_freq 40m \
|
||||
--flash_size 4MB \
|
||||
-o .pio/build/esp32dev/filaman_full.bin \
|
||||
0x1000 .pio/build/esp32dev/bootloader.bin \
|
||||
0x8000 .pio/build/esp32dev/partitions.bin \
|
||||
0x10000 .pio/build/esp32dev/firmware.bin \
|
||||
0x290000 .pio/build/esp32dev/spiffs.bin
|
||||
|
||||
- name: Prepare OTA firmware
|
||||
run: cp .pio/build/esp32dev/firmware.bin .pio/build/esp32dev/filaman_ota.bin
|
||||
|
||||
- name: Create GitHub Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release create "${{ github.ref_name }}" \
|
||||
--title "Release ${{ inputs.version }}" \
|
||||
--notes "${{ inputs.changelog }}" \
|
||||
".pio/build/esp32dev/filaman_full.bin#filaman_full.bin" \
|
||||
".pio/build/esp32dev/filaman_ota.bin#filaman_ota.bin"
|
108
.github/workflows/release.yml
vendored
@ -1,91 +1,41 @@
|
||||
name: Release
|
||||
name: Release Workflow
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: gitea-runner
|
||||
detect-provider:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
changelog: ${{ steps.changelog.outputs.changelog }}
|
||||
provider: ${{ steps.provider.outputs.provider }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Get version from tag
|
||||
id: version
|
||||
- name: Determine CI Provider
|
||||
id: provider
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION=${GITHUB_REF#refs/tags/v}
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get changelog
|
||||
id: changelog
|
||||
run: |
|
||||
CHANGELOG=$(awk "/## \\[${{ steps.version.outputs.version }}\\]/{p=1;print;next} /## \\[/{p=0} p" CHANGELOG.md)
|
||||
echo "changelog<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$CHANGELOG" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
if [ -n "${GITEA_ACTIONS}" ] || [ -n "${GITEA_REPOSITORY}" ] || [[ "${RUNNER_NAME}" == *"gitea"* ]]; then
|
||||
echo "provider=gitea" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "provider=github" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
github:
|
||||
needs: prepare
|
||||
if: github.server_url == 'https://github.com'
|
||||
uses: ./.github/workflows/providers/github-release.yml
|
||||
with:
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
changelog: ${{ needs.prepare.outputs.changelog }}
|
||||
secrets: inherit
|
||||
github-release:
|
||||
needs: detect-provider
|
||||
permissions:
|
||||
contents: write
|
||||
if: needs.detect-provider.outputs.provider == 'github'
|
||||
uses: ./.github/workflows/github-release.yml
|
||||
secrets:
|
||||
RELEASE_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
gitea:
|
||||
needs: prepare
|
||||
if: github.server_url != 'https://github.com'
|
||||
runs-on: gitea-runner
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install platformio esptool
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
pio run -t buildfs
|
||||
pio run
|
||||
|
||||
- name: Create full firmware
|
||||
run: |
|
||||
esptool.py --chip esp32 merge_bin \
|
||||
--flash_mode dio \
|
||||
--flash_freq 40m \
|
||||
--flash_size 4MB \
|
||||
-o .pio/build/esp32dev/filaman_full.bin \
|
||||
0x1000 .pio/build/esp32dev/bootloader.bin \
|
||||
0x8000 .pio/build/esp32dev/partitions.bin \
|
||||
0x10000 .pio/build/esp32dev/firmware.bin \
|
||||
0x290000 .pio/build/esp32dev/spiffs.bin
|
||||
cp .pio/build/esp32dev/firmware.bin .pio/build/esp32dev/filaman_ota.bin
|
||||
|
||||
- name: Create Release
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
GITEA_API_URL: ${{ secrets.GITEA_API_URL }}
|
||||
GITEA_REPOSITORY: ${{ secrets.GITEA_REPOSITORY }}
|
||||
run: |
|
||||
echo "Creating release for version ${{ needs.prepare.outputs.version }}"
|
||||
curl -X POST "${GITEA_API_URL}/repos/${GITEA_REPOSITORY}/releases" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"tag_name\": \"${GITHUB_REF#refs/tags/}\",
|
||||
\"name\": \"Release ${{ needs.prepare.outputs.version }}\",
|
||||
\"body\": \"${{ needs.prepare.outputs.changelog }}\",
|
||||
\"draft\": false,
|
||||
\"prerelease\": false
|
||||
}"
|
||||
gitea-release:
|
||||
needs: detect-provider
|
||||
if: needs.detect-provider.outputs.provider == 'gitea'
|
||||
uses: ./.github/workflows/gitea-release.yml
|
||||
secrets:
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
54
.vscode/settings.json
vendored
@ -1,54 +0,0 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"algorithm": "cpp",
|
||||
"vector": "cpp",
|
||||
"cmath": "cpp",
|
||||
"array": "cpp",
|
||||
"atomic": "cpp",
|
||||
"*.tcc": "cpp",
|
||||
"bitset": "cpp",
|
||||
"cctype": "cpp",
|
||||
"clocale": "cpp",
|
||||
"cstdarg": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"cstring": "cpp",
|
||||
"ctime": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"cwctype": "cpp",
|
||||
"deque": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"unordered_set": "cpp",
|
||||
"exception": "cpp",
|
||||
"functional": "cpp",
|
||||
"iterator": "cpp",
|
||||
"map": "cpp",
|
||||
"memory": "cpp",
|
||||
"memory_resource": "cpp",
|
||||
"numeric": "cpp",
|
||||
"optional": "cpp",
|
||||
"random": "cpp",
|
||||
"regex": "cpp",
|
||||
"string": "cpp",
|
||||
"string_view": "cpp",
|
||||
"system_error": "cpp",
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"utility": "cpp",
|
||||
"fstream": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"iomanip": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"istream": "cpp",
|
||||
"limits": "cpp",
|
||||
"new": "cpp",
|
||||
"ostream": "cpp",
|
||||
"sstream": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"cinttypes": "cpp",
|
||||
"typeinfo": "cpp"
|
||||
}
|
||||
}
|
1501
CHANGELOG.md
78
README.de.md
@ -6,9 +6,12 @@ Das System integriert sich nahtlos mit der [Spoolman](https://github.com/Donkie/
|
||||
|
||||

|
||||
|
||||
Weitere Bilder finden Sie im [img Ordner](/img/)
|
||||
Weitere Bilder finden Sie im [img Ordner](/img/)
|
||||
oder auf meiner Website: [FilaMan Website](https://www.filaman.app)
|
||||
Deutsches Erklärvideo: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaOHU)
|
||||
Deutsches Erklärvideo: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaOHU)
|
||||
Discord Server: [https://discord.gg/my7Gvaxj2v](https://discord.gg/my7Gvaxj2v)
|
||||
|
||||
### Es gibt jetzt auch ein Wiki, dort sind nochmal alle Funktionen beschrieben: [Wiki](https://github.com/ManuelW77/Filaman/wiki)
|
||||
|
||||
### ESP32 Hardware-Funktionen
|
||||
- **Gewichtsmessung:** Verwendung einer Wägezelle mit HX711-Verstärker für präzise Gewichtsverfolgung.
|
||||
@ -16,7 +19,7 @@ Deutsches Erklärvideo: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaO
|
||||
- **OLED-Display:** Zeigt aktuelles Gewicht, Verbindungsstatus (WiFi, Bambu Lab, Spoolman).
|
||||
- **WLAN-Konnektivität:** WiFiManager für einfache Netzwerkkonfiguration.
|
||||
- **MQTT-Integration:** Verbindet sich mit Bambu Lab Drucker für AMS-Steuerung.
|
||||
- **NFC-Tag NTAG215:** Verwendung von NTAG215 wegen ausreichendem Speicherplatz auf dem Tag
|
||||
- **NFC-Tag NTAG213 NTAG215:** Verwendung von NTAG213, besser NTAG215 wegen ausreichendem Speicherplatz auf dem Tag
|
||||
|
||||
### Weboberflächen-Funktionen
|
||||
- **Echtzeit-Updates:** WebSocket-Verbindung für Live-Daten-Updates.
|
||||
@ -33,6 +36,7 @@ Deutsches Erklärvideo: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaO
|
||||
- Filtern und Auswählen von Filamenten.
|
||||
- Automatische Aktualisierung der Spulengewichte.
|
||||
- Verfolgung von NFC-Tag-Zuweisungen.
|
||||
- Unterstützt das Spoolman Octoprint Plugin
|
||||
|
||||
### Wenn Sie meine Arbeit unterstützen möchten, freue ich mich über einen Kaffee
|
||||
<a href="https://www.buymeacoffee.com/manuelw" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 30px !important;width: 108px !important;" ></a>
|
||||
@ -51,19 +55,22 @@ 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 Wägezellen-Verstärker:** Für Gewichtsmessung.
|
||||
[Amazon Link](https://amzn.eu/d/1wZ4v0x)
|
||||
- **OLED Display:** 128x64 SSD1306.
|
||||
[Amazon Link](https://amzn.eu/d/dozAYDU)
|
||||
- **PN532 NFC Modul:** Für NFC-Tag-Operationen.
|
||||
[Amazon Link](https://amzn.eu/d/8205DDh)
|
||||
- **NFC-Tag:** NTAG215
|
||||
[Amazon Link](https://amzn.eu/d/fywy4c4)
|
||||
- **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.to/4iO6CO4)
|
||||
- **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 |
|
||||
@ -71,10 +78,17 @@ Deutsches Erklärvideo: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaO
|
||||
| OLED SCL | 22 |
|
||||
| PN532 IRQ | 32 |
|
||||
| PN532 RESET | 33 |
|
||||
| PN532 SCK | 14 |
|
||||
| PN532 MOSI | 13 |
|
||||
| PN532 MISO | 12 |
|
||||
| PN532 CS/SS | 15 |
|
||||
| PN532 SDA | 21 |
|
||||
| PN532 SCL | 22 |
|
||||
| TTP223 I/O | 25 |
|
||||
|
||||
**!! Achte darauf, dass am PN532 die DIP-Schalter auf I2C gestellt sind**
|
||||
**Nutze den 3V Pin vom ESP für den Touch Sensor**
|
||||
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
## Software-Abhängigkeiten
|
||||
|
||||
@ -101,7 +115,31 @@ Deutsches Erklärvideo: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaO
|
||||
- PN532 NFC Modul
|
||||
- Verbindungskabel
|
||||
|
||||
### Schritt-für-Schritt Installation
|
||||
## Wichtiger Hinweis
|
||||
Du musst Spoolman auf DEBUG Modus setzten, da man bisher in Spoolman keine CORS Domains setzen kann!
|
||||
|
||||
```
|
||||
# Enable debug mode
|
||||
# If enabled, the client will accept requests from any host
|
||||
# This can be useful when developing, but is also a security risk
|
||||
# Default: FALSE
|
||||
#SPOOLMAN_DEBUG_MODE=TRUE
|
||||
```
|
||||
|
||||
## Schritt-für-Schritt Installation
|
||||
### Einfache Installation
|
||||
1. **Gehe auf [FilaMan Installer](https://www.filaman.app/installer.html)**
|
||||
|
||||
2. **Stecke dein ESP an den Rechner und klicke Connect**
|
||||
|
||||
3. **Wähle dein Device Port und klicke Intall**
|
||||
|
||||
4. **Ersteinrichtung:**
|
||||
- Mit dem "FilaMan" WLAN-Zugangspunkt verbinden.
|
||||
- WLAN-Einstellungen über das Konfigurationsportal vornehmen.
|
||||
- Weboberfläche unter `http://filaman.local` oder der IP-Adresse aufrufen.
|
||||
|
||||
### Compile by yourself
|
||||
1. **Repository klonen:**
|
||||
```bash
|
||||
git clone https://github.com/ManuelW77/Filaman.git
|
||||
|
96
README.md
@ -6,12 +6,16 @@ FilaMan is a filament management system for 3D printing. It uses ESP32 hardware
|
||||
Users can manage filament spools, monitor the status of the Automatic Material System (AMS) and make settings via a web interface.
|
||||
The system integrates seamlessly with [Bambulab](https://bambulab.com/en-us) 3D printers and [Spoolman](https://github.com/Donkie/Spoolman) filament management as well as the [Openspool](https://github.com/spuder/OpenSpool) NFC-TAG format.
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
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)
|
||||
|
||||
### ESP32 Hardware Features
|
||||
- **Weight Measurement:** Using a load cell with HX711 amplifier for precise weight tracking.
|
||||
@ -19,7 +23,7 @@ german explanatory video: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62z
|
||||
- **OLED Display:** Shows current weight, connection status (WiFi, Bambu Lab, Spoolman).
|
||||
- **WiFi Connectivity:** WiFiManager for easy network configuration.
|
||||
- **MQTT Integration:** Connects to Bambu Lab printer for AMS control.
|
||||
- **NFC-Tag NTAG215:** Use NTAG215 because of enaught space on the Tag
|
||||
- **NFC-Tag NTAG213 NTAG215:** Use NTAG213, better NTAG215 because of enaught space on the Tag
|
||||
|
||||
### Web Interface Features
|
||||
- **Real-time Updates:** WebSocket connection for live data updates.
|
||||
@ -36,6 +40,7 @@ german explanatory video: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62z
|
||||
- Filter and select filaments.
|
||||
- Update spool weights automatically.
|
||||
- Track NFC tag assignments.
|
||||
- Supports Spoolman Octoprint Plugin
|
||||
|
||||
### If you want to support my work, i would be happy to get a coffe
|
||||
<a href="https://www.buymeacoffee.com/manuelw" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 30px !important;width: 108px !important;" ></a>
|
||||
@ -55,15 +60,17 @@ german explanatory video: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62z
|
||||
|
||||
### Components
|
||||
- **ESP32 Development Board:** Any ESP32 variant.
|
||||
[Amazon Link](https://amzn.eu/d/aXThslf)
|
||||
- **HX711 Load Cell Amplifier:** For weight measurement.
|
||||
[Amazon Link](https://amzn.eu/d/1wZ4v0x)
|
||||
- **OLED Display:** 128x64 SSD1306.
|
||||
[Amazon Link](https://amzn.eu/d/dozAYDU)
|
||||
- **PN532 NFC Module:** For NFC tag operations.
|
||||
[Amazon Link](https://amzn.eu/d/8205DDh)
|
||||
- **NFC-Tag:** NTAG215
|
||||
[Amazon Link](https://amzn.eu/d/fywy4c4)
|
||||
[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.to/4iO6CO4)
|
||||
- **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
|
||||
@ -75,10 +82,17 @@ german explanatory video: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62z
|
||||
| OLED SCL | 22 |
|
||||
| PN532 IRQ | 32 |
|
||||
| PN532 RESET | 33 |
|
||||
| PN532 SCK | 14 |
|
||||
| PN532 MOSI | 13 |
|
||||
| PN532 MISO | 12 |
|
||||
| PN532 CS/SS | 15 |
|
||||
| PN532 SDA | 21 |
|
||||
| PN532 SCL | 22 |
|
||||
| TTP223 I/O | 25 |
|
||||
|
||||
**!! Make sure that the DIP switches on the PN532 are set to I2C**
|
||||
**Use the 3V pin from the ESP for the touch sensor**
|
||||
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
## Software Dependencies
|
||||
|
||||
@ -91,9 +105,9 @@ german explanatory video: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62z
|
||||
- `Adafruit_SSD1306`: OLED display control
|
||||
- `HX711`: Load cell communication
|
||||
|
||||
## Installation
|
||||
### Installation
|
||||
|
||||
### Prerequisites
|
||||
## Prerequisites
|
||||
- **Software:**
|
||||
- [PlatformIO](https://platformio.org/) in VS Code
|
||||
- [Spoolman](https://github.com/Donkie/Spoolman) instance
|
||||
@ -105,7 +119,32 @@ german explanatory video: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62z
|
||||
- PN532 NFC Module
|
||||
- Connecting wires
|
||||
|
||||
### Step-by-Step Installation
|
||||
## Important Note
|
||||
You have to activate Spoolman in debug mode, because you are not able to set CORS Domains in Spoolman yet.
|
||||
|
||||
```
|
||||
# Enable debug mode
|
||||
# If enabled, the client will accept requests from any host
|
||||
# This can be useful when developing, but is also a security risk
|
||||
# Default: FALSE
|
||||
#SPOOLMAN_DEBUG_MODE=TRUE
|
||||
```
|
||||
|
||||
|
||||
## Step-by-Step Installation
|
||||
### Easy Installation
|
||||
1. **Go to [FilaMan Installer](https://www.filaman.app/installer.html)**
|
||||
|
||||
2. **Plug you device in and push Connect button**
|
||||
|
||||
3. **Select your Device Port and push Intall**
|
||||
|
||||
4. **Initial Setup:**
|
||||
- Connect to the "FilaMan" WiFi access point.
|
||||
- Configure WiFi settings through the captive portal.
|
||||
- Access the web interface at `http://filaman.local` or the IP address.
|
||||
|
||||
### Compile by yourself
|
||||
1. **Clone the Repository:**
|
||||
```bash
|
||||
git clone https://github.com/ManuelW77/Filaman.git
|
||||
@ -124,25 +163,6 @@ german explanatory video: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62z
|
||||
- Configure WiFi settings through the captive portal.
|
||||
- Access the web interface at `http://filaman.local` or the IP address.
|
||||
|
||||
## GitHub Actions Configuration
|
||||
|
||||
### Required Secrets for Gitea Releases
|
||||
|
||||
When using Gitea as your repository host, you need to configure the following secrets in your repository:
|
||||
|
||||
- `GITEA_API_URL`: The base URL of your Gitea instance, including protocol (e.g., `https://git.example.com`)
|
||||
- `GITEA_TOKEN`: Your Gitea access token with permissions to create releases
|
||||
- `GITEA_REPOSITORY`: The repository name in format `owner/repo` (e.g., `username/filaman`)
|
||||
|
||||
Example values:
|
||||
```
|
||||
GITEA_API_URL=https://git.example.com
|
||||
GITEA_TOKEN=abcdef1234567890
|
||||
GITEA_REPOSITORY=username/filaman
|
||||
```
|
||||
|
||||
Make sure to set these secrets in your repository settings under Settings > Secrets and Variables > Actions.
|
||||
|
||||
## Documentation
|
||||
|
||||
### Relevant Links
|
||||
|
15297
_3D Print Files/FilaMan-Waage.step
Normal file
BIN
_3D Print Files/Filaman-Waage.f3z
Normal file
@ -1,7 +1,31 @@
|
||||
{
|
||||
"GFU99": "Generic TPU",
|
||||
"GFN99": "Generic PA",
|
||||
"GFN98": "Generic PA-CF",
|
||||
"GFU99": "TPU",
|
||||
"GFN99": "PA",
|
||||
"GFN98": "PA-CF",
|
||||
"GFL99": "PLA",
|
||||
"GFL96": "PLA Silk",
|
||||
"GFL98": "PLA-CF",
|
||||
"GFL95": "PLA High Speed",
|
||||
"GFG99": "PETG",
|
||||
"GFG98": "PETG-CF",
|
||||
"GFG97": "PCTG",
|
||||
"GFB99": "ABS",
|
||||
"GFC99": "PC",
|
||||
"GFB98": "ASA",
|
||||
"GFS99": "PVA",
|
||||
"GFS98": "HIPS",
|
||||
"GFT98": "PPS-CF",
|
||||
"GFT97": "PPS",
|
||||
"GFN97": "PPA-CF",
|
||||
"GFN96": "PPA-GF",
|
||||
"GFP99": "PE",
|
||||
"GFP98": "PE-CF",
|
||||
"GFP97": "PP",
|
||||
"GFP96": "PP-CF",
|
||||
"GFP95": "PP-GF",
|
||||
"GFR99": "EVA",
|
||||
"GFR98": "PHA",
|
||||
"GFS97": "BVOH",
|
||||
"GFA01": "Bambu PLA Matte",
|
||||
"GFA00": "Bambu PLA Basic",
|
||||
"GFA09": "Bambu PLA Tough",
|
||||
@ -13,15 +37,11 @@
|
||||
"GFL03": "eSUN PLA+",
|
||||
"GFL01": "PolyTerra PLA",
|
||||
"GFL00": "PolyLite PLA",
|
||||
"GFL99": "Generic PLA",
|
||||
"GFL96": "Generic PLA Silk",
|
||||
"GFL98": "Generic PLA-CF",
|
||||
"GFA50": "Bambu PLA-CF",
|
||||
"GFS02": "Bambu Support For PLA",
|
||||
"GFA11": "Bambu PLA Aero",
|
||||
"GFL04": "Overture PLA",
|
||||
"GFL05": "Overture Matte PLA",
|
||||
"GFL95": "Generic PLA High Speed",
|
||||
"GFA12": "Bambu PLA Glow",
|
||||
"GFA13": "Bambu PLA Dynamic",
|
||||
"GFA15": "Bambu PLA Galaxy",
|
||||
@ -30,41 +50,21 @@
|
||||
"GFU00": "Bambu TPU 95A HF",
|
||||
"GFG00": "Bambu PETG Basic",
|
||||
"GFT01": "Bambu PET-CF",
|
||||
"GFG99": "Generic PETG",
|
||||
"GFG98": "Generic PETG-CF",
|
||||
"GFG50": "Bambu PETG-CF",
|
||||
"GFG60": "PolyLite PETG",
|
||||
"GFG01": "Bambu PETG Translucent",
|
||||
"GFG97": "Generic PCTG",
|
||||
"GFB00": "Bambu ABS",
|
||||
"GFB99": "Generic ABS",
|
||||
"GFB60": "PolyLite ABS",
|
||||
"GFB50": "Bambu ABS-GF",
|
||||
"GFC00": "Bambu PC",
|
||||
"GFC99": "Generic PC",
|
||||
"GFB98": "Generic ASA",
|
||||
"GFB01": "Bambu ASA",
|
||||
"GFB61": "PolyLite ASA",
|
||||
"GFB02": "Bambu ASA-Aero",
|
||||
"GFS99": "Generic PVA",
|
||||
"GFS04": "Bambu PVA",
|
||||
"GFS01": "Bambu Support G",
|
||||
"GFN03": "Bambu PA-CF",
|
||||
"GFN04": "Bambu PAHT-CF",
|
||||
"GFS03": "Bambu Support For PA/PET",
|
||||
"GFN05": "Bambu PA6-CF",
|
||||
"GFN08": "Bambu PA6-GF",
|
||||
"GFS98": "Generic HIPS",
|
||||
"GFT98": "Generic PPS-CF",
|
||||
"GFT97": "Generic PPS",
|
||||
"GFN97": "Generic PPA-CF",
|
||||
"GFN96": "Generic PPA-GF",
|
||||
"GFP99": "Generic PE",
|
||||
"GFP98": "Generic PE-CF",
|
||||
"GFP97": "Generic PP",
|
||||
"GFP96": "Generic PP-CF",
|
||||
"GFP95": "Generic PP-GF",
|
||||
"GFR99": "Generic EVA",
|
||||
"GFR98": "Generic PHA",
|
||||
"GFS97": "Generic BVOH"
|
||||
"GFN08": "Bambu PA6-GF"
|
||||
}
|
@ -6,13 +6,24 @@
|
||||
<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">v1.2.4</span></h1>
|
||||
<h1>FilaMan<span class="version"></span></h1>
|
||||
<h4>Filament Management Tool</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -6,13 +6,24 @@
|
||||
<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">v1.2.4</span></h1>
|
||||
<h1>FilaMan<span class="version"></span></h1>
|
||||
<h4>Filament Management Tool</h4>
|
||||
</div>
|
||||
</div>
|
||||
@ -36,7 +47,7 @@
|
||||
|
||||
<!-- head -->
|
||||
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
<h1>FilaMan</h1>
|
||||
<p>Filament Management Tool</p>
|
||||
<p>Your smart solution for <strong>Filament Management</strong> in 3D printing.</p>
|
||||
@ -44,10 +55,11 @@
|
||||
<h2>About FilaMan</h2>
|
||||
<p>
|
||||
FilaMan is a tool designed to simplify filament spool management. It allows you to identify and weigh filament spools,
|
||||
automatically sync data with the self-hosted <a href="https://github.com/Donkie/Spoolman" target="_blank">Spoolman</a> platform,
|
||||
and ensure compatibility with <a href="https://github.com/spuder/OpenSpool" target="_blank">OpenSpool</a> for Bambu printers.
|
||||
automatically sync data with the self-hosted <a href="https://github.com/Donkie/Spoolman" target="_blank">Spoolman</a> platform.
|
||||
</p>
|
||||
|
||||
<p>Get more information at <a href="https://www.filaman.app" target="_blank">https://www.filaman.app</a> and <a href="https://github.com/ManuelW77/Filaman" target="_blank">https://github.com/ManuelW77/Filaman</a>.</p>
|
||||
|
||||
<div class="features">
|
||||
<div class="feature">
|
||||
<h3>Spool Identification</h3>
|
||||
@ -62,12 +74,6 @@
|
||||
<p>Works with OpenSpool to recognize and activate spools on Bambu printers.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Future Plans</h2>
|
||||
<p>
|
||||
We are working on expanding compatibility to support smaller NFC tags like NTag213
|
||||
and developing custom software to enhance the OpenSpool experience.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
31
html/own_filaments.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"TPU": "GFU99",
|
||||
"PA": "GFN99",
|
||||
"PA-CF": "GFN98",
|
||||
"PLA": "GFL99",
|
||||
"PLA Silk": "GFL96",
|
||||
"PLA-CF": "GFL98",
|
||||
"PLA High Speed": "GFL95",
|
||||
"PETG": "GFG99",
|
||||
"PETG-CF": "GFG98",
|
||||
"PCTG": "GFG97",
|
||||
"ABS": "GFB99",
|
||||
"ABS+HS": "GFB99",
|
||||
"PC": "GFC99",
|
||||
"PC/ABS": "GFC99",
|
||||
"ASA": "GFB98",
|
||||
"PVA": "GFS99",
|
||||
"HIPS": "GFS98",
|
||||
"PPS-CF": "GFT98",
|
||||
"PPS": "GFT97",
|
||||
"PPA-CF": "GFN97",
|
||||
"PPA-GF": "GFN96",
|
||||
"PE": "GFP99",
|
||||
"PE-CF": "GFP98",
|
||||
"PP": "GFP97",
|
||||
"PP-CF": "GFP96",
|
||||
"PP-GF": "GFP95",
|
||||
"EVA": "GFR99",
|
||||
"PHA": "GFR98",
|
||||
"BVOH": "GFS97"
|
||||
}
|
@ -6,13 +6,24 @@
|
||||
<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">v1.2.4</span></h1>
|
||||
<h1>FilaMan<span class="version"></span></h1>
|
||||
<h4>Filament Management Tool</h4>
|
||||
</div>
|
||||
</div>
|
||||
@ -130,15 +141,6 @@
|
||||
</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>
|
||||
|
||||
|
62
html/rfid.js
@ -11,7 +11,7 @@ let reconnectTimer = null;
|
||||
// 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 +29,7 @@ function startHeartbeat() {
|
||||
updateConnectionStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// Sende Heartbeat
|
||||
socket.send(JSON.stringify({ type: 'heartbeat' }));
|
||||
@ -83,7 +83,7 @@ function initWebSocket() {
|
||||
isConnected = false;
|
||||
updateConnectionStatus();
|
||||
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
||||
|
||||
|
||||
// Bei Fehler Verbindung schließen und neu aufbauen
|
||||
if (socket) {
|
||||
socket.close();
|
||||
@ -109,7 +109,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
|
||||
@ -150,6 +150,13 @@ function initWebSocket() {
|
||||
ramStatus.textContent = `${data.freeHeap}k`;
|
||||
}
|
||||
}
|
||||
else if (data.type === 'setSpoolmanSettings') {
|
||||
if (data.payload == 'success') {
|
||||
showNotification(`Spoolman Settings set successfully`, true);
|
||||
} else {
|
||||
showNotification(`Error setting Spoolman Settings`, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
isConnected = false;
|
||||
@ -201,7 +208,7 @@ 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;
|
||||
@ -285,6 +292,14 @@ function displayAmsData(amsData) {
|
||||
<img src="spool_in.png" alt="Spool In" style="width: 48px; height: 48px; transform: rotate(180deg) scaleX(-1);">
|
||||
</button>`;
|
||||
|
||||
const spoolmanButtonHtml = `
|
||||
<button class="spool-button" onclick="handleSpoolmanSettings('${tray.tray_info_idx}', '${tray.setting_id}', '${tray.cali_idx}', '${tray.nozzle_temp_min}', '${tray.nozzle_temp_max}')"
|
||||
style="position: absolute; bottom: 0px; right: 0px;
|
||||
background: none; border: none; padding: 0;
|
||||
cursor: pointer; display: none;">
|
||||
<img src="set_spoolman.png" alt="Spool In" style="width: 38px; height: 38px;">
|
||||
</button>`;
|
||||
|
||||
if (!hasAnyContent) {
|
||||
return `
|
||||
<div class="tray">
|
||||
@ -348,6 +363,7 @@ function displayAmsData(amsData) {
|
||||
${trayDetails}
|
||||
${tempHTML}
|
||||
${(ams.ams_id === 255 && tray.tray_type !== '') ? outButtonHtml : ''}
|
||||
${(tray.setting_id != "" && tray.setting_id != "null") ? spoolmanButtonHtml : ''}
|
||||
</div>
|
||||
|
||||
</div>`;
|
||||
@ -373,6 +389,36 @@ function updateSpoolButtons(show) {
|
||||
});
|
||||
}
|
||||
|
||||
function handleSpoolmanSettings(tray_info_idx, setting_id, cali_idx, nozzle_temp_min, nozzle_temp_max) {
|
||||
// Hole das ausgewählte Filament
|
||||
const selectedText = document.getElementById("selected-filament").textContent;
|
||||
|
||||
// Finde die ausgewählte Spule in den Daten
|
||||
const selectedSpool = spoolsData.find(spool =>
|
||||
`${spool.id} | ${spool.filament.name} (${spool.filament.material})` === selectedText
|
||||
);
|
||||
|
||||
const payload = {
|
||||
type: 'setSpoolmanSettings',
|
||||
payload: {
|
||||
filament_id: selectedSpool.filament.id,
|
||||
tray_info_idx: tray_info_idx,
|
||||
setting_id: setting_id,
|
||||
cali_idx: cali_idx,
|
||||
temp_min: nozzle_temp_min,
|
||||
temp_max: nozzle_temp_max
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
socket.send(JSON.stringify(payload));
|
||||
showNotification(`Setting send to Spoolman`, true);
|
||||
} catch (error) {
|
||||
console.error("Error while sending settings to Spoolman:", error);
|
||||
showNotification("Error while sending!", false);
|
||||
}
|
||||
}
|
||||
|
||||
function handleSpoolOut() {
|
||||
// Erstelle Payload
|
||||
const payload = {
|
||||
@ -444,7 +490,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
|
||||
}
|
||||
};
|
||||
@ -594,8 +640,6 @@ function writeNfcTag() {
|
||||
|
||||
// Erstelle das NFC-Datenpaket mit korrekten Datentypen
|
||||
const nfcData = {
|
||||
version: "2.0",
|
||||
protocol: "openspool",
|
||||
color_hex: selectedSpool.filament.color_hex || "FFFFFF",
|
||||
type: selectedSpool.filament.material,
|
||||
min_temp: minTemp,
|
||||
@ -642,4 +686,4 @@ function showNotification(message, isSuccess) {
|
||||
notification.remove();
|
||||
}, 300);
|
||||
}, 3000);
|
||||
}
|
||||
}
|
160
html/rfid_bambu.html
Normal file
@ -0,0 +1,160 @@
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
BIN
html/set_spoolman.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
@ -6,13 +6,24 @@
|
||||
<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">v1.2.4</span></h1>
|
||||
<h1>FilaMan<span class="version"></span></h1>
|
||||
<h4>Filament Management Tool</h4>
|
||||
</div>
|
||||
</div>
|
||||
@ -41,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) {
|
||||
@ -63,12 +106,19 @@
|
||||
const ip = document.getElementById('bambuIp').value;
|
||||
const serial = document.getElementById('bambuSerial').value;
|
||||
const code = document.getElementById('bambuCode').value;
|
||||
const autoSend = document.getElementById('autoSend').checked;
|
||||
const autoSendTime = document.getElementById('autoSendTime').value;
|
||||
|
||||
fetch(`/api/bambu?bambu_ip=${encodeURIComponent(ip)}&bambu_serialnr=${encodeURIComponent(serial)}&bambu_accesscode=${encodeURIComponent(code)}`)
|
||||
fetch(`/api/bambu?bambu_ip=${encodeURIComponent(ip)}&bambu_serialnr=${encodeURIComponent(serial)}&bambu_accesscode=${encodeURIComponent(code)}&autoSend=${autoSend}&autoSendTime=${autoSendTime}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.healthy) {
|
||||
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.';
|
||||
}
|
||||
@ -77,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}}";
|
||||
@ -84,27 +143,59 @@
|
||||
|
||||
<div class="content">
|
||||
<h1>Spoolman API URL / Bambu Credentials</h1>
|
||||
<label for="spoolmanUrl">Set URL/IP to your Spoolman-Instanz:</label>
|
||||
<input type="text" id="spoolmanUrl" placeholder="http://ip-or-url-of-your-spoolman-instanz:port">
|
||||
<button onclick="checkSpoolmanInstance()">Save Spoolman URL</button>
|
||||
<p id="statusMessage"></p>
|
||||
|
||||
<h2>Bambu Lab Printer Credentials</h2>
|
||||
<div class="bambu-settings">
|
||||
<div class="input-group">
|
||||
<label for="bambuIp">Bambu Drucker IP-Adresse:</label>
|
||||
<input type="text" id="bambuIp" placeholder="192.168.1.xxx" value="{{bambuIp}}">
|
||||
<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">
|
||||
<h5 class="card-title">If you want to enable sending Spool to Spoolman Octoprint Plugin:</h5>
|
||||
<p>
|
||||
<input type="checkbox" id="spoolmanOctoEnabled" {{spoolmanOctoEnabled}} onchange="toggleOctoFields()"> Send to Octo-Plugin
|
||||
</p>
|
||||
<div id="octoFields" style="display: none;">
|
||||
<p>
|
||||
<input type="text" id="spoolmanOctoUrl" placeholder="http://ip-or-url-of-your-octoprint-instanz:port" value="{{spoolmanOctoUrl}}">
|
||||
<input type="text" id="spoolmanOctoToken" placeholder="Your Octoprint Token" value="{{spoolmanOctoToken}}">
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button onclick="checkSpoolmanInstance()">Save Spoolman URL</button>
|
||||
<p id="statusMessage"></p>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="bambuSerial">Drucker Seriennummer:</label>
|
||||
<input type="text" id="bambuSerial" placeholder="BBLXXXXXXXX" value="{{bambuSerial}}">
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<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>
|
||||
<input type="text" id="bambuIp" placeholder="192.168.1.xxx" value="{{bambuIp}}">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="bambuSerial">Drucker Seriennummer:</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}}">
|
||||
</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 for Spool in Sec:</label>
|
||||
</div>
|
||||
<div class="input-group" style="display: flex;">
|
||||
<input type="checkbox" id="autoSend" {{autoSendToBambu}} style="width: 190px; margin-right: 10px;">
|
||||
<input type="number" min="60" id="autoSendTime" placeholder="Time to wait" value="{{autoSendTime}}" style="width: 100px;">
|
||||
</div>
|
||||
|
||||
<button 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>
|
||||
<div class="input-group">
|
||||
<label for="bambuCode">Access Code:</label>
|
||||
<input type="text" id="bambuCode" placeholder="Access Code vom Drucker" value="{{bambuCode}}">
|
||||
</div>
|
||||
<button onclick="saveBambuCredentials()">Save Bambu Credentials</button>
|
||||
<p id="bambuStatusMessage"></p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
@ -86,10 +86,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 +97,15 @@ function populateVendorDropdown(data, selectedSmId = null) {
|
||||
? (weightInKg / 1000).toFixed(2) + " t"
|
||||
: weightInKg.toFixed(2) + " kg";
|
||||
|
||||
// Dropdown mit gefilterten Herstellern befüllen
|
||||
Object.entries(filteredVendors).forEach(([id, name]) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = id;
|
||||
option.textContent = name;
|
||||
vendorSelect.appendChild(option);
|
||||
});
|
||||
// Dropdown mit gefilterten Herstellern befüllen - alphabetisch sortiert
|
||||
Object.entries(filteredVendors)
|
||||
.sort(([, nameA], [, nameB]) => nameA.localeCompare(nameB)) // Sort vendors alphabetically by name
|
||||
.forEach(([id, name]) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = id;
|
||||
option.textContent = name;
|
||||
vendorSelect.appendChild(option);
|
||||
});
|
||||
|
||||
document.getElementById("totalSpools").textContent = totalSpools;
|
||||
document.getElementById("spoolsWithoutTag").textContent = spoolsWithoutTag;
|
||||
@ -145,6 +147,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 !== '""' &&
|
||||
@ -238,18 +247,6 @@ 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");
|
||||
};
|
||||
*/
|
||||
|
||||
// Event Listener
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initSpoolman();
|
||||
|
@ -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;
|
||||
}
|
||||
@ -279,9 +283,10 @@ a:hover {
|
||||
|
||||
/* Karten-Stil für optische Trennung */
|
||||
.card {
|
||||
background: #f9f9f9;
|
||||
background: var(--primary-color);
|
||||
width: 500px;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
margin: 20px auto;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
@ -760,17 +765,19 @@ a:hover {
|
||||
right: 20px;
|
||||
padding: 15px 25px;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
color: black;
|
||||
z-index: 1000;
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
.notification.success {
|
||||
background-color: #28a745;
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
.notification.error {
|
||||
background-color: #dc3545;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.notification.fade-out {
|
||||
@ -959,7 +966,6 @@ input[type="submit"]:disabled,
|
||||
|
||||
/* Bambu Settings Erweiterung */
|
||||
.bambu-settings {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
@ -1013,6 +1019,7 @@ input[type="submit"]:disabled,
|
||||
color: #000;
|
||||
vertical-align: middle;
|
||||
margin-left: 0.5rem;
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
@ -1051,9 +1058,10 @@ input[type="submit"]:disabled,
|
||||
}
|
||||
.update-form {
|
||||
background: var(--primary-color);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
|
||||
border: var(--glass-border);
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
margin: 0 auto;
|
||||
width: 400px;
|
||||
text-align: center;
|
||||
@ -1064,7 +1072,7 @@ input[type="submit"]:disabled,
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background: white;
|
||||
background-color: #4CAF50;
|
||||
}
|
||||
.update-form input[type="submit"] {
|
||||
background-color: #4CAF50;
|
||||
@ -1086,10 +1094,66 @@ input[type="submit"]:disabled,
|
||||
.warning {
|
||||
background-color: var(--primary-color);
|
||||
border: 1px solid #ffe0b2;
|
||||
color: white;
|
||||
padding: 15px;
|
||||
margin: 20px auto;
|
||||
border-radius: 4px;
|
||||
max-width: 600px;
|
||||
text-align: center;
|
||||
color: #e65100;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.update-options {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
.update-section {
|
||||
flex: 1;
|
||||
background: var(--background-green);
|
||||
padding: 1.5rem;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.update-section h2 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
}
|
||||
.update-section p {
|
||||
color: #666;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.progress-container {
|
||||
margin: 20px 0;
|
||||
background: #f0f0f0;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.progress-bar {
|
||||
width: 0;
|
||||
height: 20px;
|
||||
background: #4CAF50;
|
||||
transition: width 0.3s ease-in-out;
|
||||
text-align: center;
|
||||
line-height: 20px;
|
||||
color: white;
|
||||
}
|
||||
.status {
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
display: none;
|
||||
}
|
||||
.status.success {
|
||||
background: #e8f5e9;
|
||||
color: #2e7d32;
|
||||
}
|
||||
.status.error {
|
||||
background: #ffebee;
|
||||
color: #c62828;
|
||||
}
|
||||
.warning {
|
||||
background: #fff3e0;
|
||||
color: #e65100;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
}
|
@ -6,13 +6,24 @@
|
||||
<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">v1.2.4</span></h1>
|
||||
<h1>FilaMan<span class="version"></span></h1>
|
||||
<h4>Filament Management Tool</h4>
|
||||
</div>
|
||||
</div>
|
||||
@ -40,18 +51,34 @@
|
||||
<h1>Firmware Upgrade</h1>
|
||||
|
||||
<div class="warning">
|
||||
<strong>Warning:</strong> Please do not turn off or restart the device during the update.
|
||||
The device will restart automatically after the update.
|
||||
<strong>Warning:</strong> Do not power off the device during update.
|
||||
</div>
|
||||
|
||||
<div class="update-form">
|
||||
<form id="updateForm" enctype='multipart/form-data'>
|
||||
<input type='file' name='update' accept='.bin' required>
|
||||
<input type='submit' value='Start Firmware Update'>
|
||||
</form>
|
||||
<div class="update-options">
|
||||
<div class="update-section">
|
||||
<h2>Firmware Update</h2>
|
||||
<p>Upload a new firmware file (filaman_*.bin)</p>
|
||||
<div class="update-form">
|
||||
<form id="firmwareForm" enctype='multipart/form-data' data-type="firmware">
|
||||
<input type='file' name='update' accept='.bin' required>
|
||||
<input type='submit' value='Start Firmware Update'>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="update-section">
|
||||
<h2>Webpage Update</h2>
|
||||
<p>Upload a new webpage file (webpage_*.bin)</p>
|
||||
<div class="update-form">
|
||||
<form id="webpageForm" enctype='multipart/form-data' data-type="webpage">
|
||||
<input type='file' name='update' accept='.bin' required>
|
||||
<input type='submit' value='Start Webpage Update'>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="progress-container">
|
||||
<div class="progress-container" style="display: none;">
|
||||
<div class="progress-bar">0%</div>
|
||||
</div>
|
||||
<div class="status"></div>
|
||||
@ -64,91 +91,165 @@
|
||||
statusContainer.style.display = 'none';
|
||||
}
|
||||
|
||||
document.getElementById('updateForm').addEventListener('submit', async (e) => {
|
||||
const progress = document.querySelector('.progress-bar');
|
||||
const progressContainer = document.querySelector('.progress-container');
|
||||
const status = document.querySelector('.status');
|
||||
let updateInProgress = false;
|
||||
let lastReceivedProgress = 0;
|
||||
|
||||
// WebSocket Handling
|
||||
let ws = null;
|
||||
let wsReconnectTimer = null;
|
||||
|
||||
function connectWebSocket() {
|
||||
ws = new WebSocket('ws://' + window.location.host + '/ws');
|
||||
|
||||
ws.onmessage = function(event) {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.type === "updateProgress" && updateInProgress) {
|
||||
// Zeige Fortschrittsbalken
|
||||
progressContainer.style.display = 'block';
|
||||
|
||||
// Aktualisiere den Fortschritt nur wenn er größer ist
|
||||
const newProgress = parseInt(data.progress);
|
||||
if (!isNaN(newProgress) && newProgress >= lastReceivedProgress) {
|
||||
progress.style.width = newProgress + '%';
|
||||
progress.textContent = newProgress + '%';
|
||||
lastReceivedProgress = newProgress;
|
||||
}
|
||||
|
||||
// Zeige Status-Nachricht
|
||||
if (data.message || data.status) {
|
||||
status.textContent = data.message || getStatusMessage(data.status);
|
||||
status.className = 'status success';
|
||||
status.style.display = 'block';
|
||||
|
||||
// Starte Reload wenn Update erfolgreich
|
||||
if (data.status === 'success' || lastReceivedProgress >= 98) {
|
||||
clearTimeout(wsReconnectTimer);
|
||||
setTimeout(() => {
|
||||
window.location.reload(true);
|
||||
window.location.href = '/';
|
||||
}, 30000);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('WebSocket message error:', e);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = function() {
|
||||
if (updateInProgress) {
|
||||
// Wenn der Fortschritt hoch genug ist, gehen wir von einem erfolgreichen Update aus
|
||||
if (lastReceivedProgress >= 85) {
|
||||
status.textContent = "Update appears successful! Device is restarting... Page will reload in 30 seconds.";
|
||||
status.className = 'status success';
|
||||
status.style.display = 'block';
|
||||
clearTimeout(wsReconnectTimer);
|
||||
setTimeout(() => {
|
||||
window.location.reload(true);
|
||||
window.location.href = '/';
|
||||
}, 30000);
|
||||
} else {
|
||||
// Versuche Reconnect bei niedrigem Fortschritt
|
||||
wsReconnectTimer = setTimeout(connectWebSocket, 1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = function(err) {
|
||||
console.error('WebSocket error:', err);
|
||||
if (updateInProgress && lastReceivedProgress >= 85) {
|
||||
status.textContent = "Update appears successful! Device is restarting... Page will reload in 30 seconds.";
|
||||
status.className = 'status success';
|
||||
status.style.display = 'block';
|
||||
setTimeout(() => {
|
||||
window.location.href = '/';
|
||||
}, 30000);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Initial WebSocket connection
|
||||
connectWebSocket();
|
||||
|
||||
function getStatusMessage(status) {
|
||||
switch(status) {
|
||||
case 'starting': return 'Starting update...';
|
||||
case 'uploading': return 'Uploading...';
|
||||
case 'finalizing': return 'Finalizing update...';
|
||||
case 'restoring': return 'Restoring configurations...';
|
||||
case 'preparing': return 'Preparing for restart...';
|
||||
case 'success': return 'Update successful! Device is restarting... Page will reload in 30 seconds.';
|
||||
default: return 'Updating...';
|
||||
}
|
||||
}
|
||||
|
||||
function handleUpdate(e) {
|
||||
e.preventDefault();
|
||||
const form = e.target;
|
||||
const file = form.update.files[0];
|
||||
const updateType = form.dataset.type;
|
||||
|
||||
if (!file) {
|
||||
alert('Please select a firmware file.');
|
||||
alert('Please select a file.');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('update', file);
|
||||
|
||||
const progress = document.querySelector('.progress-bar');
|
||||
const progressContainer = document.querySelector('.progress-container');
|
||||
const status = document.querySelector('.status');
|
||||
|
||||
// Validate file name pattern
|
||||
if (updateType === 'firmware' && !file.name.startsWith('upgrade_filaman_firmware_')) {
|
||||
alert('Please select a valid firmware file (upgrade_filaman_firmware_*.bin)');
|
||||
return;
|
||||
}
|
||||
if (updateType === 'webpage' && !file.name.startsWith('upgrade_filaman_website_')) {
|
||||
alert('Please select a valid webpage file (upgrade_filaman_website_*.bin)');
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset UI
|
||||
updateInProgress = true;
|
||||
progressContainer.style.display = 'block';
|
||||
status.style.display = 'none';
|
||||
status.className = 'status';
|
||||
form.querySelector('input[type=submit]').disabled = true;
|
||||
progress.style.width = '0%';
|
||||
progress.textContent = '0%';
|
||||
|
||||
// Disable submit buttons
|
||||
document.querySelectorAll('form input[type=submit]').forEach(btn => btn.disabled = true);
|
||||
|
||||
// Send update
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '/update', true);
|
||||
|
||||
xhr.upload.onprogress = (e) => {
|
||||
if (e.lengthComputable) {
|
||||
const percentComplete = (e.loaded / e.total) * 100;
|
||||
progress.style.width = percentComplete + '%';
|
||||
progress.textContent = Math.round(percentComplete) + '%';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
xhr.onload = function() {
|
||||
try {
|
||||
let response = this.responseText;
|
||||
try {
|
||||
const jsonResponse = JSON.parse(response);
|
||||
response = jsonResponse.message;
|
||||
|
||||
if (jsonResponse.restart) {
|
||||
status.textContent = response + " Redirecting in 20 seconds...";
|
||||
let countdown = 20;
|
||||
const timer = setInterval(() => {
|
||||
countdown--;
|
||||
if (countdown <= 0) {
|
||||
clearInterval(timer);
|
||||
window.location.href = '/';
|
||||
} else {
|
||||
status.textContent = response + ` Redirecting in ${countdown} seconds...`;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
} catch (e) {
|
||||
if (!isNaN(response)) {
|
||||
const percent = parseInt(response);
|
||||
progress.style.width = percent + '%';
|
||||
progress.textContent = percent + '%';
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
status.textContent = response;
|
||||
status.classList.add(xhr.status === 200 ? 'success' : 'error');
|
||||
if (xhr.status !== 200 && !progress.textContent.startsWith('100')) {
|
||||
status.textContent = "Update failed: " + (xhr.responseText || "Unknown error");
|
||||
status.className = 'status error';
|
||||
status.style.display = 'block';
|
||||
|
||||
if (xhr.status !== 200) {
|
||||
form.querySelector('input[type=submit]').disabled = false;
|
||||
}
|
||||
} catch (error) {
|
||||
status.textContent = 'Error: ' + error.message;
|
||||
status.classList.add('error');
|
||||
updateInProgress = false;
|
||||
document.querySelectorAll('form input[type=submit]').forEach(btn => btn.disabled = false);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function() {
|
||||
if (!progress.textContent.startsWith('100')) {
|
||||
status.textContent = "Network error during update";
|
||||
status.className = 'status error';
|
||||
status.style.display = 'block';
|
||||
form.querySelector('input[type=submit]').disabled = false;
|
||||
updateInProgress = false;
|
||||
document.querySelectorAll('form input[type=submit]').forEach(btn => btn.disabled = false);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function() {
|
||||
status.textContent = 'Update failed: Network error';
|
||||
status.classList.add('error');
|
||||
status.style.display = 'block';
|
||||
form.querySelector('input[type=submit]').disabled = false;
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('update', file);
|
||||
xhr.send(formData);
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('firmwareForm').addEventListener('submit', handleUpdate);
|
||||
document.getElementById('webpageForm').addEventListener('submit', handleUpdate);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -6,13 +6,24 @@
|
||||
<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">v1.2.4</span></h1>
|
||||
<h1>FilaMan<span class="version"></span></h1>
|
||||
<h4>Filament Management Tool</h4>
|
||||
</div>
|
||||
</div>
|
||||
@ -44,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>
|
||||
@ -129,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>
|
||||
|
@ -6,13 +6,24 @@
|
||||
<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">v1.2.4</span></h1>
|
||||
<h1>FilaMan<span class="version"></span></h1>
|
||||
<h4>Filament Management Tool</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
BIN
img/7-enable.png
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
img/ESP32-SPI-Pins.png
Normal file
After Width: | Height: | Size: 143 KiB |
BIN
img/IMG_2589.jpeg
Normal file
After Width: | Height: | Size: 136 KiB |
BIN
img/IMG_2590.jpeg
Normal file
After Width: | Height: | Size: 143 KiB |
BIN
img/Schaltplan.png
Normal file
After Width: | Height: | Size: 283 KiB |
@ -9,8 +9,10 @@
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[common]
|
||||
version = "1.2.17"
|
||||
version = "1.5.1"
|
||||
to_old_version = "1.5.0"
|
||||
|
||||
##
|
||||
[env:esp32dev]
|
||||
platform = espressif32
|
||||
board = esp32dev
|
||||
@ -20,7 +22,10 @@ monitor_speed = 115200
|
||||
lib_deps =
|
||||
tzapu/WiFiManager @ ^2.0.17
|
||||
https://github.com/me-no-dev/ESPAsyncWebServer.git#master
|
||||
me-no-dev/AsyncTCP @ ^1.1.1
|
||||
#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
|
||||
@ -30,7 +35,8 @@ lib_deps =
|
||||
digitaldragon/SSLClient @ ^1.3.2
|
||||
|
||||
; Enable SPIFFS upload
|
||||
board_build.filesystem = spiffs
|
||||
#board_build.filesystem = spiffs
|
||||
board_build.filesystem = littlefs
|
||||
; Update partition settings
|
||||
board_build.partitions = partitions.csv
|
||||
board_upload.flash_size = 4MB
|
||||
@ -41,36 +47,29 @@ build_flags =
|
||||
-Os
|
||||
-ffunction-sections
|
||||
-fdata-sections
|
||||
-DNDEBUG
|
||||
#-DNDEBUG
|
||||
-mtext-section-literals
|
||||
'-D VERSION="${common.version}"'
|
||||
-DVERSION=\"${common.version}\"
|
||||
-DTOOLDVERSION=\"${common.to_old_version}\"
|
||||
-DASYNCWEBSERVER_REGEX
|
||||
-DCORE_DEBUG_LEVEL=1
|
||||
#-DCORE_DEBUG_LEVEL=3
|
||||
-DCONFIG_ARDUHAL_LOG_COLORS=1
|
||||
-DOTA_DEBUG=1
|
||||
-DARDUINO_RUNNING_CORE=1
|
||||
-DARDUINO_EVENT_RUNNING_CORE=1
|
||||
#-DOTA_DEBUG=1
|
||||
-DCONFIG_OPTIMIZATION_LEVEL_DEBUG=1
|
||||
-DCONFIG_ESP32_PANIC_PRINT_REBOOT
|
||||
-DCONFIG_ARDUINO_OTA_READSIZE=1024
|
||||
-DCONFIG_ASYNC_TCP_RUNNING_CORE=1
|
||||
-DCONFIG_ASYNC_TCP_USE_WDT=0
|
||||
-DCONFIG_LWIP_TCP_MSS=1460
|
||||
-DOTA_PARTITION_SUBTYPE=0x10
|
||||
-DPARTITION_TABLE_OFFSET=0x8000
|
||||
-DPARTITION_TABLE_SIZE=0x1000
|
||||
-DBOOT_APP_PARTITION_OTA_0=1
|
||||
-DCONFIG_LWIP_TCP_MSL=60000
|
||||
-DCONFIG_LWIP_TCP_RCV_BUF_DEFAULT=4096
|
||||
-DCONFIG_LWIP_MAX_ACTIVE_TCP=16
|
||||
|
||||
extra_scripts =
|
||||
scripts/extra_script.py
|
||||
pre:scripts/pre_build.py ; wird zuerst ausgeführt
|
||||
pre:scripts/pre_spiffs.py ; wird als zweites ausgeführt
|
||||
pre:scripts/combine_html.py ; wird als drittes ausgeführt
|
||||
scripts/gzip_files.py
|
||||
${env:buildfs.extra_scripts}
|
||||
|
||||
; Remove or comment out the targets line
|
||||
;targets = buildfs, build
|
||||
[env:buildfs]
|
||||
extra_scripts =
|
||||
pre:scripts/combine_html.py ; Combine header with HTML files
|
||||
scripts/gzip_files.py ; Compress files for SPIFFS
|
||||
|
||||
; Add a custom target to build both
|
||||
[platformio]
|
||||
default_envs = esp32dev
|
||||
|
||||
|
@ -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'))
|
||||
|
@ -1,7 +1,39 @@
|
||||
Import("env")
|
||||
|
||||
board_config = env.BoardConfig()
|
||||
|
||||
# Calculate SPIFFS size based on partition table
|
||||
SPIFFS_START = 0x310000 # From partitions.csv
|
||||
SPIFFS_SIZE = 0xE0000 # From partitions.csv
|
||||
SPIFFS_PAGE = 256
|
||||
SPIFFS_BLOCK = 4096
|
||||
|
||||
env.Replace(
|
||||
MKSPIFFSTOOL="mkspiffs",
|
||||
SPIFFSBLOCKSZ=SPIFFS_BLOCK,
|
||||
SPIFFSBLOCKSIZE=SPIFFS_BLOCK,
|
||||
SPIFFSSTART=SPIFFS_START,
|
||||
SPIFFSEND=SPIFFS_START + SPIFFS_SIZE,
|
||||
SPIFFSPAGESZ=SPIFFS_PAGE,
|
||||
SPIFFSSIZE=SPIFFS_SIZE
|
||||
)
|
||||
|
||||
# Wiederverwendung der replace_version Funktion
|
||||
exec(open("./scripts/pre_build.py").read())
|
||||
|
||||
# Bind to SPIFFS build
|
||||
env.AddPreAction("buildfs", replace_version)
|
||||
env.AddPreAction("buildfs", replace_version)
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from SCons.Script import DefaultEnvironment
|
||||
|
||||
env = DefaultEnvironment()
|
||||
|
||||
# Format SPIFFS partition before uploading new files
|
||||
spiffs_dir = os.path.join(env.subst("$BUILD_DIR"), "spiffs")
|
||||
if os.path.exists(spiffs_dir):
|
||||
shutil.rmtree(spiffs_dir)
|
||||
os.makedirs(spiffs_dir)
|
||||
|
||||
print("SPIFFS partition formatted.")
|
@ -64,29 +64,10 @@ def get_changes_from_git():
|
||||
|
||||
return changes
|
||||
|
||||
def push_changes(version):
|
||||
"""Push changes to upstream"""
|
||||
try:
|
||||
# Stage the CHANGELOG.md
|
||||
subprocess.run(['git', 'add', 'CHANGELOG.md'], check=True)
|
||||
|
||||
# Commit the changelog
|
||||
commit_msg = f"docs: update changelog for version {version}"
|
||||
subprocess.run(['git', 'commit', '-m', commit_msg], check=True)
|
||||
|
||||
# Push to origin (local)
|
||||
subprocess.run(['git', 'push', 'origin'], check=True)
|
||||
print("Successfully pushed to origin")
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error during git operations: {e}")
|
||||
return False
|
||||
return True
|
||||
|
||||
def update_changelog():
|
||||
print("Starting changelog update...") # Add this line
|
||||
print("Starting changelog update...")
|
||||
version = get_version()
|
||||
print(f"Current version: {version}") # Add this line
|
||||
print(f"Current version: {version}")
|
||||
today = datetime.now().strftime('%Y-%m-%d')
|
||||
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
@ -111,7 +92,7 @@ def update_changelog():
|
||||
if not os.path.exists(changelog_path):
|
||||
with open(changelog_path, 'w') as f:
|
||||
f.write(f"# Changelog\n\n{changelog_entry}")
|
||||
push_changes(version)
|
||||
print(f"Created new changelog file with version {version}")
|
||||
else:
|
||||
with open(changelog_path, 'r') as f:
|
||||
content = f.read()
|
||||
@ -120,9 +101,30 @@ def update_changelog():
|
||||
updated_content = content.replace("# Changelog\n", f"# Changelog\n\n{changelog_entry}")
|
||||
with open(changelog_path, 'w') as f:
|
||||
f.write(updated_content)
|
||||
push_changes(version)
|
||||
print(f"Added new version {version} to changelog")
|
||||
else:
|
||||
print(f"Version {version} already exists in changelog")
|
||||
# Version existiert bereits, aktualisiere die bestehenden Einträge
|
||||
version_pattern = f"## \\[{version}\\] - \\d{{4}}-\\d{{2}}-\\d{{2}}"
|
||||
next_version_pattern = "## \\[.*?\\] - \\d{4}-\\d{2}-\\d{2}"
|
||||
|
||||
# Finde den Start der aktuellen Version
|
||||
version_match = re.search(version_pattern, content)
|
||||
if version_match:
|
||||
version_start = version_match.start()
|
||||
# Suche nach der nächsten Version
|
||||
next_version_match = re.search(next_version_pattern, content[version_start + 1:])
|
||||
|
||||
if next_version_match:
|
||||
# Ersetze den Inhalt zwischen aktueller und nächster Version
|
||||
next_version_pos = version_start + 1 + next_version_match.start()
|
||||
updated_content = content[:version_start] + changelog_entry + content[next_version_pos:]
|
||||
else:
|
||||
# Wenn keine nächste Version existiert, ersetze bis zum Ende
|
||||
updated_content = content[:version_start] + changelog_entry + "\n"
|
||||
|
||||
with open(changelog_path, 'w') as f:
|
||||
f.write(updated_content)
|
||||
print(f"Updated entries for version {version}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
update_changelog()
|
286
src/api.cpp
@ -3,43 +3,23 @@
|
||||
#include <ArduinoJson.h>
|
||||
#include "commonFS.h"
|
||||
|
||||
bool spoolman_connected = false;
|
||||
volatile spoolmanApiStateType spoolmanApiState = API_INIT;
|
||||
//bool spoolman_connected = false;
|
||||
String spoolmanUrl = "";
|
||||
bool octoEnabled = false;
|
||||
String octoUrl = "";
|
||||
String octoToken = "";
|
||||
|
||||
struct SendToApiParams {
|
||||
String httpType;
|
||||
String spoolsUrl;
|
||||
String updatePayload;
|
||||
String octoToken;
|
||||
};
|
||||
|
||||
/*
|
||||
// Spoolman Data
|
||||
{
|
||||
"version":"1.0",
|
||||
"protocol":"openspool",
|
||||
"color_hex":"AF7933",
|
||||
"type":"ABS",
|
||||
"min_temp":175,
|
||||
"max_temp":275,
|
||||
"brand":"Overture"
|
||||
}
|
||||
|
||||
// FilaMan Data
|
||||
{
|
||||
"version":"1.0",
|
||||
"protocol":"openspool",
|
||||
"color_hex":"AF7933",
|
||||
"type":"ABS",
|
||||
"min_temp":175,
|
||||
"max_temp":275,
|
||||
"brand":"Overture",
|
||||
"sm_id":
|
||||
}
|
||||
*/
|
||||
|
||||
JsonDocument fetchSpoolsForWebsite() {
|
||||
JsonDocument fetchSingleSpoolInfo(int spoolId) {
|
||||
HTTPClient http;
|
||||
String spoolsUrl = spoolmanUrl + apiUrl + "/spool";
|
||||
String spoolsUrl = spoolmanUrl + apiUrl + "/spool/" + spoolId;
|
||||
|
||||
Serial.print("Rufe Spool-Daten von: ");
|
||||
Serial.println(spoolsUrl);
|
||||
@ -56,84 +36,45 @@ JsonDocument fetchSpoolsForWebsite() {
|
||||
Serial.print("Fehler beim Parsen der JSON-Antwort: ");
|
||||
Serial.println(error.c_str());
|
||||
} else {
|
||||
JsonArray spools = doc.as<JsonArray>();
|
||||
JsonArray filteredSpools = filteredDoc.to<JsonArray>();
|
||||
String filamentType = doc["filament"]["material"].as<String>();
|
||||
String filamentBrand = doc["filament"]["vendor"]["name"].as<String>();
|
||||
|
||||
for (JsonObject spool : spools) {
|
||||
JsonObject filteredSpool = filteredSpools.createNestedObject();
|
||||
filteredSpool["extra"]["nfc_id"] = spool["extra"]["nfc_id"];
|
||||
int nozzle_temp_min = 0;
|
||||
int nozzle_temp_max = 0;
|
||||
if (doc["filament"]["extra"]["nozzle_temperature"].is<String>()) {
|
||||
String tempString = doc["filament"]["extra"]["nozzle_temperature"].as<String>();
|
||||
tempString.replace("[", "");
|
||||
tempString.replace("]", "");
|
||||
int commaIndex = tempString.indexOf(',');
|
||||
|
||||
if (commaIndex != -1) {
|
||||
nozzle_temp_min = tempString.substring(0, commaIndex).toInt();
|
||||
nozzle_temp_max = tempString.substring(commaIndex + 1).toInt();
|
||||
}
|
||||
}
|
||||
|
||||
JsonObject filament = filteredSpool.createNestedObject("filament");
|
||||
filament["sm_id"] = spool["id"];
|
||||
filament["id"] = spool["filament"]["id"];
|
||||
filament["name"] = spool["filament"]["name"];
|
||||
filament["material"] = spool["filament"]["material"];
|
||||
filament["color_hex"] = spool["filament"]["color_hex"];
|
||||
filament["nozzle_temperature"] = spool["filament"]["extra"]["nozzle_temperature"]; // [190,230]
|
||||
filament["price_meter"] = spool["filament"]["extra"]["price_meter"];
|
||||
filament["price_gramm"] = spool["filament"]["extra"]["price_gramm"];
|
||||
String filamentColor = doc["filament"]["color_hex"].as<String>();
|
||||
filamentColor.toUpperCase();
|
||||
|
||||
JsonObject vendor = filament.createNestedObject("vendor");
|
||||
vendor["id"] = spool["filament"]["vendor"]["id"];
|
||||
vendor["name"] = spool["filament"]["vendor"]["name"];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Serial.print("Fehler beim Abrufen der Spool-Daten. HTTP-Code: ");
|
||||
Serial.println(httpCode);
|
||||
}
|
||||
String tray_info_idx = doc["filament"]["extra"]["bambu_idx"].as<String>();
|
||||
tray_info_idx.replace("\"", "");
|
||||
|
||||
String cali_idx = doc["filament"]["extra"]["bambu_cali_id"].as<String>(); // "\"153\""
|
||||
cali_idx.replace("\"", "");
|
||||
|
||||
String bambu_setting_id = doc["filament"]["extra"]["bambu_setting_id"].as<String>(); // "\"PFUSf40e9953b40d3d\""
|
||||
bambu_setting_id.replace("\"", "");
|
||||
|
||||
http.end();
|
||||
return filteredDoc;
|
||||
}
|
||||
doc.clear();
|
||||
|
||||
JsonDocument fetchAllSpoolsInfo() {
|
||||
HTTPClient http;
|
||||
String spoolsUrl = spoolmanUrl + apiUrl + "/spool";
|
||||
|
||||
Serial.print("Rufe Spool-Daten von: ");
|
||||
Serial.println(spoolsUrl);
|
||||
|
||||
http.begin(spoolsUrl);
|
||||
int httpCode = http.GET();
|
||||
|
||||
JsonDocument filteredDoc;
|
||||
if (httpCode == HTTP_CODE_OK) {
|
||||
String payload = http.getString();
|
||||
JsonDocument doc;
|
||||
DeserializationError error = deserializeJson(doc, payload);
|
||||
if (error) {
|
||||
Serial.print("Fehler beim Parsen der JSON-Antwort: ");
|
||||
Serial.println(error.c_str());
|
||||
} else {
|
||||
JsonArray spools = doc.as<JsonArray>();
|
||||
JsonArray filteredSpools = filteredDoc.to<JsonArray>();
|
||||
|
||||
for (JsonObject spool : spools) {
|
||||
JsonObject filteredSpool = filteredSpools.createNestedObject();
|
||||
filteredSpool["price"] = spool["price"];
|
||||
filteredSpool["remaining_weight"] = spool["remaining_weight"];
|
||||
filteredSpool["used_weight"] = spool["used_weight"];
|
||||
filteredSpool["extra"]["nfc_id"] = spool["extra"]["nfc_id"];
|
||||
|
||||
JsonObject filament = filteredSpool.createNestedObject("filament");
|
||||
filament["id"] = spool["filament"]["id"];
|
||||
filament["name"] = spool["filament"]["name"];
|
||||
filament["material"] = spool["filament"]["material"];
|
||||
filament["density"] = spool["filament"]["density"];
|
||||
filament["diameter"] = spool["filament"]["diameter"];
|
||||
filament["spool_weight"] = spool["filament"]["spool_weight"];
|
||||
filament["color_hex"] = spool["filament"]["color_hex"];
|
||||
|
||||
JsonObject vendor = filament.createNestedObject("vendor");
|
||||
vendor["id"] = spool["filament"]["vendor"]["id"];
|
||||
vendor["name"] = spool["filament"]["vendor"]["name"];
|
||||
|
||||
JsonObject extra = filament.createNestedObject("extra");
|
||||
extra["nozzle_temperature"] = spool["filament"]["extra"]["nozzle_temperature"];
|
||||
extra["price_gramm"] = spool["filament"]["extra"]["price_gramm"];
|
||||
extra["price_meter"] = spool["filament"]["extra"]["price_meter"];
|
||||
}
|
||||
filteredDoc["color"] = filamentColor;
|
||||
filteredDoc["type"] = filamentType;
|
||||
filteredDoc["nozzle_temp_min"] = nozzle_temp_min;
|
||||
filteredDoc["nozzle_temp_max"] = nozzle_temp_max;
|
||||
filteredDoc["brand"] = filamentBrand;
|
||||
filteredDoc["tray_info_idx"] = tray_info_idx;
|
||||
filteredDoc["cali_idx"] = cali_idx;
|
||||
filteredDoc["bambu_setting_id"] = bambu_setting_id;
|
||||
}
|
||||
} else {
|
||||
Serial.print("Fehler beim Abrufen der Spool-Daten. HTTP-Code: ");
|
||||
@ -145,34 +86,42 @@ JsonDocument fetchAllSpoolsInfo() {
|
||||
}
|
||||
|
||||
void sendToApi(void *parameter) {
|
||||
spoolmanApiState = API_TRANSMITTING;
|
||||
SendToApiParams* params = (SendToApiParams*)parameter;
|
||||
|
||||
// Extrahiere die Werte
|
||||
String httpType = params->httpType;
|
||||
String spoolsUrl = params->spoolsUrl;
|
||||
String updatePayload = params->updatePayload;
|
||||
|
||||
String octoToken = params->octoToken;
|
||||
|
||||
HTTPClient http;
|
||||
http.setReuse(false);
|
||||
|
||||
http.begin(spoolsUrl);
|
||||
http.addHeader("Content-Type", "application/json");
|
||||
if (octoEnabled && octoToken != "") http.addHeader("X-Api-Key", octoToken);
|
||||
|
||||
int httpCode = http.PUT(updatePayload);
|
||||
int httpCode;
|
||||
if (httpType == "PATCH") httpCode = http.PATCH(updatePayload);
|
||||
else if (httpType == "POST") httpCode = http.POST(updatePayload);
|
||||
else httpCode = http.PUT(updatePayload);
|
||||
|
||||
if (httpCode == HTTP_CODE_OK) {
|
||||
Serial.println("Gewicht der Spule erfolgreich aktualisiert");
|
||||
Serial.println("Spoolman erfolgreich aktualisiert");
|
||||
} else {
|
||||
Serial.println("Fehler beim Aktualisieren des Gewichts der Spule");
|
||||
Serial.println("Fehler beim Senden an Spoolman!");
|
||||
oledShowMessage("Spoolman update failed");
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
http.end();
|
||||
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||
|
||||
// Speicher freigeben
|
||||
delete params;
|
||||
vTaskDelete(NULL);
|
||||
spoolmanApiState = API_IDLE;
|
||||
}
|
||||
|
||||
bool updateSpoolTagId(String uidString, const char* payload) {
|
||||
@ -186,7 +135,7 @@ bool updateSpoolTagId(String uidString, const char* payload) {
|
||||
}
|
||||
|
||||
// Überprüfe, ob die erforderlichen Felder vorhanden sind
|
||||
if (!doc.containsKey("sm_id") || doc["sm_id"] == "") {
|
||||
if (!doc["sm_id"].is<String>() || doc["sm_id"].as<String>() == "") {
|
||||
Serial.println("Keine Spoolman-ID gefunden.");
|
||||
return false;
|
||||
}
|
||||
@ -217,12 +166,14 @@ bool updateSpoolTagId(String uidString, const char* payload) {
|
||||
BaseType_t result = xTaskCreate(
|
||||
sendToApi, // Task-Funktion
|
||||
"SendToApiTask", // Task-Name
|
||||
4096, // Stackgröße in Bytes
|
||||
6144, // Stackgröße in Bytes
|
||||
(void*)params, // Parameter
|
||||
0, // Priorität
|
||||
NULL // Task-Handle (nicht benötigt)
|
||||
);
|
||||
|
||||
updateDoc.clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -253,15 +204,102 @@ uint8_t updateSpoolWeight(String spoolId, uint16_t weight) {
|
||||
BaseType_t result = xTaskCreate(
|
||||
sendToApi, // Task-Funktion
|
||||
"SendToApiTask", // Task-Name
|
||||
4096, // Stackgröße in Bytes
|
||||
6144, // Stackgröße in Bytes
|
||||
(void*)params, // Parameter
|
||||
0, // Priorität
|
||||
NULL // Task-Handle (nicht benötigt)
|
||||
);
|
||||
|
||||
updateDoc.clear();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool updateSpoolOcto(int spoolId) {
|
||||
String spoolsUrl = octoUrl + "/plugin/Spoolman/selectSpool";
|
||||
Serial.print("Update Spule in Octoprint mit URL: ");
|
||||
Serial.println(spoolsUrl);
|
||||
|
||||
JsonDocument updateDoc;
|
||||
updateDoc["spool_id"] = spoolId;
|
||||
updateDoc["tool"] = "tool0";
|
||||
|
||||
String updatePayload;
|
||||
serializeJson(updateDoc, updatePayload);
|
||||
Serial.print("Update Payload: ");
|
||||
Serial.println(updatePayload);
|
||||
|
||||
SendToApiParams* params = new SendToApiParams();
|
||||
if (params == nullptr) {
|
||||
Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren.");
|
||||
return false;
|
||||
}
|
||||
params->httpType = "POST";
|
||||
params->spoolsUrl = spoolsUrl;
|
||||
params->updatePayload = updatePayload;
|
||||
params->octoToken = octoToken;
|
||||
|
||||
// Erstelle die Task
|
||||
BaseType_t result = xTaskCreate(
|
||||
sendToApi, // Task-Funktion
|
||||
"SendToApiTask", // Task-Name
|
||||
6144, // Stackgröße in Bytes
|
||||
(void*)params, // Parameter
|
||||
0, // Priorität
|
||||
NULL // Task-Handle (nicht benötigt)
|
||||
);
|
||||
|
||||
updateDoc.clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool updateSpoolBambuData(String payload) {
|
||||
JsonDocument doc;
|
||||
DeserializationError error = deserializeJson(doc, payload);
|
||||
if (error) {
|
||||
Serial.print("Fehler beim JSON-Parsing: ");
|
||||
Serial.println(error.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
String spoolsUrl = spoolmanUrl + apiUrl + "/filament/" + doc["filament_id"].as<String>();
|
||||
Serial.print("Update Spule mit URL: ");
|
||||
Serial.println(spoolsUrl);
|
||||
|
||||
JsonDocument updateDoc;
|
||||
updateDoc["extra"]["bambu_setting_id"] = "\"" + doc["setting_id"].as<String>() + "\"";
|
||||
updateDoc["extra"]["bambu_cali_id"] = "\"" + doc["cali_idx"].as<String>() + "\"";
|
||||
updateDoc["extra"]["bambu_idx"] = "\"" + doc["tray_info_idx"].as<String>() + "\"";
|
||||
updateDoc["extra"]["nozzle_temperature"] = "[" + doc["temp_min"].as<String>() + "," + doc["temp_max"].as<String>() + "]";
|
||||
|
||||
String updatePayload;
|
||||
serializeJson(updateDoc, updatePayload);
|
||||
Serial.print("Update Payload: ");
|
||||
Serial.println(updatePayload);
|
||||
|
||||
SendToApiParams* params = new SendToApiParams();
|
||||
if (params == nullptr) {
|
||||
Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren.");
|
||||
return false;
|
||||
}
|
||||
params->httpType = "PATCH";
|
||||
params->spoolsUrl = spoolsUrl;
|
||||
params->updatePayload = updatePayload;
|
||||
|
||||
// Erstelle die Task
|
||||
BaseType_t result = xTaskCreate(
|
||||
sendToApi, // Task-Funktion
|
||||
"SendToApiTask", // Task-Name
|
||||
6144, // Stackgröße in Bytes
|
||||
(void*)params, // Parameter
|
||||
0, // Priorität
|
||||
NULL // Task-Handle (nicht benötigt)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// #### Spoolman init
|
||||
bool checkSpoolmanExtraFields() {
|
||||
HTTPClient http;
|
||||
@ -368,7 +406,7 @@ bool checkSpoolmanExtraFields() {
|
||||
for (uint8_t s = 0; s < extraLength; s++) {
|
||||
bool found = false;
|
||||
for (JsonObject field : doc.as<JsonArray>()) {
|
||||
if (field.containsKey("key") && field["key"] == extraFields[s]) {
|
||||
if (field["key"].is<String>() && field["key"] == extraFields[s]) {
|
||||
Serial.println("Feld gefunden: " + extraFields[s]);
|
||||
found = true;
|
||||
break;
|
||||
@ -403,12 +441,13 @@ bool checkSpoolmanExtraFields() {
|
||||
}
|
||||
}
|
||||
}
|
||||
http.end();
|
||||
}
|
||||
|
||||
Serial.println("-------- ENDE Prüfe Felder --------");
|
||||
Serial.println();
|
||||
|
||||
http.end();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -430,7 +469,7 @@ bool checkSpoolmanInstance(const String& url) {
|
||||
String payload = http.getString();
|
||||
JsonDocument doc;
|
||||
DeserializationError error = deserializeJson(doc, payload);
|
||||
if (!error && doc.containsKey("status")) {
|
||||
if (!error && doc["status"].is<String>()) {
|
||||
const char* status = doc["status"];
|
||||
http.end();
|
||||
|
||||
@ -443,7 +482,8 @@ bool checkSpoolmanInstance(const String& url) {
|
||||
return false;
|
||||
}
|
||||
|
||||
spoolman_connected = true;
|
||||
spoolmanApiState = API_IDLE;
|
||||
oledShowTopRow();
|
||||
return strcmp(status, "healthy") == 0;
|
||||
}
|
||||
}
|
||||
@ -452,24 +492,38 @@ bool checkSpoolmanInstance(const String& url) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool saveSpoolmanUrl(const String& url) {
|
||||
bool saveSpoolmanUrl(const String& url, bool octoOn, const String& octoWh, const String& octoTk) {
|
||||
if (!checkSpoolmanInstance(url)) return false;
|
||||
|
||||
JsonDocument doc;
|
||||
doc["url"] = url;
|
||||
Serial.print("Speichere URL in Datei: ");
|
||||
Serial.println(url);
|
||||
doc["octoEnabled"] = octoOn;
|
||||
doc["octoUrl"] = octoWh;
|
||||
doc["octoToken"] = octoTk;
|
||||
Serial.print("Speichere Spoolman Data in Datei: ");
|
||||
Serial.println(doc.as<String>());
|
||||
if (!saveJsonValue("/spoolman_url.json", doc)) {
|
||||
Serial.println("Fehler beim Speichern der Spoolman-URL.");
|
||||
return false;
|
||||
}
|
||||
spoolmanUrl = url;
|
||||
octoEnabled = octoOn;
|
||||
octoUrl = octoWh;
|
||||
octoToken = octoTk;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
String loadSpoolmanUrl() {
|
||||
JsonDocument doc;
|
||||
if (loadJsonValue("/spoolman_url.json", doc) && doc.containsKey("url")) {
|
||||
if (loadJsonValue("/spoolman_url.json", doc) && doc["url"].is<String>()) {
|
||||
octoEnabled = (doc["octoEnabled"].is<bool>()) ? doc["octoEnabled"].as<bool>() : false;
|
||||
if (octoEnabled && doc["octoToken"].is<String>() && doc["octoUrl"].is<String>())
|
||||
{
|
||||
octoUrl = doc["octoUrl"].as<String>();
|
||||
octoToken = doc["octoToken"].as<String>();
|
||||
}
|
||||
|
||||
return doc["url"].as<String>();
|
||||
}
|
||||
Serial.println("Keine gültige Spoolman-URL gefunden.");
|
||||
|
17
src/api.h
@ -6,19 +6,28 @@
|
||||
#include "website.h"
|
||||
#include "display.h"
|
||||
#include <ArduinoJson.h>
|
||||
typedef enum {
|
||||
API_INIT,
|
||||
API_IDLE,
|
||||
API_TRANSMITTING
|
||||
} spoolmanApiStateType;
|
||||
|
||||
extern volatile spoolmanApiStateType spoolmanApiState;
|
||||
extern bool spoolman_connected;
|
||||
extern String spoolmanUrl;
|
||||
extern bool octoEnabled;
|
||||
extern String octoUrl;
|
||||
extern String octoToken;
|
||||
|
||||
bool checkSpoolmanInstance(const String& url);
|
||||
bool saveSpoolmanUrl(const String& url);
|
||||
bool saveSpoolmanUrl(const String& url, bool octoOn, const String& octoWh, const String& octoTk);
|
||||
String loadSpoolmanUrl(); // Neue Funktion zum Laden der URL
|
||||
bool checkSpoolmanExtraFields(); // Neue Funktion zum Überprüfen der Extrafelder
|
||||
JsonDocument fetchSpoolsForWebsite(); // API-Funktion für die Webseite
|
||||
JsonDocument fetchAllSpoolsInfo();
|
||||
void sendAmsData(AsyncWebSocketClient *client); // Neue Funktion zum Senden von AMS-Daten
|
||||
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
|
||||
bool initSpoolman(); // Neue Funktion zum Initialisieren von Spoolman
|
||||
bool updateSpoolBambuData(String payload); // Neue Funktion zum Aktualisieren der Bambu-Daten
|
||||
bool updateSpoolOcto(int spoolId); // Neue Funktion zum Aktualisieren der Octo-Daten
|
||||
|
||||
#endif
|
||||
|
497
src/bambu.cpp
@ -17,20 +17,54 @@ PubSubClient client(sslClient);
|
||||
|
||||
TaskHandle_t BambuMqttTask;
|
||||
|
||||
String report_topic = "";
|
||||
String 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;
|
||||
|
||||
// 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
|
||||
AMSData ams_data[MAX_AMS]; // Definition des Arrays;
|
||||
|
||||
bool saveBambuCredentials(const String& ip, const String& serialnr, const String& accesscode) {
|
||||
bool removeBambuCredentials() {
|
||||
if (BambuMqttTask) {
|
||||
vTaskDelete(BambuMqttTask);
|
||||
}
|
||||
|
||||
if (!removeJsonValue("/bambu_credentials.json")) {
|
||||
Serial.println("Fehler beim Löschen der Bambu-Credentials.");
|
||||
return false;
|
||||
}
|
||||
// Löschen der globalen Variablen
|
||||
g_bambu_ip = "";
|
||||
g_bambu_accesscode = "";
|
||||
g_bambu_serialnr = "";
|
||||
bambu_ip = nullptr;
|
||||
bambu_accesscode = nullptr;
|
||||
bambu_serialnr = nullptr;
|
||||
autoSendToBambu = false;
|
||||
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);
|
||||
}
|
||||
@ -39,6 +73,8 @@ bool saveBambuCredentials(const String& ip, const String& serialnr, const String
|
||||
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.");
|
||||
@ -49,6 +85,8 @@ bool saveBambuCredentials(const String& ip, const String& serialnr, const String
|
||||
bambu_ip = ip.c_str();
|
||||
bambu_accesscode = accesscode.c_str();
|
||||
bambu_serialnr = serialnr.c_str();
|
||||
autoSendToBambu = autoSend;
|
||||
autoSetBambuAmsCounter = autoSendTime.toInt();
|
||||
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
if (!setupMqtt()) return false;
|
||||
@ -58,22 +96,29 @@ bool saveBambuCredentials(const String& ip, const String& serialnr, const String
|
||||
|
||||
bool loadBambuCredentials() {
|
||||
JsonDocument doc;
|
||||
if (loadJsonValue("/bambu_credentials.json", doc) && doc.containsKey("bambu_ip")) {
|
||||
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>();
|
||||
|
||||
g_bambu_ip = ip;
|
||||
g_bambu_accesscode = code;
|
||||
g_bambu_serialnr = serial;
|
||||
|
||||
if (doc["autoSendToBambu"].is<bool>()) autoSendToBambu = doc["autoSendToBambu"].as<bool>();
|
||||
if (doc["autoSendTime"].is<int>()) autoSetBambuAmsCounter = doc["autoSendTime"].as<int>();
|
||||
|
||||
ip.trim();
|
||||
code.trim();
|
||||
serial.trim();
|
||||
|
||||
// Dynamische Speicherallokation für die globalen Pointer
|
||||
bambu_ip = strdup(ip.c_str());
|
||||
bambu_accesscode = strdup(code.c_str());
|
||||
bambu_serialnr = strdup(serial.c_str());
|
||||
bambu_ip = g_bambu_ip.c_str();
|
||||
bambu_accesscode = g_bambu_accesscode.c_str();
|
||||
bambu_serialnr = g_bambu_serialnr.c_str();
|
||||
|
||||
report_topic = "device/" + String(bambu_serialnr) + "/report";
|
||||
topic = "device/" + String(bambu_serialnr);
|
||||
//request_topic = "device/" + String(bambu_serialnr) + "/request";
|
||||
return true;
|
||||
}
|
||||
@ -81,19 +126,49 @@ bool loadBambuCredentials() {
|
||||
return false;
|
||||
}
|
||||
|
||||
String findFilamentIdx(String brand, String type) {
|
||||
struct FilamentResult {
|
||||
String key;
|
||||
String type;
|
||||
};
|
||||
|
||||
FilamentResult findFilamentIdx(String brand, String type) {
|
||||
// JSON-Dokument für die Filament-Daten erstellen
|
||||
JsonDocument doc;
|
||||
|
||||
// Laden der own_filaments.json
|
||||
String ownFilament = "";
|
||||
if (!loadJsonValue("/own_filaments.json", doc))
|
||||
{
|
||||
Serial.println("Fehler beim Laden der eigenen Filament-Daten");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Durchsuche direkt nach dem Type als Schlüssel
|
||||
if (doc[type].is<String>()) {
|
||||
ownFilament = doc[type].as<String>();
|
||||
}
|
||||
doc.clear();
|
||||
}
|
||||
doc.clear();
|
||||
|
||||
// Laden der bambu_filaments.json
|
||||
if (!loadJsonValue("/bambu_filaments.json", doc)) {
|
||||
if (!loadJsonValue("/bambu_filaments.json", doc))
|
||||
{
|
||||
Serial.println("Fehler beim Laden der Filament-Daten");
|
||||
return "GFL99"; // Fallback auf Generic PLA
|
||||
return {"GFL99", "PLA"}; // Fallback auf Generic PLA
|
||||
}
|
||||
|
||||
// Wenn eigener Typ
|
||||
if (ownFilament != "")
|
||||
{
|
||||
if (doc[ownFilament].is<String>())
|
||||
{
|
||||
return {ownFilament, doc[ownFilament].as<String>()};
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Erst versuchen wir die exakte Brand + Type Kombination zu finden
|
||||
String searchKey;
|
||||
|
||||
// 1. Suche nach Brand + Type Kombination
|
||||
if (brand == "Bambu" || brand == "Bambulab") {
|
||||
searchKey = "Bambu " + type;
|
||||
} else if (brand == "PolyLite") {
|
||||
@ -109,26 +184,49 @@ String findFilamentIdx(String brand, String type) {
|
||||
// Durchsuche alle Einträge nach der Brand + Type Kombination
|
||||
for (JsonPair kv : doc.as<JsonObject>()) {
|
||||
if (kv.value().as<String>() == searchKey) {
|
||||
return kv.key().c_str();
|
||||
return {kv.key().c_str(), kv.value().as<String>()};
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Wenn nicht gefunden, suche nach Generic + Type
|
||||
searchKey = "Generic " + type;
|
||||
// 2. Wenn nicht gefunden, zerlege den type String in Wörter und suche nach jedem Wort
|
||||
// Sammle alle vorhandenen Filamenttypen aus der JSON
|
||||
std::vector<String> knownTypes;
|
||||
for (JsonPair kv : doc.as<JsonObject>()) {
|
||||
if (kv.value().as<String>() == searchKey) {
|
||||
return kv.key().c_str();
|
||||
String value = kv.value().as<String>();
|
||||
// Extrahiere den Typ ohne Markennamen
|
||||
if (value.indexOf(" ") != -1) {
|
||||
value = value.substring(value.indexOf(" ") + 1);
|
||||
}
|
||||
if (!value.isEmpty()) {
|
||||
knownTypes.push_back(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Zerlege den Input-Type in Wörter
|
||||
String typeStr = type;
|
||||
typeStr.trim();
|
||||
|
||||
// Durchsuche für jedes bekannte Filament, ob es im Input vorkommt
|
||||
for (const String& knownType : knownTypes) {
|
||||
if (typeStr.indexOf(knownType) != -1) {
|
||||
// Suche nach diesem Typ in der Original-JSON
|
||||
for (JsonPair kv : doc.as<JsonObject>()) {
|
||||
String value = kv.value().as<String>();
|
||||
if (value.indexOf(knownType) != -1) {
|
||||
return {kv.key().c_str(), knownType};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Wenn immer noch nichts gefunden, gebe GFL99 zurück (Generic PLA)
|
||||
return "GFL99";
|
||||
return {"GFL99", "PLA"};
|
||||
}
|
||||
|
||||
bool sendMqttMessage(String payload) {
|
||||
bool sendMqttMessage(const String& payload) {
|
||||
Serial.println("Sending MQTT message");
|
||||
Serial.println(payload);
|
||||
if (client.publish(report_topic.c_str(), payload.c_str()))
|
||||
if (client.publish((String(topic) + "/request").c_str(), payload.c_str()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -156,15 +254,22 @@ bool setBambuSpool(String payload) {
|
||||
int minTemp = doc["nozzle_temp_min"];
|
||||
int maxTemp = doc["nozzle_temp_max"];
|
||||
String type = doc["type"].as<String>();
|
||||
(type == "PLA+") ? type = "PLA" : type;
|
||||
String brand = doc["brand"].as<String>();
|
||||
String tray_info_idx = (doc["tray_info_idx"].as<String>() != "-1") ? doc["tray_info_idx"].as<String>() : "";
|
||||
if (tray_info_idx == "") tray_info_idx = (brand != "" && type != "") ? findFilamentIdx(brand, type) : "";
|
||||
if (tray_info_idx == "") {
|
||||
if (brand != "" && type != "") {
|
||||
FilamentResult result = findFilamentIdx(brand, type);
|
||||
tray_info_idx = result.key;
|
||||
type = result.type; // Aktualisiere den type mit dem gefundenen Basistyp
|
||||
}
|
||||
}
|
||||
String setting_id = doc["bambu_setting_id"].as<String>();
|
||||
String cali_idx = doc["cali_idx"].as<String>();
|
||||
|
||||
doc.clear();
|
||||
|
||||
doc["print"]["sequence_id"] = 0;
|
||||
doc["print"]["sequence_id"] = "0";
|
||||
doc["print"]["command"] = "ams_filament_setting";
|
||||
doc["print"]["ams_id"] = amsId < 200 ? amsId : 255;
|
||||
doc["print"]["tray_id"] = trayId < 200 ? trayId : 254;
|
||||
@ -172,10 +277,10 @@ bool setBambuSpool(String payload) {
|
||||
doc["print"]["nozzle_temp_min"] = minTemp;
|
||||
doc["print"]["nozzle_temp_max"] = maxTemp;
|
||||
doc["print"]["tray_type"] = type;
|
||||
doc["print"]["cali_idx"] = (cali_idx != "") ? cali_idx : "";
|
||||
//doc["print"]["cali_idx"] = (cali_idx != "") ? cali_idx : "";
|
||||
doc["print"]["tray_info_idx"] = tray_info_idx;
|
||||
doc["print"]["setting_id"] = setting_id;
|
||||
|
||||
|
||||
// Serialize the JSON
|
||||
String output;
|
||||
serializeJson(doc, output);
|
||||
@ -194,13 +299,13 @@ bool setBambuSpool(String payload) {
|
||||
|
||||
if (cali_idx != "") {
|
||||
yield();
|
||||
doc["print"]["sequence_id"] = 0;
|
||||
doc["print"]["sequence_id"] = "0";
|
||||
doc["print"]["command"] = "extrusion_cali_sel";
|
||||
doc["print"]["filament_id"] = tray_info_idx;
|
||||
doc["print"]["nozzle_diameter"] = "0.4";
|
||||
doc["print"]["cali_idx"] = cali_idx.toInt();
|
||||
doc["print"]["tray_id"] = trayId < 200 ? trayId : 254;
|
||||
doc["print"]["ams_id"] = amsId < 200 ? amsId : 255;
|
||||
//doc["print"]["ams_id"] = amsId < 200 ? amsId : 255;
|
||||
|
||||
// Serialize the JSON
|
||||
String output;
|
||||
@ -218,44 +323,120 @@ bool setBambuSpool(String payload) {
|
||||
doc.clear();
|
||||
yield();
|
||||
}
|
||||
/*
|
||||
if (setting_id != "") {
|
||||
yield();
|
||||
doc["print"]["sequence_id"] = 0;
|
||||
doc["print"]["command"] = "ams_filament_setting";
|
||||
doc["print"]["nozzle_temp_min"] = minTemp;
|
||||
doc["print"]["nozzle_temp_max"] = maxTemp;
|
||||
doc["print"]["setting_id"] = setting_id;
|
||||
doc["print"]["tray_color"] = color.length() == 8 ? color : color+"FF";
|
||||
doc["print"]["ams_id"] = amsId < 200 ? amsId : 255;
|
||||
doc["print"]["tray_id"] = trayId < 200 ? trayId : 254;
|
||||
doc["print"]["tray_info_idx"] = tray_info_idx;
|
||||
doc["print"]["tray_type"] = type;
|
||||
|
||||
// Serialize the JSON
|
||||
String output;
|
||||
serializeJson(doc, output);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sendMqttMessage(output)) {
|
||||
Serial.println("Filament Setting successfully set");
|
||||
void autoSetSpool(int spoolId, uint8_t trayId) {
|
||||
// wenn neue spule erkannt und autoSetToBambu > 0
|
||||
JsonDocument spoolInfo = fetchSingleSpoolInfo(spoolId);
|
||||
|
||||
if (!spoolInfo.isNull())
|
||||
{
|
||||
// AMS und TRAY id ergänzen
|
||||
spoolInfo["amsId"] = 0;
|
||||
spoolInfo["trayId"] = trayId;
|
||||
|
||||
Serial.println("Auto set spool");
|
||||
Serial.println(spoolInfo.as<String>());
|
||||
|
||||
setBambuSpool(spoolInfo.as<String>());
|
||||
|
||||
oledShowMessage("Spool set");
|
||||
}
|
||||
|
||||
// id wieder zurücksetzen damit abgeschlossen
|
||||
autoSetToBambuSpoolId = 0;
|
||||
}
|
||||
|
||||
void updateAmsWsData(JsonDocument& doc, JsonArray& amsArray, int& ams_count, JsonObject& vtTray) {
|
||||
// Fortfahren mit der bestehenden Verarbeitung, da Änderungen gefunden wurden
|
||||
ams_count = amsArray.size();
|
||||
|
||||
for (int i = 0; i < ams_count && i < 16; i++) {
|
||||
JsonObject amsObj = amsArray[i];
|
||||
JsonArray trayArray = amsObj["tray"].as<JsonArray>();
|
||||
|
||||
ams_data[i].ams_id = i; // Setze die AMS-ID
|
||||
for (int j = 0; j < trayArray.size() && j < 4; j++) { // Annahme: Maximal 4 Trays pro AMS
|
||||
JsonObject trayObj = trayArray[j];
|
||||
|
||||
ams_data[i].trays[j].id = trayObj["id"].as<uint8_t>();
|
||||
ams_data[i].trays[j].tray_info_idx = trayObj["tray_info_idx"].as<String>();
|
||||
ams_data[i].trays[j].tray_type = trayObj["tray_type"].as<String>();
|
||||
ams_data[i].trays[j].tray_sub_brands = trayObj["tray_sub_brands"].as<String>();
|
||||
ams_data[i].trays[j].tray_color = trayObj["tray_color"].as<String>();
|
||||
ams_data[i].trays[j].nozzle_temp_min = trayObj["nozzle_temp_min"].as<int>();
|
||||
ams_data[i].trays[j].nozzle_temp_max = trayObj["nozzle_temp_max"].as<int>();
|
||||
if (trayObj["tray_type"].as<String>() == "") ams_data[i].trays[j].setting_id = "";
|
||||
ams_data[i].trays[j].cali_idx = trayObj["cali_idx"].as<String>();
|
||||
}
|
||||
}
|
||||
|
||||
// Setze ams_count auf die Anzahl der normalen AMS
|
||||
ams_count = amsArray.size();
|
||||
|
||||
// Wenn externe Spule vorhanden, füge sie hinzu
|
||||
if (doc["print"]["vt_tray"].is<JsonObject>()) {
|
||||
//JsonObject vtTray = doc["print"]["vt_tray"];
|
||||
int extIdx = ams_count; // Index für externe Spule
|
||||
ams_data[extIdx].ams_id = 255; // Spezielle ID für externe Spule
|
||||
ams_data[extIdx].trays[0].id = 254; // Spezielle ID für externes Tray
|
||||
ams_data[extIdx].trays[0].tray_info_idx = vtTray["tray_info_idx"].as<String>();
|
||||
ams_data[extIdx].trays[0].tray_type = vtTray["tray_type"].as<String>();
|
||||
ams_data[extIdx].trays[0].tray_sub_brands = vtTray["tray_sub_brands"].as<String>();
|
||||
ams_data[extIdx].trays[0].tray_color = vtTray["tray_color"].as<String>();
|
||||
ams_data[extIdx].trays[0].nozzle_temp_min = vtTray["nozzle_temp_min"].as<int>();
|
||||
ams_data[extIdx].trays[0].nozzle_temp_max = vtTray["nozzle_temp_max"].as<int>();
|
||||
|
||||
if (doc["print"]["vt_tray"]["tray_type"].as<String>() != "")
|
||||
{
|
||||
//ams_data[extIdx].trays[0].setting_id = vtTray["setting_id"].as<String>();
|
||||
ams_data[extIdx].trays[0].cali_idx = vtTray["cali_idx"].as<String>();
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("Failed to set Filament setting");
|
||||
return false;
|
||||
ams_data[extIdx].trays[0].setting_id = "";
|
||||
ams_data[extIdx].trays[0].cali_idx = "";
|
||||
}
|
||||
|
||||
doc.clear();
|
||||
yield();
|
||||
ams_count++; // Erhöhe ams_count für die externe Spule
|
||||
}
|
||||
*/
|
||||
|
||||
return true;
|
||||
// Erstelle JSON für WebSocket-Clients
|
||||
JsonDocument wsDoc;
|
||||
JsonArray wsArray = wsDoc.to<JsonArray>();
|
||||
|
||||
for (int i = 0; i < ams_count; i++) {
|
||||
JsonObject amsObj = wsArray.add<JsonObject>();
|
||||
amsObj["ams_id"] = ams_data[i].ams_id;
|
||||
|
||||
JsonArray trays = amsObj["tray"].to<JsonArray>();
|
||||
int maxTrays = (ams_data[i].ams_id == 255) ? 1 : 4;
|
||||
|
||||
for (int j = 0; j < maxTrays; j++) {
|
||||
JsonObject trayObj = trays.add<JsonObject>();
|
||||
trayObj["id"] = ams_data[i].trays[j].id;
|
||||
trayObj["tray_info_idx"] = ams_data[i].trays[j].tray_info_idx;
|
||||
trayObj["tray_type"] = ams_data[i].trays[j].tray_type;
|
||||
trayObj["tray_sub_brands"] = ams_data[i].trays[j].tray_sub_brands;
|
||||
trayObj["tray_color"] = ams_data[i].trays[j].tray_color;
|
||||
trayObj["nozzle_temp_min"] = ams_data[i].trays[j].nozzle_temp_min;
|
||||
trayObj["nozzle_temp_max"] = ams_data[i].trays[j].nozzle_temp_max;
|
||||
trayObj["setting_id"] = ams_data[i].trays[j].setting_id;
|
||||
trayObj["cali_idx"] = ams_data[i].trays[j].cali_idx;
|
||||
}
|
||||
}
|
||||
|
||||
serializeJson(wsArray, amsJsonData);
|
||||
wsDoc.clear();
|
||||
Serial.println("AMS data updated");
|
||||
sendAmsData(nullptr);
|
||||
}
|
||||
|
||||
// init
|
||||
void mqtt_callback(char* topic, byte* payload, unsigned int length) {
|
||||
String message;
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
message += (char)payload[i];
|
||||
}
|
||||
@ -263,21 +444,25 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
|
||||
// JSON-Dokument parsen
|
||||
JsonDocument doc;
|
||||
DeserializationError error = deserializeJson(doc, message);
|
||||
if (error) {
|
||||
message = "";
|
||||
if (error)
|
||||
{
|
||||
Serial.print("Fehler beim Parsen des JSON: ");
|
||||
Serial.println(error.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Prüfen, ob "print->upgrade_state" und "print.ams.ams" existieren
|
||||
if (doc["print"].containsKey("upgrade_state")) {
|
||||
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"].containsKey("ams") || !doc["print"]["ams"].containsKey("ams")) {
|
||||
if (!doc["print"]["ams"].is<JsonObject>() || !doc["print"]["ams"]["ams"].is<JsonArray>())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
JsonArray amsArray = doc["print"]["ams"]["ams"].as<JsonArray>();
|
||||
|
||||
|
||||
// Prüfe ob sich die AMS-Daten geändert haben
|
||||
bool hasChanges = false;
|
||||
|
||||
@ -304,154 +489,81 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
|
||||
// Vergleiche die Trays
|
||||
for (int j = 0; j < trayArray.size() && j < 4 && !hasChanges; j++) {
|
||||
JsonObject trayObj = trayArray[j];
|
||||
|
||||
if (trayObj["tray_type"].as<String>() == "") ams_data[storedIndex].trays[j].setting_id = "";
|
||||
if (trayObj["setting_id"].isNull()) trayObj["setting_id"] = "";
|
||||
if (trayObj["tray_info_idx"].as<String>() != ams_data[storedIndex].trays[j].tray_info_idx ||
|
||||
trayObj["tray_type"].as<String>() != ams_data[storedIndex].trays[j].tray_type ||
|
||||
trayObj["tray_color"].as<String>() != ams_data[storedIndex].trays[j].tray_color ||
|
||||
(trayObj["setting_id"].as<String>() != "" && trayObj["setting_id"].as<String>() != ams_data[storedIndex].trays[j].setting_id) ||
|
||||
trayObj["cali_idx"].as<String>() != ams_data[storedIndex].trays[j].cali_idx) {
|
||||
hasChanges = true;
|
||||
|
||||
if (autoSendToBambu && autoSetToBambuSpoolId > 0 && hasChanges)
|
||||
{
|
||||
autoSetSpool(autoSetToBambuSpoolId, ams_data[storedIndex].trays[j].id);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prüfe die externe Spule
|
||||
if (!hasChanges && doc["print"].containsKey("vt_tray")) {
|
||||
JsonObject vtTray = doc["print"]["vt_tray"];
|
||||
bool foundExternal = false;
|
||||
|
||||
JsonObject vtTray = doc["print"]["vt_tray"];
|
||||
if (doc["print"]["vt_tray"].is<JsonObject>()) {
|
||||
for (int i = 0; i < ams_count; i++) {
|
||||
if (ams_data[i].ams_id == 255) {
|
||||
foundExternal = true;
|
||||
if (vtTray["tray_type"].as<String>() == "") ams_data[i].trays[0].setting_id = "";
|
||||
if (vtTray["setting_id"].isNull()) vtTray["setting_id"] = "";
|
||||
if (vtTray["tray_info_idx"].as<String>() != ams_data[i].trays[0].tray_info_idx ||
|
||||
vtTray["tray_type"].as<String>() != ams_data[i].trays[0].tray_type ||
|
||||
vtTray["tray_color"].as<String>() != ams_data[i].trays[0].tray_color ||
|
||||
vtTray["cali_idx"].as<String>() != ams_data[i].trays[0].cali_idx) {
|
||||
(vtTray["setting_id"].as<String>() != "" && vtTray["setting_id"].as<String>() != ams_data[i].trays[0].setting_id) ||
|
||||
(vtTray["tray_type"].as<String>() != "" && vtTray["cali_idx"].as<String>() != ams_data[i].trays[0].cali_idx)) {
|
||||
hasChanges = true;
|
||||
|
||||
if (autoSendToBambu && autoSetToBambuSpoolId > 0 && hasChanges)
|
||||
{
|
||||
autoSetSpool(autoSetToBambuSpoolId, 254);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundExternal) hasChanges = true;
|
||||
}
|
||||
|
||||
if (!hasChanges) return;
|
||||
|
||||
// Fortfahren mit der bestehenden Verarbeitung, da Änderungen gefunden wurden
|
||||
ams_count = amsArray.size();
|
||||
|
||||
for (int i = 0; i < ams_count && i < 16; i++) {
|
||||
JsonObject amsObj = amsArray[i];
|
||||
JsonArray trayArray = amsObj["tray"].as<JsonArray>();
|
||||
|
||||
ams_data[i].ams_id = i; // Setze die AMS-ID
|
||||
for (int j = 0; j < trayArray.size() && j < 4; j++) { // Annahme: Maximal 4 Trays pro AMS
|
||||
JsonObject trayObj = trayArray[j];
|
||||
|
||||
ams_data[i].trays[j].id = trayObj["id"].as<uint8_t>();
|
||||
ams_data[i].trays[j].tray_info_idx = trayObj["tray_info_idx"].as<String>();
|
||||
ams_data[i].trays[j].tray_type = trayObj["tray_type"].as<String>();
|
||||
ams_data[i].trays[j].tray_sub_brands = trayObj["tray_sub_brands"].as<String>();
|
||||
ams_data[i].trays[j].tray_color = trayObj["tray_color"].as<String>();
|
||||
ams_data[i].trays[j].nozzle_temp_min = trayObj["nozzle_temp_min"].as<int>();
|
||||
ams_data[i].trays[j].nozzle_temp_max = trayObj["nozzle_temp_max"].as<int>();
|
||||
ams_data[i].trays[j].setting_id = trayObj["setting_id"].as<String>();
|
||||
ams_data[i].trays[j].cali_idx = trayObj["cali_idx"].as<String>();
|
||||
}
|
||||
}
|
||||
|
||||
// Setze ams_count auf die Anzahl der normalen AMS
|
||||
ams_count = amsArray.size();
|
||||
|
||||
// Wenn externe Spule vorhanden, füge sie hinzu
|
||||
if (doc["print"].containsKey("vt_tray")) {
|
||||
JsonObject vtTray = doc["print"]["vt_tray"];
|
||||
int extIdx = ams_count; // Index für externe Spule
|
||||
ams_data[extIdx].ams_id = 255; // Spezielle ID für externe Spule
|
||||
ams_data[extIdx].trays[0].id = 254; // Spezielle ID für externes Tray
|
||||
ams_data[extIdx].trays[0].tray_info_idx = vtTray["tray_info_idx"].as<String>();
|
||||
ams_data[extIdx].trays[0].tray_type = vtTray["tray_type"].as<String>();
|
||||
ams_data[extIdx].trays[0].tray_sub_brands = vtTray["tray_sub_brands"].as<String>();
|
||||
ams_data[extIdx].trays[0].tray_color = vtTray["tray_color"].as<String>();
|
||||
ams_data[extIdx].trays[0].nozzle_temp_min = vtTray["nozzle_temp_min"].as<int>();
|
||||
ams_data[extIdx].trays[0].nozzle_temp_max = vtTray["nozzle_temp_max"].as<int>();
|
||||
ams_data[extIdx].trays[0].setting_id = vtTray["setting_id"].as<String>();
|
||||
ams_data[extIdx].trays[0].cali_idx = vtTray["cali_idx"].as<String>();
|
||||
ams_count++; // Erhöhe ams_count für die externe Spule
|
||||
}
|
||||
|
||||
// Sende die aktualisierten AMS-Daten
|
||||
//sendAmsData(nullptr);
|
||||
|
||||
// Erstelle JSON für WebSocket-Clients
|
||||
JsonDocument wsDoc;
|
||||
JsonArray wsArray = wsDoc.to<JsonArray>();
|
||||
|
||||
for (int i = 0; i < ams_count; i++) {
|
||||
JsonObject amsObj = wsArray.createNestedObject();
|
||||
amsObj["ams_id"] = ams_data[i].ams_id;
|
||||
|
||||
JsonArray trays = amsObj.createNestedArray("tray");
|
||||
int maxTrays = (ams_data[i].ams_id == 255) ? 1 : 4;
|
||||
|
||||
for (int j = 0; j < maxTrays; j++) {
|
||||
JsonObject trayObj = trays.createNestedObject();
|
||||
trayObj["id"] = ams_data[i].trays[j].id;
|
||||
trayObj["tray_info_idx"] = ams_data[i].trays[j].tray_info_idx;
|
||||
trayObj["tray_type"] = ams_data[i].trays[j].tray_type;
|
||||
trayObj["tray_sub_brands"] = ams_data[i].trays[j].tray_sub_brands;
|
||||
trayObj["tray_color"] = ams_data[i].trays[j].tray_color;
|
||||
trayObj["nozzle_temp_min"] = ams_data[i].trays[j].nozzle_temp_min;
|
||||
trayObj["nozzle_temp_max"] = ams_data[i].trays[j].nozzle_temp_max;
|
||||
trayObj["setting_id"] = ams_data[i].trays[j].setting_id;
|
||||
trayObj["cali_idx"] = ams_data[i].trays[j].cali_idx;
|
||||
}
|
||||
}
|
||||
|
||||
serializeJson(wsArray, amsJsonData);
|
||||
sendAmsData(nullptr);
|
||||
updateAmsWsData(doc, amsArray, ams_count, vtTray);
|
||||
}
|
||||
|
||||
// Neue Bedingung für ams_filament_setting
|
||||
else if (doc["print"]["command"] == "ams_filament_setting") {
|
||||
if (doc["print"]["command"] == "ams_filament_setting") {
|
||||
int amsId = doc["print"]["ams_id"].as<int>();
|
||||
int trayId = doc["print"]["tray_id"].as<int>();
|
||||
String settingId = doc["print"]["setting_id"].as<String>();
|
||||
|
||||
String settingId = (doc["print"]["setting_id"].is<String>()) ? doc["print"]["setting_id"].as<String>() : "";
|
||||
|
||||
// Finde das entsprechende AMS und Tray
|
||||
for (int i = 0; i < ams_count; i++) {
|
||||
if (ams_data[i].ams_id == amsId) {
|
||||
// Update setting_id im entsprechenden Tray
|
||||
ams_data[i].trays[trayId].setting_id = settingId;
|
||||
|
||||
// Erstelle neues JSON für WebSocket-Clients
|
||||
JsonDocument wsDoc;
|
||||
JsonArray wsArray = wsDoc.to<JsonArray>();
|
||||
|
||||
for (int j = 0; j < ams_count; j++) {
|
||||
JsonObject amsObj = wsArray.createNestedObject();
|
||||
amsObj["ams_id"] = ams_data[j].ams_id;
|
||||
|
||||
JsonArray trays = amsObj.createNestedArray("tray");
|
||||
int maxTrays = (ams_data[j].ams_id == 255) ? 1 : 4;
|
||||
|
||||
for (int k = 0; k < maxTrays; k++) {
|
||||
JsonObject trayObj = trays.createNestedObject();
|
||||
trayObj["id"] = ams_data[j].trays[k].id;
|
||||
trayObj["tray_info_idx"] = ams_data[j].trays[k].tray_info_idx;
|
||||
trayObj["tray_type"] = ams_data[j].trays[k].tray_type;
|
||||
trayObj["tray_sub_brands"] = ams_data[j].trays[k].tray_sub_brands;
|
||||
trayObj["tray_color"] = ams_data[j].trays[k].tray_color;
|
||||
trayObj["nozzle_temp_min"] = ams_data[j].trays[k].nozzle_temp_min;
|
||||
trayObj["nozzle_temp_max"] = ams_data[j].trays[k].nozzle_temp_max;
|
||||
trayObj["setting_id"] = ams_data[j].trays[k].setting_id;
|
||||
trayObj["cali_idx"] = ams_data[j].trays[k].cali_idx;
|
||||
if (trayId == 254)
|
||||
{
|
||||
// Suche AMS mit ID 255 (externe Spule)
|
||||
for (int j = 0; j < ams_count; j++) {
|
||||
if (ams_data[j].ams_id == 255) {
|
||||
ams_data[j].trays[0].setting_id = settingId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Aktualisiere das globale amsJsonData
|
||||
amsJsonData = "";
|
||||
serializeJson(wsArray, amsJsonData);
|
||||
|
||||
else
|
||||
{
|
||||
ams_data[i].trays[trayId].setting_id = settingId;
|
||||
}
|
||||
|
||||
// Sende an WebSocket Clients
|
||||
Serial.println("Filament setting updated");
|
||||
sendAmsData(nullptr);
|
||||
break;
|
||||
}
|
||||
@ -461,16 +573,18 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
|
||||
|
||||
void reconnect() {
|
||||
// Loop until we're reconnected
|
||||
uint8_t retries = 0;
|
||||
while (!client.connected()) {
|
||||
Serial.print("Attempting MQTT connection...");
|
||||
Serial.println("Attempting MQTT re/connection...");
|
||||
bambu_connected = false;
|
||||
oledShowTopRow();
|
||||
|
||||
// Attempt to connect
|
||||
if (client.connect(bambu_serialnr, bambu_username, bambu_accesscode)) {
|
||||
Serial.println("... re-connected");
|
||||
// ... and resubscribe
|
||||
client.subscribe(report_topic.c_str());
|
||||
String clientId = String(bambu_serialnr) + "_" + String(random(0, 100));
|
||||
if (client.connect(clientId.c_str(), bambu_username, bambu_accesscode)) {
|
||||
Serial.println("MQTT re/connected");
|
||||
|
||||
client.subscribe((String(topic) + "/report").c_str());
|
||||
bambu_connected = true;
|
||||
oledShowTopRow();
|
||||
} else {
|
||||
@ -479,14 +593,23 @@ void reconnect() {
|
||||
Serial.println(" try again in 5 seconds");
|
||||
bambu_connected = false;
|
||||
oledShowTopRow();
|
||||
// Wait 5 seconds before retrying
|
||||
|
||||
yield();
|
||||
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
||||
if (retries > 5) {
|
||||
Serial.println("Disable Bambu MQTT Task after 5 retries");
|
||||
//vTaskSuspend(BambuMqttTask);
|
||||
vTaskDelete(BambuMqttTask);
|
||||
break;
|
||||
}
|
||||
|
||||
retries++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mqtt_loop(void * parameter) {
|
||||
Serial.println("Bambu MQTT Task gestartet");
|
||||
for(;;) {
|
||||
if (pauseBambuMqttTask) {
|
||||
vTaskDelay(10000);
|
||||
@ -500,6 +623,7 @@ void mqtt_loop(void * parameter) {
|
||||
}
|
||||
client.loop();
|
||||
yield();
|
||||
esp_task_wdt_reset();
|
||||
vTaskDelay(100);
|
||||
}
|
||||
}
|
||||
@ -507,30 +631,27 @@ void mqtt_loop(void * parameter) {
|
||||
bool setupMqtt() {
|
||||
// Wenn Bambu Daten vorhanden
|
||||
bool success = loadBambuCredentials();
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
|
||||
if (!success) {
|
||||
Serial.println("Failed to load Bambu credentials");
|
||||
oledShowMessage("Bambu Credentials Missing");
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
bambuDisabled = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (success && bambu_ip != "" && bambu_accesscode != "" && bambu_serialnr != "")
|
||||
{
|
||||
bambuDisabled = false;
|
||||
sslClient.setCACert(root_ca);
|
||||
sslClient.setInsecure();
|
||||
client.setServer(bambu_ip, 8883);
|
||||
|
||||
// Verbinden mit dem MQTT-Server
|
||||
bool connected = true;
|
||||
if (client.connect(bambu_serialnr, bambu_username, bambu_accesscode))
|
||||
String clientId = String(bambu_serialnr) + "_" + String(random(0, 100));
|
||||
if (client.connect(clientId.c_str(), bambu_username, bambu_accesscode))
|
||||
{
|
||||
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((String(topic) + "/report").c_str());
|
||||
Serial.println("MQTT-Client initialisiert");
|
||||
|
||||
oledShowMessage("Bambu Connected");
|
||||
@ -540,7 +661,7 @@ bool setupMqtt() {
|
||||
xTaskCreatePinnedToCore(
|
||||
mqtt_loop, /* Function to implement the task */
|
||||
"BambuMqtt", /* Name of the task */
|
||||
10000, /* Stack size in words */
|
||||
8192, /* Stack size in words */
|
||||
NULL, /* Task input parameter */
|
||||
mqttTaskPrio, /* Priority of the task */
|
||||
&BambuMqttTask, /* Task handle. */
|
||||
@ -559,10 +680,7 @@ bool setupMqtt() {
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("Fehler: Keine MQTT-Daten vorhanden");
|
||||
oledShowMessage("Bambu Credentials Missing");
|
||||
oledShowTopRow();
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
bambuDisabled = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -571,6 +689,7 @@ bool setupMqtt() {
|
||||
void bambu_restart() {
|
||||
if (BambuMqttTask) {
|
||||
vTaskDelete(BambuMqttTask);
|
||||
delay(10);
|
||||
}
|
||||
setupMqtt();
|
||||
}
|
@ -28,12 +28,17 @@ extern bool bambu_connected;
|
||||
|
||||
extern int ams_count;
|
||||
extern AMSData ams_data[MAX_AMS];
|
||||
extern bool autoSendToBambu;
|
||||
extern int autoSetToBambuSpoolId;
|
||||
extern bool bambuDisabled;
|
||||
|
||||
bool removeBambuCredentials();
|
||||
bool loadBambuCredentials();
|
||||
bool saveBambuCredentials(const String& bambu_ip, const String& bambu_serialnr, const String& bambu_accesscode);
|
||||
bool saveBambuCredentials(const String& bambu_ip, const String& bambu_serialnr, const String& bambu_accesscode, const bool autoSend, const String& autoSendTime);
|
||||
bool setupMqtt();
|
||||
void mqtt_loop(void * parameter);
|
||||
bool setBambuSpool(String payload);
|
||||
void bambu_restart();
|
||||
|
||||
extern TaskHandle_t BambuMqttTask;
|
||||
#endif
|
||||
|
@ -1,7 +1,22 @@
|
||||
#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 = SPIFFS.open(filename, "w");
|
||||
File file = LittleFS.open(filename, "w");
|
||||
if (!file) {
|
||||
Serial.print("Fehler beim Öffnen der Datei zum Schreiben: ");
|
||||
Serial.println(filename);
|
||||
@ -19,7 +34,7 @@ bool saveJsonValue(const char* filename, const JsonDocument& doc) {
|
||||
}
|
||||
|
||||
bool loadJsonValue(const char* filename, JsonDocument& doc) {
|
||||
File file = SPIFFS.open(filename, "r");
|
||||
File file = LittleFS.open(filename, "r");
|
||||
if (!file) {
|
||||
Serial.print("Fehler beim Öffnen der Datei zum Lesen: ");
|
||||
Serial.println(filename);
|
||||
@ -35,23 +50,12 @@ bool loadJsonValue(const char* filename, JsonDocument& doc) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool initializeSPIFFS() {
|
||||
// Erster Versuch
|
||||
if (SPIFFS.begin(true)) {
|
||||
Serial.println("SPIFFS mounted successfully.");
|
||||
return true;
|
||||
void initializeFileSystem() {
|
||||
if (!LittleFS.begin(true)) {
|
||||
Serial.println("LittleFS Mount Failed");
|
||||
return;
|
||||
}
|
||||
|
||||
// Formatierung versuchen
|
||||
Serial.println("Failed to mount SPIFFS. Formatting...");
|
||||
SPIFFS.format();
|
||||
|
||||
// Zweiter Versuch nach Formatierung
|
||||
if (SPIFFS.begin(true)) {
|
||||
Serial.println("SPIFFS formatted and mounted successfully.");
|
||||
return true;
|
||||
}
|
||||
|
||||
Serial.println("SPIFFS initialization failed completely.");
|
||||
return false;
|
||||
Serial.printf("LittleFS Total: %u bytes\n", LittleFS.totalBytes());
|
||||
Serial.printf("LittleFS Used: %u bytes\n", LittleFS.usedBytes());
|
||||
Serial.printf("LittleFS Free: %u bytes\n", LittleFS.totalBytes() - LittleFS.usedBytes());
|
||||
}
|
@ -2,11 +2,12 @@
|
||||
#define COMMONFS_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <SPIFFS.h>
|
||||
#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);
|
||||
bool initializeSPIFFS();
|
||||
void initializeFileSystem();
|
||||
|
||||
#endif
|
||||
|
@ -19,6 +19,12 @@ 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)
|
||||
@ -40,6 +46,10 @@ 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
|
||||
uint8_t rfidTaskCore = 1;
|
||||
uint8_t rfidTaskPrio = 1;
|
||||
|
@ -11,6 +11,8 @@ extern const uint8_t LOADCELL_SCK_PIN;
|
||||
extern const uint8_t calVal_eepromAdress;
|
||||
extern const uint16_t SCALE_LEVEL_WEIGHT;
|
||||
|
||||
extern const uint8_t TTP223_PIN;
|
||||
|
||||
extern const int8_t OLED_RESET;
|
||||
extern const uint8_t SCREEN_ADDRESS;
|
||||
extern const uint8_t SCREEN_WIDTH;
|
||||
@ -23,6 +25,8 @@ 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[];
|
||||
extern const unsigned char cloud_on[];
|
||||
|
@ -20,9 +20,9 @@ void setupDisplay() {
|
||||
// the library initializes this with an Adafruit splash screen.
|
||||
display.setTextColor(WHITE);
|
||||
display.display();
|
||||
delay(1000); // Pause for 2 seconds
|
||||
oledShowTopRow();
|
||||
delay(2000);
|
||||
oledShowMessage("FilaMan v" + String(VERSION));
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
void oledclearline() {
|
||||
@ -117,7 +117,6 @@ std::vector<String> splitTextIntoLines(String text, uint8_t textSize) {
|
||||
lines.push_back(currentLine);
|
||||
}
|
||||
|
||||
Serial.println(lines.size());
|
||||
return lines;
|
||||
}
|
||||
|
||||
@ -140,8 +139,9 @@ void oledShowMultilineMessage(String message, uint8_t size) {
|
||||
int totalHeight = lines.size() * lineHeight;
|
||||
int startY = OLED_DATA_START + ((OLED_DATA_END - OLED_DATA_START - totalHeight) / 2);
|
||||
|
||||
uint8_t lineDistance = (lines.size() == 2) ? 5 : 0;
|
||||
for (size_t i = 0; i < lines.size(); i++) {
|
||||
display.setCursor(oled_center_h(lines[i]), startY + (i * lineHeight));
|
||||
display.setCursor(oled_center_h(lines[i]), startY + (i * lineHeight) + (i == 1 ? lineDistance : 0));
|
||||
display.print(lines[i]);
|
||||
}
|
||||
|
||||
@ -177,7 +177,7 @@ void oledShowTopRow() {
|
||||
display.drawBitmap(50, 0, bitmap_off , 16, 16, WHITE);
|
||||
}
|
||||
|
||||
if (spoolman_connected == 1) {
|
||||
if (spoolmanApiState != API_INIT) {
|
||||
display.drawBitmap(80, 0, bitmap_spoolman_on , 16, 16, WHITE);
|
||||
} else {
|
||||
display.drawBitmap(80, 0, bitmap_off , 16, 16, WHITE);
|
||||
|
175
src/main.cpp
@ -1,6 +1,4 @@
|
||||
#include <Arduino.h>
|
||||
#include <DNSServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <Wire.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
@ -15,12 +13,22 @@
|
||||
#include "esp_task_wdt.h"
|
||||
#include "commonFS.h"
|
||||
|
||||
bool mainTaskWasPaused = 0;
|
||||
uint8_t scaleTareCounter = 0;
|
||||
bool touchSensorConnected = false;
|
||||
|
||||
// ##### SETUP #####
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
uint64_t chipid;
|
||||
|
||||
chipid = ESP.getEfuseMac(); //The chip ID is essentially its MAC address(length: 6 bytes).
|
||||
Serial.printf("ESP32 Chip ID = %04X", (uint16_t)(chipid >> 32)); //print High 2 bytes
|
||||
Serial.printf("%08X\n", (uint32_t)chipid); //print Low 4bytes.
|
||||
|
||||
// Initialize SPIFFS
|
||||
initializeSPIFFS();
|
||||
initializeFileSystem();
|
||||
|
||||
// Start Display
|
||||
setupDisplay();
|
||||
@ -29,86 +37,151 @@ void setup() {
|
||||
initWiFi();
|
||||
|
||||
// Webserver
|
||||
Serial.println("Starte Webserver");
|
||||
setupWebserver(server);
|
||||
|
||||
// Spoolman API
|
||||
// api.cpp
|
||||
initSpoolman();
|
||||
|
||||
// Bambu MQTT
|
||||
// bambu.cpp
|
||||
setupMqtt();
|
||||
|
||||
// mDNS
|
||||
Serial.println("Starte MDNS");
|
||||
if (!MDNS.begin("filaman")) { // Set the hostname to "esp32.local"
|
||||
Serial.println("Error setting up MDNS responder!");
|
||||
while(1) {
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
Serial.println("mDNS responder started");
|
||||
|
||||
// NFC Reader
|
||||
startNfc();
|
||||
|
||||
start_scale();
|
||||
|
||||
// Touch Sensor
|
||||
pinMode(TTP223_PIN, INPUT_PULLUP);
|
||||
if (digitalRead(TTP223_PIN) == LOW)
|
||||
{
|
||||
Serial.println("Touch Sensor is connected");
|
||||
touchSensorConnected = true;
|
||||
}
|
||||
|
||||
// Scale
|
||||
start_scale(touchSensorConnected);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Safe interval check that handles millis() overflow
|
||||
* @param currentTime Current millis() value
|
||||
* @param lastTime Last recorded time
|
||||
* @param interval Desired interval in milliseconds
|
||||
* @return True if interval has elapsed
|
||||
*/
|
||||
bool intervalElapsed(unsigned long currentTime, unsigned long &lastTime, unsigned long interval) {
|
||||
if (currentTime - lastTime >= interval || currentTime < lastTime) {
|
||||
lastTime = currentTime;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned long lastWeightReadTime = 0;
|
||||
const unsigned long weightReadInterval = 1000; // 1 second
|
||||
|
||||
unsigned long lastAmsSendTime = 0;
|
||||
const unsigned long amsSendInterval = 60000; // 1 minute
|
||||
unsigned long lastAutoSetBambuAmsTime = 0;
|
||||
const unsigned long autoSetBambuAmsInterval = 1000; // 1 second
|
||||
uint8_t autoAmsCounter = 0;
|
||||
|
||||
uint8_t weightSend = 0;
|
||||
int16_t lastWeight = 0;
|
||||
uint8_t wifiErrorCounter = 0;
|
||||
|
||||
// WIFI check variables
|
||||
unsigned long lastWifiCheckTime = 0;
|
||||
const unsigned long wifiCheckInterval = 60000; // Überprüfe alle 60 Sekunden (60000 ms)
|
||||
|
||||
// Button debounce variables
|
||||
unsigned long lastButtonPress = 0;
|
||||
const unsigned long debounceDelay = 500; // 500 ms debounce delay
|
||||
|
||||
// ##### PROGRAM START #####
|
||||
void loop() {
|
||||
// Überprüfe den WLAN-Status
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
wifiErrorCounter++;
|
||||
wifiOn = false;
|
||||
} else {
|
||||
wifiErrorCounter = 0;
|
||||
wifiOn = true;
|
||||
}
|
||||
if (wifiErrorCounter > 20) ESP.restart();
|
||||
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
// Send AMS Data min every Minute
|
||||
if (currentMillis - lastAmsSendTime >= amsSendInterval) {
|
||||
lastAmsSendTime = currentMillis;
|
||||
sendAmsData(nullptr);
|
||||
// Ü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))
|
||||
{
|
||||
checkWiFiConnection();
|
||||
}
|
||||
|
||||
// Wenn Bambu auto set Spool aktiv
|
||||
if (autoSendToBambu && autoSetToBambuSpoolId > 0)
|
||||
{
|
||||
if (!bambuDisabled && !bambu_connected)
|
||||
{
|
||||
bambu_restart();
|
||||
}
|
||||
|
||||
if (intervalElapsed(currentMillis, lastAutoSetBambuAmsTime, autoSetBambuAmsInterval))
|
||||
{
|
||||
if (nfcReaderState == NFC_IDLE)
|
||||
{
|
||||
lastAutoSetBambuAmsTime = currentMillis;
|
||||
oledShowMessage("Auto Set " + String(autoSetBambuAmsCounter - autoAmsCounter) + "s");
|
||||
autoAmsCounter++;
|
||||
|
||||
if (autoAmsCounter >= autoSetBambuAmsCounter)
|
||||
{
|
||||
autoSetToBambuSpoolId = 0;
|
||||
autoAmsCounter = 0;
|
||||
oledShowWeight(weight);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
autoAmsCounter = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wenn Waage nicht Kalibriert
|
||||
if (scaleCalibrated == 3)
|
||||
{
|
||||
oledShowMessage("Scale not calibrated!");
|
||||
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
||||
yield();
|
||||
esp_task_wdt_reset();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Ausgabe der Waage auf Display
|
||||
if (pauseMainTask == 0 && weight != lastWeight && hasReadRfidTag == 0)
|
||||
if(pauseMainTask == 0)
|
||||
{
|
||||
(weight < 0) ? oledShowMessage("!! -1") : oledShowWeight(weight);
|
||||
if (mainTaskWasPaused || (weight != lastWeight && nfcReaderState == NFC_IDLE && (!autoSendToBambu || autoSetToBambuSpoolId == 0)))
|
||||
{
|
||||
(weight < 2) ? ((weight < -2) ? oledShowMessage("!! -0") : oledShowWeight(0)) : oledShowWeight(weight);
|
||||
}
|
||||
mainTaskWasPaused = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
mainTaskWasPaused = true;
|
||||
}
|
||||
|
||||
|
||||
// Wenn Timer abgelaufen und nicht gerade ein RFID-Tag geschrieben wird
|
||||
if (currentMillis - lastWeightReadTime >= weightReadInterval && hasReadRfidTag < 3)
|
||||
if (currentMillis - lastWeightReadTime >= weightReadInterval && nfcReaderState < NFC_WRITING)
|
||||
{
|
||||
lastWeightReadTime = currentMillis;
|
||||
|
||||
// Prüfen ob die Waage korrekt genullt ist
|
||||
if ((weight > 0 && weight < 5) || weight < 0)
|
||||
// Abweichung von 2g ignorieren
|
||||
if (autoTare && (weight > 2 && weight < 7) || weight < -2)
|
||||
{
|
||||
scale_tare_counter++;
|
||||
}
|
||||
@ -118,7 +191,7 @@ void loop() {
|
||||
}
|
||||
|
||||
// Prüfen ob das Gewicht gleich bleibt und dann senden
|
||||
if (weight == lastWeight && weight > 5)
|
||||
if (abs(weight - lastWeight) <= 2 && weight > 5)
|
||||
{
|
||||
weigthCouterToApi++;
|
||||
}
|
||||
@ -130,7 +203,8 @@ void loop() {
|
||||
}
|
||||
|
||||
// reset weight counter after writing tag
|
||||
if (currentMillis - lastWeightReadTime >= weightReadInterval && hasReadRfidTag > 1)
|
||||
// TBD: what exactly is the logic behind this?
|
||||
if (currentMillis - lastWeightReadTime >= weightReadInterval && nfcReaderState != NFC_IDLE && nfcReaderState != NFC_READ_SUCCESS)
|
||||
{
|
||||
weigthCouterToApi = 0;
|
||||
}
|
||||
@ -138,13 +212,19 @@ void loop() {
|
||||
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) {
|
||||
if (spoolId != "" && weigthCouterToApi > 3 && weightSend == 0 && nfcReaderState == NFC_READ_SUCCESS) {
|
||||
oledShowIcon("loading");
|
||||
if (updateSpoolWeight(spoolId, weight))
|
||||
{
|
||||
oledShowIcon("success");
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
weightSend = 1;
|
||||
autoSetToBambuSpoolId = spoolId.toInt();
|
||||
|
||||
if (octoEnabled)
|
||||
{
|
||||
updateSpoolOcto(autoSetToBambuSpoolId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -152,7 +232,6 @@ void loop() {
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
yield();
|
||||
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
|
119
src/nfc.cpp
@ -18,7 +18,7 @@ String spoolId = "";
|
||||
String nfcJsonData = "";
|
||||
volatile bool pauseBambuMqttTask = false;
|
||||
|
||||
volatile uint8_t hasReadRfidTag = 0;
|
||||
volatile nfcReaderStateType nfcReaderState = NFC_IDLE;
|
||||
// 0 = nicht gelesen
|
||||
// 1 = erfolgreich gelesen
|
||||
// 2 = fehler beim Lesen
|
||||
@ -44,8 +44,6 @@ void payloadToJson(uint8_t *data) {
|
||||
DeserializationError error = deserializeJson(doc, jsonString);
|
||||
|
||||
if (!error) {
|
||||
const char* version = doc["version"];
|
||||
const char* protocol = doc["protocol"];
|
||||
const char* color_hex = doc["color_hex"];
|
||||
const char* type = doc["type"];
|
||||
int min_temp = doc["min_temp"];
|
||||
@ -55,8 +53,6 @@ void payloadToJson(uint8_t *data) {
|
||||
Serial.println();
|
||||
Serial.println("-----------------");
|
||||
Serial.println("JSON-Parsed Data:");
|
||||
Serial.println(version);
|
||||
Serial.println(protocol);
|
||||
Serial.println(color_hex);
|
||||
Serial.println(type);
|
||||
Serial.println(min_temp);
|
||||
@ -93,8 +89,16 @@ bool formatNdefTag() {
|
||||
return success;
|
||||
}
|
||||
|
||||
uint16_t readTagSize()
|
||||
{
|
||||
uint8_t buffer[4];
|
||||
memset(buffer, 0, 4);
|
||||
nfc.ntag2xx_ReadPage(3, buffer);
|
||||
return buffer[2]*8;
|
||||
}
|
||||
|
||||
uint8_t ntag2xx_WriteNDEF(const char *payload) {
|
||||
uint8_t tagSize = 240; // 144 bytes is maximum for NTAG213
|
||||
uint16_t tagSize = readTagSize();
|
||||
Serial.print("Tag Size: ");Serial.println(tagSize);
|
||||
|
||||
uint8_t pageBuffer[4] = {0, 0, 0, 0};
|
||||
@ -136,6 +140,8 @@ uint8_t ntag2xx_WriteNDEF(const char *payload) {
|
||||
if (combinedData == NULL)
|
||||
{
|
||||
Serial.println("Fehler: Nicht genug Speicher vorhanden.");
|
||||
oledShowMessage("Tag too small");
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -236,14 +242,16 @@ void writeJsonToTag(void *parameter) {
|
||||
Serial.println("Erstelle NDEF-Message...");
|
||||
Serial.println(payload);
|
||||
|
||||
hasReadRfidTag = 3;
|
||||
nfcReaderState = NFC_WRITING;
|
||||
vTaskSuspend(RfidReaderTask);
|
||||
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||
|
||||
//pauseBambuMqttTask = true;
|
||||
// aktualisieren der Website wenn sich der Status ändert
|
||||
sendNfcData(nullptr);
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
oledShowMessage("Waiting for NFC-Tag");
|
||||
|
||||
|
||||
// Wait 10sec for tag
|
||||
uint8_t success = 0;
|
||||
String uidString = "";
|
||||
@ -280,7 +288,7 @@ void writeJsonToTag(void *parameter) {
|
||||
//oledShowMessage("NFC-Tag written");
|
||||
oledShowIcon("success");
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
hasReadRfidTag = 5;
|
||||
nfcReaderState = NFC_WRITE_SUCCESS;
|
||||
// aktualisieren der Website wenn sich der Status ändert
|
||||
sendNfcData(nullptr);
|
||||
pauseBambuMqttTask = false;
|
||||
@ -302,7 +310,7 @@ void writeJsonToTag(void *parameter) {
|
||||
Serial.println("Fehler beim Schreiben der NDEF-Message auf den Tag");
|
||||
oledShowIcon("failed");
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
hasReadRfidTag = 4;
|
||||
nfcReaderState = NFC_WRITE_ERROR;
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -310,7 +318,7 @@ void writeJsonToTag(void *parameter) {
|
||||
Serial.println("Fehler: Kein Tag zu schreiben gefunden.");
|
||||
oledShowMessage("No NFC-Tag found");
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
hasReadRfidTag = 0;
|
||||
nfcReaderState = NFC_IDLE;
|
||||
}
|
||||
|
||||
sendWriteResult(nullptr, success);
|
||||
@ -326,12 +334,12 @@ void startWriteJsonToTag(const char* payload) {
|
||||
char* payloadCopy = strdup(payload);
|
||||
|
||||
// Task nicht mehrfach starten
|
||||
if (hasReadRfidTag != 3) {
|
||||
if (nfcReaderState != NFC_WRITING) {
|
||||
// Erstelle die Task
|
||||
xTaskCreate(
|
||||
writeJsonToTag, // Task-Funktion
|
||||
"WriteJsonToTagTask", // Task-Name
|
||||
4096, // Stackgröße in Bytes
|
||||
5115, // Stackgröße in Bytes
|
||||
(void*)payloadCopy, // Parameter
|
||||
rfidWriteTaskPrio, // Priorität
|
||||
NULL // Task-Handle (nicht benötigt)
|
||||
@ -343,7 +351,7 @@ void scanRfidTask(void * parameter) {
|
||||
Serial.println("RFID Task gestartet");
|
||||
for(;;) {
|
||||
// Wenn geschrieben wird Schleife aussetzen
|
||||
if (hasReadRfidTag != 3)
|
||||
if (nfcReaderState != NFC_WRITING)
|
||||
{
|
||||
yield();
|
||||
|
||||
@ -355,58 +363,63 @@ void scanRfidTask(void * parameter) {
|
||||
|
||||
foundNfcTag(nullptr, success);
|
||||
|
||||
if (success && hasReadRfidTag != 1)
|
||||
if (success && nfcReaderState != NFC_READ_SUCCESS)
|
||||
{
|
||||
// Display some basic information about the card
|
||||
Serial.println("Found an ISO14443A card");
|
||||
|
||||
hasReadRfidTag = 6;
|
||||
nfcReaderState = NFC_READING;
|
||||
|
||||
oledShowIcon("transfer");
|
||||
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||
|
||||
if (uidLength == 7)
|
||||
{
|
||||
uint8_t data[256];
|
||||
uint16_t tagSize = readTagSize();
|
||||
if(tagSize > 0)
|
||||
{
|
||||
// Create a buffer depending on the size of the tag
|
||||
uint8_t* data = (uint8_t*)malloc(tagSize);
|
||||
memset(data, 0, tagSize);
|
||||
|
||||
// We probably have an NTAG2xx card (though it could be Ultralight as well)
|
||||
Serial.println("Seems to be an NTAG2xx tag (7 byte UID)");
|
||||
|
||||
for (uint8_t i = 0; i < 45; i++) {
|
||||
/*
|
||||
if (i < uidLength) {
|
||||
uidString += String(uid[i], HEX);
|
||||
if (i < uidLength - 1) {
|
||||
uidString += ":"; // Optional: Trennzeichen hinzufügen
|
||||
// We probably have an NTAG2xx card (though it could be Ultralight as well)
|
||||
Serial.println("Seems to be an NTAG2xx tag (7 byte UID)");
|
||||
|
||||
uint8_t numPages = readTagSize()/4;
|
||||
for (uint8_t i = 4; i < 4+numPages; i++) {
|
||||
if (!nfc.ntag2xx_ReadPage(i, data+(i-4) * 4))
|
||||
{
|
||||
break; // Stop if reading fails
|
||||
}
|
||||
}
|
||||
*/
|
||||
if (!nfc.mifareclassic_ReadDataBlock(i, data + (i - 4) * 4))
|
||||
{
|
||||
break; // Stop if reading fails
|
||||
}
|
||||
// Check for NDEF message end
|
||||
if (data[(i - 4) * 4] == 0xFE)
|
||||
{
|
||||
break; // End of NDEF message
|
||||
// Check for NDEF message end
|
||||
if (data[(i - 4) * 4] == 0xFE)
|
||||
{
|
||||
break; // End of NDEF message
|
||||
}
|
||||
|
||||
yield();
|
||||
esp_task_wdt_reset();
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
}
|
||||
|
||||
yield();
|
||||
esp_task_wdt_reset();
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
}
|
||||
if (!decodeNdefAndReturnJson(data))
|
||||
{
|
||||
oledShowMessage("NFC-Tag unknown");
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
nfcReaderState = NFC_READ_ERROR;
|
||||
}
|
||||
else
|
||||
{
|
||||
nfcReaderState = NFC_READ_SUCCESS;
|
||||
}
|
||||
|
||||
if (!decodeNdefAndReturnJson(data))
|
||||
{
|
||||
oledShowMessage("NFC-Tag unknown");
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
hasReadRfidTag = 2;
|
||||
free(data);
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
hasReadRfidTag = 1;
|
||||
oledShowMessage("NFC-Tag read error");
|
||||
nfcReaderState = NFC_READ_ERROR;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -414,13 +427,13 @@ void scanRfidTask(void * parameter) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!success && hasReadRfidTag > 0)
|
||||
if (!success && nfcReaderState != NFC_IDLE)
|
||||
{
|
||||
hasReadRfidTag = 0;
|
||||
nfcReaderState = NFC_IDLE;
|
||||
//uidString = "";
|
||||
nfcJsonData = "";
|
||||
Serial.println("Tag entfernt");
|
||||
oledShowWeight(0);
|
||||
if (!autoSendToBambu) oledShowWeight(weight);
|
||||
}
|
||||
|
||||
// aktualisieren der Website wenn sich der Status ändert
|
||||
@ -456,7 +469,7 @@ void startNfc() {
|
||||
BaseType_t result = xTaskCreatePinnedToCore(
|
||||
scanRfidTask, /* Function to implement the task */
|
||||
"RfidReader", /* Name of the task */
|
||||
10000, /* Stack size in words */
|
||||
5115, /* Stack size in words */
|
||||
NULL, /* Task input parameter */
|
||||
rfidTaskPrio, /* Priority of the task */
|
||||
&RfidReaderTask, /* Task handle. */
|
||||
|
14
src/nfc.h
@ -3,6 +3,16 @@
|
||||
|
||||
#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);
|
||||
@ -10,7 +20,9 @@ void startWriteJsonToTag(const char* payload);
|
||||
extern TaskHandle_t RfidReaderTask;
|
||||
extern String nfcJsonData;
|
||||
extern String spoolId;
|
||||
extern volatile uint8_t hasReadRfidTag;
|
||||
extern volatile nfcReaderStateType nfcReaderState;
|
||||
extern volatile bool pauseBambuMqttTask;
|
||||
|
||||
|
||||
|
||||
#endif
|
290
src/ota.cpp
@ -1,46 +1,266 @@
|
||||
#include <Arduino.h>
|
||||
#include "ota.h"
|
||||
#include <Update.h>
|
||||
#include <SPIFFS.h>
|
||||
#include "commonFS.h"
|
||||
#include <website.h>
|
||||
#include <commonFS.h>
|
||||
#include "scale.h"
|
||||
#include "bambu.h"
|
||||
#include "nfc.h"
|
||||
|
||||
void handleOTAUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
|
||||
static size_t contentLength = 0;
|
||||
|
||||
// Globale Variablen für Config Backups hinzufügen
|
||||
String bambuCredentialsBackup;
|
||||
String spoolmanUrlBackup;
|
||||
|
||||
// Globale Variable für den Update-Typ
|
||||
static int currentUpdateCommand = 0;
|
||||
|
||||
// Globale Update-Variablen
|
||||
static size_t updateTotalSize = 0;
|
||||
static size_t updateWritten = 0;
|
||||
static bool isSpiffsUpdate = false;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
if (!index) {
|
||||
contentLength = request->contentLength();
|
||||
Serial.printf("Update size: %u bytes\n", contentLength);
|
||||
|
||||
if (contentLength == 0) {
|
||||
request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"Invalid file size\"}");
|
||||
return;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (!Update.begin(contentLength)) {
|
||||
Serial.printf("Not enough space: %u required\n", contentLength);
|
||||
request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"Not enough space available\"}");
|
||||
return;
|
||||
void backupJsonConfigs() {
|
||||
// Bambu Credentials backup
|
||||
if (LittleFS.exists("/bambu_credentials.json")) {
|
||||
File file = LittleFS.open("/bambu_credentials.json", "r");
|
||||
if (file) {
|
||||
bambuCredentialsBackup = file.readString();
|
||||
file.close();
|
||||
Serial.println("Bambu credentials backed up");
|
||||
}
|
||||
|
||||
Serial.println("Update started");
|
||||
}
|
||||
|
||||
if (Update.write(data, len) != len) {
|
||||
Update.printError(Serial);
|
||||
request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"Error writing update\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (final) {
|
||||
if (Update.end(true)) {
|
||||
Serial.println("Update complete");
|
||||
request->send(200, "application/json", "{\"status\":\"success\",\"message\":\"Update successful! Device will restart...\",\"restart\":true}");
|
||||
delay(1000);
|
||||
ESP.restart();
|
||||
} else {
|
||||
Update.printError(Serial);
|
||||
request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"Update failed\"}");
|
||||
// Spoolman URL backup
|
||||
if (LittleFS.exists("/spoolman_url.json")) {
|
||||
File file = LittleFS.open("/spoolman_url.json", "r");
|
||||
if (file) {
|
||||
spoolmanUrlBackup = file.readString();
|
||||
file.close();
|
||||
Serial.println("Spoolman URL backed up");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void restoreJsonConfigs() {
|
||||
// Restore Bambu credentials
|
||||
if (bambuCredentialsBackup.length() > 0) {
|
||||
File file = LittleFS.open("/bambu_credentials.json", "w");
|
||||
if (file) {
|
||||
file.print(bambuCredentialsBackup);
|
||||
file.close();
|
||||
Serial.println("Bambu credentials restored");
|
||||
}
|
||||
bambuCredentialsBackup = ""; // Clear backup
|
||||
}
|
||||
|
||||
// Restore Spoolman URL
|
||||
if (spoolmanUrlBackup.length() > 0) {
|
||||
File file = LittleFS.open("/spoolman_url.json", "w");
|
||||
if (file) {
|
||||
file.print(spoolmanUrlBackup);
|
||||
file.close();
|
||||
Serial.println("Spoolman URL restored");
|
||||
}
|
||||
spoolmanUrlBackup = ""; // Clear backup
|
||||
}
|
||||
}
|
||||
|
||||
void espRestart() {
|
||||
yield();
|
||||
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
||||
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
|
||||
void sendUpdateProgress(int progress, const char* status = nullptr, const char* message = nullptr) {
|
||||
static int lastSentProgress = -1;
|
||||
|
||||
// Verhindere zu häufige Updates
|
||||
if (progress == lastSentProgress && !status && !message) {
|
||||
return;
|
||||
}
|
||||
|
||||
String progressMsg = "{\"type\":\"updateProgress\",\"progress\":" + String(progress);
|
||||
if (status) {
|
||||
progressMsg += ",\"status\":\"" + String(status) + "\"";
|
||||
}
|
||||
if (message) {
|
||||
progressMsg += ",\"message\":\"" + String(message) + "\"";
|
||||
}
|
||||
progressMsg += "}";
|
||||
|
||||
if (progress >= 100) {
|
||||
// Sende die Nachricht nur einmal für den Abschluss
|
||||
ws.textAll("{\"type\":\"updateProgress\",\"progress\":100,\"status\":\"success\",\"message\":\"Update successful! Restarting device...\"}");
|
||||
delay(50);
|
||||
}
|
||||
|
||||
// Sende die Nachricht mehrmals mit Verzögerung für wichtige Updates
|
||||
if (status || abs(progress - lastSentProgress) >= 10 || progress == 100) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
ws.textAll(progressMsg);
|
||||
delay(100); // Längerer Delay zwischen Nachrichten
|
||||
}
|
||||
} else {
|
||||
ws.textAll(progressMsg);
|
||||
delay(50);
|
||||
}
|
||||
|
||||
lastSentProgress = progress;
|
||||
}
|
||||
|
||||
void handleUpdate(AsyncWebServer &server) {
|
||||
AsyncCallbackWebHandler* updateHandler = new AsyncCallbackWebHandler();
|
||||
updateHandler->setUri("/update");
|
||||
updateHandler->setMethod(HTTP_POST);
|
||||
|
||||
// Check if current version is less than defined TOOLVERSION before proceeding with update
|
||||
if (isVersionLessThan(VERSION, TOOLDVERSION)) {
|
||||
updateHandler->onRequest([](AsyncWebServerRequest *request) {
|
||||
request->send(400, "application/json",
|
||||
"{\"success\":false,\"message\":\"Your current version is too old. Please perform a full upgrade.\"}");
|
||||
});
|
||||
server.addHandler(updateHandler);
|
||||
return;
|
||||
}
|
||||
|
||||
updateHandler->onUpload([](AsyncWebServerRequest *request, String filename,
|
||||
size_t index, uint8_t *data, size_t len, bool final) {
|
||||
|
||||
// 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;
|
||||
isSpiffsUpdate = (filename.indexOf("website") > -1);
|
||||
|
||||
if (isSpiffsUpdate) {
|
||||
// Backup vor dem Update
|
||||
sendUpdateProgress(0, "backup", "Backing up configurations...");
|
||||
vTaskDelay(200 / portTICK_PERIOD_MS);
|
||||
backupJsonConfigs();
|
||||
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)) {
|
||||
request->send(400, "application/json", "{\"success\":false,\"message\":\"Update initialization failed\"}");
|
||||
return;
|
||||
}
|
||||
sendUpdateProgress(5, "starting", "Starting SPIFFS update...");
|
||||
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...");
|
||||
vTaskDelay(200 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
if (len) {
|
||||
if (Update.write(data, len) != len) {
|
||||
request->send(400, "application/json", "{\"success\":false,\"message\":\"Write failed\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
updateWritten += len;
|
||||
int currentProgress;
|
||||
|
||||
// Berechne den Fortschritt basierend auf dem Update-Typ
|
||||
if (isSpiffsUpdate) {
|
||||
// SPIFFS: 5-75% für Upload
|
||||
currentProgress = 6 + (updateWritten * 100) / updateTotalSize;
|
||||
} else {
|
||||
// Firmware: 0-100% für Upload
|
||||
currentProgress = 1 + (updateWritten * 100) / updateTotalSize;
|
||||
}
|
||||
|
||||
static int lastProgress = -1;
|
||||
if (currentProgress != lastProgress && (currentProgress % 10 == 0 || final)) {
|
||||
sendUpdateProgress(currentProgress, "uploading");
|
||||
oledShowMessage("Update: " + String(currentProgress) + "%");
|
||||
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||
lastProgress = currentProgress;
|
||||
}
|
||||
}
|
||||
|
||||
if (final) {
|
||||
if (Update.end(true)) {
|
||||
if (isSpiffsUpdate) {
|
||||
restoreJsonConfigs();
|
||||
}
|
||||
} else {
|
||||
request->send(400, "application/json", "{\"success\":false,\"message\":\"Update finalization failed\"}");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
updateHandler->onRequest([](AsyncWebServerRequest *request) {
|
||||
if (Update.hasError()) {
|
||||
request->send(400, "application/json", "{\"success\":false,\"message\":\"Update failed\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Erste 100% Nachricht
|
||||
ws.textAll("{\"type\":\"updateProgress\",\"progress\":100,\"status\":\"success\",\"message\":\"Update successful! Restarting device...\"}");
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
|
||||
AsyncWebServerResponse *response = request->beginResponse(200, "application/json",
|
||||
"{\"success\":true,\"message\":\"Update successful! Restarting device...\"}");
|
||||
response->addHeader("Connection", "close");
|
||||
request->send(response);
|
||||
|
||||
// Zweite 100% Nachricht zur Sicherheit
|
||||
ws.textAll("{\"type\":\"updateProgress\",\"progress\":100,\"status\":\"success\",\"message\":\"Update successful! Restarting device...\"}");
|
||||
|
||||
espRestart();
|
||||
});
|
||||
|
||||
server.addHandler(updateHandler);
|
||||
}
|
@ -1,8 +1,9 @@
|
||||
#ifndef OTA_H
|
||||
#define OTA_H
|
||||
|
||||
#include <ArduinoOTA.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
void handleOTAUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
|
||||
void handleUpdate(AsyncWebServer &server);
|
||||
|
||||
#endif
|
137
src/scale.cpp
@ -3,9 +3,9 @@
|
||||
#include <ArduinoJson.h>
|
||||
#include "config.h"
|
||||
#include "HX711.h"
|
||||
#include <EEPROM.h>
|
||||
#include "display.h"
|
||||
#include "esp_task_wdt.h"
|
||||
#include <Preferences.h>
|
||||
|
||||
HX711 scale;
|
||||
|
||||
@ -15,9 +15,30 @@ int16_t weight = 0;
|
||||
|
||||
uint8_t weigthCouterToApi = 0;
|
||||
uint8_t scale_tare_counter = 0;
|
||||
bool scaleTareRequest = false;
|
||||
uint8_t pauseMainTask = 0;
|
||||
uint8_t scaleCalibrated = 1;
|
||||
|
||||
Preferences preferences;
|
||||
const char* NVS_NAMESPACE = "scale";
|
||||
const char* NVS_KEY_CALIBRATION = "cal_value";
|
||||
const char* NVS_KEY_AUTOTARE = "auto_tare";
|
||||
bool autoTare = true;
|
||||
|
||||
// ##### Funktionen für Waage #####
|
||||
uint8_t setAutoTare(bool autoTareValue) {
|
||||
Serial.print("Set AutoTare to ");
|
||||
Serial.println(autoTareValue);
|
||||
autoTare = autoTareValue;
|
||||
|
||||
// Speichern mit NVS
|
||||
preferences.begin(NVS_NAMESPACE, false); // false = readwrite
|
||||
preferences.putBool(NVS_KEY_AUTOTARE, autoTare);
|
||||
preferences.end();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint8_t tareScale() {
|
||||
Serial.println("Tare scale");
|
||||
scale.tare();
|
||||
@ -29,39 +50,69 @@ 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)
|
||||
// Waage automatisch Taren, wenn zu lange Abweichung
|
||||
if (autoTare && scale_tare_counter >= 5)
|
||||
{
|
||||
Serial.println("Auto Tare scale");
|
||||
scale.tare();
|
||||
scale_tare_counter = 0;
|
||||
}
|
||||
|
||||
// Waage manuell Taren
|
||||
if (scaleTareRequest == true)
|
||||
{
|
||||
Serial.println("Re-Tare scale");
|
||||
oledShowMessage("TARE Scale");
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
scale.tare();
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
oledShowWeight(0);
|
||||
scaleTareRequest = false;
|
||||
}
|
||||
|
||||
weight = round(scale.get_units());
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(100)); // Verzögerung, um die CPU nicht zu überlasten
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
}
|
||||
|
||||
void start_scale() {
|
||||
void start_scale(bool touchSensorConnected) {
|
||||
Serial.println("Prüfe Calibration Value");
|
||||
long calibrationValue; // calibration value (see example file "Calibration.ino")
|
||||
//calibrationValue = 696.0; // uncomment this if you want to set the calibration value in the sketch
|
||||
float calibrationValue;
|
||||
|
||||
EEPROM.begin(512);
|
||||
EEPROM.get(calVal_eepromAdress, calibrationValue); // uncomment this if you want to fetch the calibration value from eeprom
|
||||
// NVS lesen
|
||||
preferences.begin(NVS_NAMESPACE, true); // true = readonly
|
||||
calibrationValue = preferences.getFloat(NVS_KEY_CALIBRATION, defaultScaleCalibrationValue);
|
||||
|
||||
// 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);
|
||||
|
||||
//calibrationValue = EEPROM.read(calVal_eepromAdress);
|
||||
preferences.end();
|
||||
|
||||
Serial.print("Read Scale Calibration Value ");
|
||||
Serial.println(calibrationValue);
|
||||
|
||||
scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
|
||||
|
||||
if (isnan(calibrationValue) || calibrationValue < 1) calibrationValue = defaultScaleCalibrationValue;
|
||||
if (isnan(calibrationValue) || calibrationValue < 1) {
|
||||
calibrationValue = defaultScaleCalibrationValue;
|
||||
scaleCalibrated = 0;
|
||||
|
||||
oledShowMessage("Scale not calibrated!");
|
||||
for (uint16_t i = 0; i < 50000; i++) {
|
||||
yield();
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
}
|
||||
|
||||
oledShowMessage("Scale Tare Please remove all");
|
||||
for (uint16_t i = 0; i < 2000; i++) {
|
||||
@ -83,7 +134,7 @@ void 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. */
|
||||
@ -97,15 +148,18 @@ void start_scale() {
|
||||
}
|
||||
|
||||
uint8_t calibrate_scale() {
|
||||
long newCalibrationValue;
|
||||
uint8_t returnState = 0;
|
||||
float newCalibrationValue;
|
||||
|
||||
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");
|
||||
|
||||
@ -127,7 +181,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);
|
||||
|
||||
@ -137,30 +191,36 @@ uint8_t calibrate_scale() {
|
||||
{
|
||||
Serial.print("New calibration value has been set to: ");
|
||||
Serial.println(newCalibrationValue);
|
||||
Serial.print("Save this value to EEPROM adress ");
|
||||
Serial.println(calVal_eepromAdress);
|
||||
|
||||
//EEPROM.put(calVal_eepromAdress, newCalibrationValue);
|
||||
EEPROM.put(calVal_eepromAdress, newCalibrationValue);
|
||||
EEPROM.commit();
|
||||
// Speichern mit NVS
|
||||
preferences.begin(NVS_NAMESPACE, false); // false = readwrite
|
||||
preferences.putFloat(NVS_KEY_CALIBRATION, newCalibrationValue);
|
||||
preferences.end();
|
||||
|
||||
EEPROM.get(calVal_eepromAdress, newCalibrationValue);
|
||||
//newCalibrationValue = EEPROM.read(calVal_eepromAdress);
|
||||
// Verifizieren
|
||||
preferences.begin(NVS_NAMESPACE, true);
|
||||
float verifyValue = preferences.getFloat(NVS_KEY_CALIBRATION, 0);
|
||||
preferences.end();
|
||||
|
||||
Serial.print("Read Value ");
|
||||
Serial.println(newCalibrationValue);
|
||||
Serial.print("Verified stored value: ");
|
||||
Serial.println(verifyValue);
|
||||
|
||||
Serial.println("End calibration, revome weight");
|
||||
Serial.println("End calibration, remove weight");
|
||||
|
||||
oledShowMessage("Remove weight");
|
||||
|
||||
scale.set_scale(newCalibrationValue);
|
||||
for (uint16_t i = 0; i < 2000; i++) {
|
||||
yield();
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
|
||||
oledShowMessage("Scale calibrated");
|
||||
|
||||
oledShowMessage("Calibration done");
|
||||
// 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();
|
||||
@ -168,8 +228,9 @@ uint8_t calibrate_scale() {
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
|
||||
//ESP.restart();
|
||||
returnState = 1;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
{
|
||||
@ -182,10 +243,10 @@ uint8_t calibrate_scale() {
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
return 0;
|
||||
returnState = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("HX711 not found.");
|
||||
@ -197,17 +258,13 @@ 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;
|
||||
|
||||
return 1;
|
||||
return returnState;
|
||||
}
|
||||
|
@ -4,8 +4,8 @@
|
||||
#include <Arduino.h>
|
||||
#include "HX711.h"
|
||||
|
||||
|
||||
void start_scale();
|
||||
uint8_t setAutoTare(bool autoTareValue);
|
||||
uint8_t start_scale(bool touchSensorConnected);
|
||||
uint8_t calibrate_scale();
|
||||
uint8_t tareScale();
|
||||
|
||||
@ -13,6 +13,11 @@ 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 autoTare;
|
||||
|
||||
extern TaskHandle_t ScaleTask;
|
||||
|
||||
#endif
|
250
src/website.cpp
@ -7,44 +7,57 @@
|
||||
#include "nfc.h"
|
||||
#include "scale.h"
|
||||
#include "esp_task_wdt.h"
|
||||
#include <Update.h>
|
||||
#include "display.h"
|
||||
#include "ota.h"
|
||||
|
||||
#ifndef VERSION
|
||||
#define VERSION "1.1.0"
|
||||
#endif
|
||||
|
||||
// Cache-Control Header definieren
|
||||
#define CACHE_CONTROL "max-age=31536000" // Cache für 1 Jahr
|
||||
#define CACHE_CONTROL "max-age=604800" // Cache für 1 Woche
|
||||
|
||||
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) {
|
||||
if (type == WS_EVT_CONNECT) {
|
||||
Serial.println("Neuer Client verbunden!");
|
||||
// Sende die AMS-Daten an den neuen Client
|
||||
sendAmsData(client);
|
||||
if (!bambuDisabled) sendAmsData(client);
|
||||
sendNfcData(client);
|
||||
foundNfcTag(client, 0);
|
||||
sendWriteResult(client, 3);
|
||||
} else if (type == WS_EVT_DISCONNECT) {
|
||||
Serial.println("Client getrennt.");
|
||||
} else if (type == WS_EVT_ERROR) {
|
||||
Serial.printf("WebSocket Client #%u error(%u): %s\n", client->id(), *((uint16_t*)arg), (char*)data);
|
||||
} 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);
|
||||
|
||||
|
||||
bool spoolmanConnected = (spoolmanApiState != API_INIT);
|
||||
|
||||
if (doc["type"] == "heartbeat") {
|
||||
// Sende Heartbeat-Antwort
|
||||
ws.text(client->id(), "{"
|
||||
"\"type\":\"heartbeat\","
|
||||
"\"freeHeap\":" + String(ESP.getFreeHeap()/1024) + ","
|
||||
"\"bambu_connected\":" + String(bambu_connected) + ","
|
||||
"\"spoolman_connected\":" + String(spoolman_connected) + ""
|
||||
"\"spoolman_connected\":" + String(spoolmanConnected) + ""
|
||||
"}");
|
||||
}
|
||||
|
||||
else if (doc["type"] == "writeNfcTag") {
|
||||
if (doc.containsKey("payload")) {
|
||||
if (doc["payload"].is<JsonObject>()) {
|
||||
// Versuche NFC-Daten zu schreiben
|
||||
String payloadString;
|
||||
serializeJson(doc["payload"], payloadString);
|
||||
@ -62,6 +75,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 {
|
||||
@ -84,6 +101,15 @@ void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventTyp
|
||||
setBambuSpool(doc["payload"]);
|
||||
}
|
||||
|
||||
else if (doc["type"] == "setSpoolmanSettings") {
|
||||
Serial.println(doc["payload"].as<String>());
|
||||
if (updateSpoolBambuData(doc["payload"].as<String>())) {
|
||||
ws.textAll("{\"type\":\"setSpoolmanSettings\",\"payload\":\"success\"}");
|
||||
} else {
|
||||
ws.textAll("{\"type\":\"setSpoolmanSettings\",\"payload\":\"error\"}");
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
Serial.println("Unbekannter WebSocket-Typ: " + doc["type"].as<String>());
|
||||
}
|
||||
@ -93,12 +119,12 @@ void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventTyp
|
||||
// Funktion zum Laden und Ersetzen des Headers in einer HTML-Datei
|
||||
String loadHtmlWithHeader(const char* filename) {
|
||||
Serial.println("Lade HTML-Datei: " + String(filename));
|
||||
if (!SPIFFS.exists(filename)) {
|
||||
if (!LittleFS.exists(filename)) {
|
||||
Serial.println("Fehler: Datei nicht gefunden!");
|
||||
return "Fehler: Datei nicht gefunden!";
|
||||
}
|
||||
|
||||
File file = SPIFFS.open(filename, "r");
|
||||
File file = LittleFS.open(filename, "r");
|
||||
String html = file.readString();
|
||||
file.close();
|
||||
|
||||
@ -119,43 +145,51 @@ void foundNfcTag(AsyncWebSocketClient *client, uint8_t success) {
|
||||
}
|
||||
|
||||
void sendNfcData(AsyncWebSocketClient *client) {
|
||||
if (lastHasReadRfidTag == hasReadRfidTag) return;
|
||||
if (hasReadRfidTag == 0) {
|
||||
ws.textAll("{\"type\":\"nfcData\", \"payload\":{}}");
|
||||
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) {
|
||||
if (ams_count > 0) {
|
||||
ws.textAll("{\"type\":\"amsData\", \"payload\":" + amsJsonData + "}");
|
||||
ws.textAll("{\"type\":\"amsData\",\"payload\":" + amsJsonData + "}");
|
||||
}
|
||||
}
|
||||
|
||||
void setupWebserver(AsyncWebServer &server) {
|
||||
// Deaktiviere alle Debug-Ausgaben
|
||||
Serial.setDebugOutput(false);
|
||||
|
||||
// WebSocket-Optimierungen
|
||||
ws.onEvent(onWsEvent);
|
||||
ws.enable(true);
|
||||
|
||||
// Konfiguriere Server für große Uploads
|
||||
server.onRequestBody([](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){});
|
||||
server.onFileUpload([](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final){});
|
||||
|
||||
// Lade die Spoolman-URL beim Booten
|
||||
spoolmanUrl = loadSpoolmanUrl();
|
||||
Serial.print("Geladene Spoolman-URL: ");
|
||||
@ -164,7 +198,7 @@ void setupWebserver(AsyncWebServer &server) {
|
||||
// Route für about
|
||||
server.on("/about", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
Serial.println("Anfrage für /about erhalten");
|
||||
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/index.html.gz", "text/html");
|
||||
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/index.html.gz", "text/html");
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
response->addHeader("Cache-Control", CACHE_CONTROL);
|
||||
request->send(response);
|
||||
@ -173,33 +207,29 @@ void setupWebserver(AsyncWebServer &server) {
|
||||
// Route für Waage
|
||||
server.on("/waage", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
Serial.println("Anfrage für /waage erhalten");
|
||||
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/waage.html.gz", "text/html");
|
||||
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(SPIFFS, "/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);
|
||||
Serial.println("RFID-Seite gesendet");
|
||||
});
|
||||
|
||||
/*
|
||||
// Neue API-Route für das Abrufen der Spool-Daten
|
||||
server.on("/api/spools", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
Serial.println("API-Aufruf: /api/spools");
|
||||
JsonDocument spoolsData = fetchSpoolsForWebsite();
|
||||
String response;
|
||||
serializeJson(spoolsData, response);
|
||||
request->send(200, "application/json", response);
|
||||
});
|
||||
*/
|
||||
|
||||
server.on("/api/url", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
Serial.println("API-Aufruf: /api/url");
|
||||
String jsonResponse = "{\"spoolman_url\": \"" + String(spoolmanUrl) + "\"}";
|
||||
@ -209,7 +239,7 @@ void setupWebserver(AsyncWebServer &server) {
|
||||
// Route für WiFi
|
||||
server.on("/wifi", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
Serial.println("Anfrage für /wifi erhalten");
|
||||
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/wifi.html.gz", "text/html");
|
||||
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/wifi.html.gz", "text/html");
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
response->addHeader("Cache-Control", CACHE_CONTROL);
|
||||
request->send(response);
|
||||
@ -219,13 +249,18 @@ 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.containsKey("bambu_ip")) {
|
||||
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();
|
||||
@ -233,7 +268,17 @@ void setupWebserver(AsyncWebServer &server) {
|
||||
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));
|
||||
}
|
||||
|
||||
request->send(200, "text/html", html);
|
||||
});
|
||||
@ -245,17 +290,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;
|
||||
@ -264,16 +337,20 @@ void setupWebserver(AsyncWebServer &server) {
|
||||
String bambu_ip = request->getParam("bambu_ip")->value();
|
||||
String bambu_serialnr = request->getParam("bambu_serialnr")->value();
|
||||
String bambu_accesscode = request->getParam("bambu_accesscode")->value();
|
||||
bool autoSend = (request->getParam("autoSend")->value() == "true") ? true : false;
|
||||
String autoSendTime = request->getParam("autoSendTime")->value();
|
||||
|
||||
bambu_ip.trim();
|
||||
bambu_serialnr.trim();
|
||||
bambu_accesscode.trim();
|
||||
autoSendTime.trim();
|
||||
|
||||
if (bambu_ip.length() == 0 || bambu_serialnr.length() == 0 || bambu_accesscode.length() == 0) {
|
||||
request->send(400, "application/json", "{\"success\": false, \"error\": \"Empty parameter\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
bool success = saveBambuCredentials(bambu_ip, bambu_serialnr, bambu_accesscode);
|
||||
bool success = saveBambuCredentials(bambu_ip, bambu_serialnr, bambu_accesscode, autoSend, autoSendTime);
|
||||
|
||||
request->send(200, "application/json", "{\"healthy\": " + String(success ? "true" : "false") + "}");
|
||||
});
|
||||
@ -286,7 +363,7 @@ void setupWebserver(AsyncWebServer &server) {
|
||||
// Route für das Laden der CSS-Datei
|
||||
server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
Serial.println("Lade style.css");
|
||||
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/style.css.gz", "text/css");
|
||||
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/style.css.gz", "text/css");
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
response->addHeader("Cache-Control", CACHE_CONTROL);
|
||||
request->send(response);
|
||||
@ -295,7 +372,7 @@ void setupWebserver(AsyncWebServer &server) {
|
||||
|
||||
// Route für das Logo
|
||||
server.on("/logo.png", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/logo.png.gz", "image/png");
|
||||
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/logo.png.gz", "image/png");
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
response->addHeader("Cache-Control", CACHE_CONTROL);
|
||||
request->send(response);
|
||||
@ -304,7 +381,7 @@ void setupWebserver(AsyncWebServer &server) {
|
||||
|
||||
// Route für Favicon
|
||||
server.on("/favicon.ico", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/favicon.ico", "image/x-icon");
|
||||
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/favicon.ico", "image/x-icon");
|
||||
response->addHeader("Cache-Control", CACHE_CONTROL);
|
||||
request->send(response);
|
||||
Serial.println("favicon.ico gesendet");
|
||||
@ -312,17 +389,26 @@ void setupWebserver(AsyncWebServer &server) {
|
||||
|
||||
// Route für spool_in.png
|
||||
server.on("/spool_in.png", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/spool_in.png.gz", "image/png");
|
||||
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/spool_in.png.gz", "image/png");
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
response->addHeader("Cache-Control", CACHE_CONTROL);
|
||||
request->send(response);
|
||||
Serial.println("spool_in.png gesendet");
|
||||
});
|
||||
|
||||
// Route für set_spoolman.png
|
||||
server.on("/set_spoolman.png", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/set_spoolman.png.gz", "image/png");
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
response->addHeader("Cache-Control", CACHE_CONTROL);
|
||||
request->send(response);
|
||||
Serial.println("set_spoolman.png gesendet");
|
||||
});
|
||||
|
||||
// Route für JavaScript Dateien
|
||||
server.on("/spoolman.js", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
Serial.println("Anfrage für /spoolman.js erhalten");
|
||||
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/spoolman.js.gz", "text/javascript");
|
||||
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/spoolman.js.gz", "text/javascript");
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
response->addHeader("Cache-Control", CACHE_CONTROL);
|
||||
request->send(response);
|
||||
@ -331,37 +417,29 @@ void setupWebserver(AsyncWebServer &server) {
|
||||
|
||||
server.on("/rfid.js", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
Serial.println("Anfrage für /rfid.js erhalten");
|
||||
AsyncWebServerResponse *response = request->beginResponse(SPIFFS,"/rfid.js.gz", "text/javascript");
|
||||
AsyncWebServerResponse *response = request->beginResponse(LittleFS,"/rfid.js.gz", "text/javascript");
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
response->addHeader("Cache-Control", CACHE_CONTROL);
|
||||
request->send(response);
|
||||
Serial.println("RFID.js gesendet");
|
||||
});
|
||||
|
||||
// Route for Firmware Update
|
||||
// Vereinfachter Update-Handler
|
||||
server.on("/upgrade", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||
// During OTA, reduce memory usage
|
||||
ws.enable(false); // Temporarily disable WebSocket
|
||||
ws.cleanupClients();
|
||||
|
||||
Serial.println("Request for /upgrade received");
|
||||
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/upgrade.html.gz", "text/html");
|
||||
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/upgrade.html.gz", "text/html");
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
response->addHeader("Cache-Control", CACHE_CONTROL);
|
||||
response->addHeader("Cache-Control", "no-store");
|
||||
request->send(response);
|
||||
});
|
||||
|
||||
server.on("/update", HTTP_POST,
|
||||
[](AsyncWebServerRequest *request) {
|
||||
// The response will be sent from handleOTAUpload when the upload is complete
|
||||
},
|
||||
[](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) {
|
||||
// Free memory before handling update
|
||||
ws.enable(false);
|
||||
ws.cleanupClients();
|
||||
handleOTAUpload(request, filename, index, data, len, final);
|
||||
}
|
||||
);
|
||||
// Update-Handler registrieren
|
||||
handleUpdate(server);
|
||||
|
||||
server.on("/api/version", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
String fm_version = VERSION;
|
||||
String jsonResponse = "{\"version\": \""+ fm_version +"\"}";
|
||||
request->send(200, "application/json", jsonResponse);
|
||||
});
|
||||
|
||||
// Fehlerbehandlung für nicht gefundene Seiten
|
||||
server.onNotFound([](AsyncWebServerRequest *request){
|
||||
|
@ -6,8 +6,8 @@
|
||||
#include "commonFS.h"
|
||||
#include "api.h"
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <AsyncWebSocket.h>
|
||||
#include <Update.h>
|
||||
#include <AsyncTCP.h>
|
||||
#include "bambu.h"
|
||||
#include "nfc.h"
|
||||
#include "scale.h"
|
||||
@ -17,7 +17,12 @@ extern String spoolmanUrl;
|
||||
extern AsyncWebServer server;
|
||||
extern AsyncWebSocket ws;
|
||||
|
||||
// Server-Initialisierung und Handler
|
||||
void initWebServer();
|
||||
void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total);
|
||||
void setupWebserver(AsyncWebServer &server);
|
||||
|
||||
// WebSocket-Funktionen
|
||||
void sendAmsData(AsyncWebSocketClient *client);
|
||||
void sendNfcData(AsyncWebSocketClient *client);
|
||||
void foundNfcTag(AsyncWebSocketClient *client, uint8_t success);
|
||||
|
138
src/wlan.cpp
@ -3,43 +3,127 @@
|
||||
#include <WiFi.h>
|
||||
#include <esp_wifi.h>
|
||||
#include <WiFiManager.h>
|
||||
#include <DNSServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include "display.h"
|
||||
#include "config.h"
|
||||
|
||||
WiFiManager wm;
|
||||
bool wm_nonblocking = false;
|
||||
uint8_t wifiErrorCounter = 0;
|
||||
|
||||
void wifiSettings() {
|
||||
// Optimierte WiFi-Einstellungen
|
||||
WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP
|
||||
WiFi.setSleep(false); // disable sleep mode
|
||||
WiFi.setHostname("FilaMan");
|
||||
esp_wifi_set_ps(WIFI_PS_NONE);
|
||||
|
||||
// Maximale Sendeleistung
|
||||
WiFi.setTxPower(WIFI_POWER_19_5dBm); // Set maximum transmit power
|
||||
|
||||
// Optimiere TCP/IP Stack
|
||||
esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N);
|
||||
|
||||
// Aktiviere WiFi-Roaming für bessere Stabilität
|
||||
esp_wifi_set_rssi_threshold(-80);
|
||||
}
|
||||
|
||||
void startMDNS() {
|
||||
if (!MDNS.begin("filaman")) {
|
||||
Serial.println("Error setting up MDNS responder!");
|
||||
while(1) {
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
Serial.println("mDNS responder started");
|
||||
}
|
||||
|
||||
void configModeCallback (WiFiManager *myWiFiManager) {
|
||||
Serial.println("Entered config mode");
|
||||
oledShowTopRow();
|
||||
oledShowMessage("WiFi Config Mode");
|
||||
}
|
||||
|
||||
void initWiFi() {
|
||||
WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP
|
||||
|
||||
esp_wifi_set_max_tx_power(72); // Setze maximale Sendeleistung auf 20dBm
|
||||
|
||||
if(wm_nonblocking) wm.setConfigPortalBlocking(false);
|
||||
wm.setConfigPortalTimeout(320); // Portal nach 5min schließen
|
||||
// load Wifi settings
|
||||
wifiSettings();
|
||||
|
||||
wm.setAPCallback(configModeCallback);
|
||||
|
||||
wm.setSaveConfigCallback([]() {
|
||||
Serial.println("Configurations updated");
|
||||
ESP.restart();
|
||||
});
|
||||
|
||||
if(wm_nonblocking) wm.setConfigPortalBlocking(false);
|
||||
//wm.setConfigPortalTimeout(320); // Portal nach 5min schließen
|
||||
wm.setWiFiAutoReconnect(true);
|
||||
wm.setConnectTimeout(10);
|
||||
|
||||
oledShowTopRow();
|
||||
oledShowMessage("WiFi Setup");
|
||||
|
||||
//bool res = wm.autoConnect("FilaMan"); // anonymous ap
|
||||
if(!wm.autoConnect("FilaMan")) {
|
||||
Serial.println("Failed to connect or hit timeout");
|
||||
// ESP.restart();
|
||||
oledShowTopRow();
|
||||
oledShowMessage("WiFi Setup");
|
||||
|
||||
bool res;
|
||||
// res = wm.autoConnect(); // auto generated AP name from chipid
|
||||
res = wm.autoConnect("FilaMan"); // anonymous ap
|
||||
// res = wm.autoConnect("spoolman","password"); // password protected ap
|
||||
|
||||
if(!res) {
|
||||
Serial.println("Failed to connect or hit timeout");
|
||||
// ESP.restart();
|
||||
oledShowTopRow();
|
||||
oledShowMessage("WiFi not connected Check Portal");
|
||||
oledShowMessage("WiFi not connected Check Portal");
|
||||
}
|
||||
else {
|
||||
wifiOn = true;
|
||||
|
||||
//if you get here you have connected to the WiFi
|
||||
Serial.println("connected...yeey :)");
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
oledShowTopRow();
|
||||
display.display();
|
||||
|
||||
vTaskDelay(500 / portTICK_PERIOD_MS);
|
||||
|
||||
// mDNS
|
||||
startMDNS();
|
||||
}
|
||||
}
|
||||
|
||||
void checkWiFiConnection() {
|
||||
if (WiFi.status() != WL_CONNECTED)
|
||||
{
|
||||
Serial.println("WiFi connection lost. Reconnecting...");
|
||||
wifiOn = false;
|
||||
oledShowTopRow();
|
||||
oledShowMessage("WiFi reconnecting");
|
||||
WiFi.reconnect(); // Versuche, die Verbindung wiederherzustellen
|
||||
vTaskDelay(5000 / portTICK_PERIOD_MS); // Warte 5 Sekunden, bevor erneut geprüft wird
|
||||
if (WiFi.status() != WL_CONNECTED)
|
||||
{
|
||||
Serial.println("Failed to reconnect. Restarting WiFi...");
|
||||
WiFi.disconnect();
|
||||
Serial.println("WiFi disconnected.");
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
wifiErrorCounter++;
|
||||
|
||||
//wifiSettings();
|
||||
WiFi.reconnect();
|
||||
Serial.println("WiFi reconnecting...");
|
||||
WiFi.waitForConnectResult();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
Serial.println("WiFi reconnected.");
|
||||
wifiErrorCounter = 0;
|
||||
wifiOn = true;
|
||||
|
||||
//if you get here you have connected to the WiFi
|
||||
Serial.println("connected...yeey :)");
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
oledShowTopRow();
|
||||
display.display();
|
||||
startMDNS();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wifiErrorCounter >= 5)
|
||||
{
|
||||
Serial.println("Too many WiFi errors. Restarting...");
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
@ -4,5 +4,6 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
void initWiFi();
|
||||
void checkWiFiConnection();
|
||||
|
||||
#endif
|
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.
|