Compare commits

..

No commits in common. "main" and "v1.2.76" have entirely different histories.

71 changed files with 1276 additions and 52244 deletions

View File

@ -1,208 +0,0 @@
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

View File

@ -1,185 +0,0 @@
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

View File

@ -0,0 +1,128 @@
name: Gitea Release
on:
workflow_call:
inputs:
gitea_ref_name:
required: true
type: string
gitea_server_url:
required: true
type: string
gitea_repository:
required: true
type: string
secrets:
GITEA_TOKEN:
required: true
jobs:
create-release:
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 platformio esptool
- name: Build Firmware
run: |
# Build SPIFFS first
platformio run -t buildfs
# Then build firmware
platformio run
- name: Prepare Release Files
run: |
cd .pio/build/esp32dev
# Get bootloader
BOOTLOADER_PATH=$(find ~/.platformio -name "bootloader_dio_40m.bin" | head -n 1)
if [ ! -f "$BOOTLOADER_PATH" ]; then
echo "Error: bootloader not found!"
exit 1
fi
echo "Using bootloader from: $BOOTLOADER_PATH"
cp "$BOOTLOADER_PATH" bootloader.bin
# Create OTA update binary (firmware only)
cp firmware.bin filaman_ota.bin
# Create full binary
echo "Creating full binary..."
esptool.py --chip esp32 merge_bin \
-o filaman_full.bin \
--flash_mode dio \
--flash_freq 40m \
--flash_size 4MB \
0x1000 bootloader.bin \
0x8000 partitions.bin \
0x10000 firmware.bin \
0x3D0000 spiffs.bin
# Verify results
echo "File sizes:"
ls -l bootloader.bin partitions.bin firmware.bin spiffs.bin filaman_full.bin filaman_ota.bin
echo "Firmware info:"
esptool.py --chip esp32 image_info firmware.bin
echo "Full binary first 16 bytes:"
hexdump -C -n 16 filaman_full.bin
- name: Read CHANGELOG.md
id: changelog
run: |
VERSION=${GITHUB_REF_NAME#v}
CHANGELOG=$(awk "/## \\[$VERSION\\]/{p=1;print;next} /## \\[/{p=0} p" CHANGELOG.md)
echo "CHANGES<<EOF" >> $GITHUB_OUTPUT
echo "$CHANGELOG" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create Release
env:
TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
TAG="${{ inputs.gitea_ref_name }}"
API_URL="${{ inputs.gitea_server_url }}/api/v1"
REPO="${{ inputs.gitea_repository }}"
# Create release
RESPONSE=$(curl -k -s \
-X POST \
-H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"tag_name\":\"${TAG}\",
\"name\":\"Release ${TAG}\",
\"body\":\"${{ steps.changelog.outputs.CHANGES }}\"
}" \
"${API_URL}/repos/${REPO}/releases")
RELEASE_ID=$(echo "$RESPONSE" | grep -o '"id":[0-9]*' | cut -d':' -f2 | head -n1)
if [ -n "$RELEASE_ID" ]; then
echo "Release created with ID: $RELEASE_ID"
# Upload binaries
for file in filaman_full.bin filaman_ota.bin; do
echo "Uploading $file..."
curl -k -s \
-X POST \
-H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/octet-stream" \
--data-binary "@.pio/build/esp32dev/$file" \
"${API_URL}/repos/${REPO}/releases/${RELEASE_ID}/assets?name=$file"
done
else
echo "Failed to create release. Response:"
echo "$RESPONSE"
exit 1
fi

View File

@ -0,0 +1,71 @@
name: GitHub Release
on:
workflow_call:
jobs:
create-release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- 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
- name: Build Firmware
run: |
pio run -t buildfs # Build SPIFFS
pio run # Build firmware
- name: Install esptool
run: |
pip install esptool
- 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 \
0x3D0000 .pio/build/esp32dev/spiffs.bin
- name: Prepare OTA firmware
run: |
cp .pio/build/esp32dev/firmware.bin .pio/build/esp32dev/filaman_ota.bin
- name: Get version from tag
id: get_version
run: |
echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Read CHANGELOG.md
id: changelog
run: |
VERSION=${{ steps.get_version.outputs.VERSION }}
CHANGELOG=$(awk "/## \\[$VERSION\\]/{p=1;print;next} /## \\[/{p=0} p" CHANGELOG.md)
echo "CHANGES<<EOF" >> $GITHUB_OUTPUT
echo "$CHANGELOG" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "${{ github.ref_name }}" \
--title "Release ${{ steps.get_version.outputs.VERSION }}" \
--notes "${{ steps.changelog.outputs.CHANGES }}" \
.pio/build/esp32dev/filaman_full.bin \
.pio/build/esp32dev/filaman_ota.bin

View File

@ -5,37 +5,66 @@ on:
tags: tags:
- 'v*' - 'v*'
permissions:
contents: write
jobs: jobs:
detect-provider: route:
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
provider: ${{ steps.provider.outputs.provider }} provider: ${{ steps.provider.outputs.provider }}
gitea_ref_name: ${{ steps.provider.outputs.gitea_ref_name }}
gitea_server_url: ${{ steps.provider.outputs.gitea_server_url }}
gitea_repository: ${{ steps.provider.outputs.gitea_repository }}
steps: steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Debug Environment
run: |
echo "CI Environment Details:"
echo "GITHUB_ACTIONS=${GITHUB_ACTIONS:-not set}"
echo "GITEA_ACTIONS=${GITEA_ACTIONS:-not set}"
echo "GITEA_REPOSITORY=${GITEA_REPOSITORY:-not set}"
echo "GITEA_SERVER_URL=${GITEA_SERVER_URL:-not set}"
echo "RUNNER_NAME=${RUNNER_NAME:-not set}"
- name: Determine CI Provider - name: Determine CI Provider
id: provider id: provider
shell: bash shell: bash
run: | run: |
if [ -n "${GITEA_ACTIONS}" ] || [ -n "${GITEA_REPOSITORY}" ] || [[ "${RUNNER_NAME}" == *"gitea"* ]]; then if [ -n "${GITEA_ACTIONS}" ] || [ -n "${GITEA_REPOSITORY}" ] || [[ "${RUNNER_NAME}" == *"gitea"* ]]; then
echo "provider=gitea" >> "$GITHUB_OUTPUT" echo "provider=gitea" >> "$GITHUB_OUTPUT"
else echo "gitea_ref_name=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
echo "gitea_server_url=${GITHUB_SERVER_URL}" >> "$GITHUB_OUTPUT"
echo "gitea_repository=${GITHUB_REPOSITORY}" >> "$GITHUB_OUTPUT"
elif [ "${GITHUB_ACTIONS}" = "true" ]; then
echo "provider=github" >> "$GITHUB_OUTPUT" echo "provider=github" >> "$GITHUB_OUTPUT"
else
echo "provider=unknown" >> "$GITHUB_OUTPUT"
fi
verify-provider:
needs: route
runs-on: ubuntu-latest
steps:
- name: Echo detected provider
run: |
echo "Detected CI Provider: ${{ needs.route.outputs.provider }}"
if [ "${{ needs.route.outputs.provider }}" = "unknown" ]; then
echo "::error::Failed to detect CI provider!"
exit 1
fi fi
github-release: github-release:
needs: detect-provider needs: [route, verify-provider]
permissions: if: needs.route.outputs.provider == 'github'
contents: write uses: ./.github/workflows/providers/github-release.yml
if: needs.detect-provider.outputs.provider == 'github'
uses: ./.github/workflows/github-release.yml
secrets:
RELEASE_TOKEN: ${{ secrets.GITHUB_TOKEN }}
gitea-release: gitea-release:
needs: detect-provider needs: [route, verify-provider]
if: needs.detect-provider.outputs.provider == 'gitea' if: needs.route.outputs.provider == 'gitea'
uses: ./.github/workflows/gitea-release.yml uses: ./.github/workflows/providers/gitea-release.yml
with:
gitea_ref_name: ${{ needs.route.outputs.gitea_ref_name }}
gitea_server_url: ${{ needs.route.outputs.gitea_server_url }}
gitea_repository: ${{ needs.route.outputs.gitea_repository }}
secrets: secrets:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}

54
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,54 @@
{
"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"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -9,9 +9,6 @@ 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) 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 ### ESP32 Hardware-Funktionen
- **Gewichtsmessung:** Verwendung einer Wägezelle mit HX711-Verstärker für präzise Gewichtsverfolgung. - **Gewichtsmessung:** Verwendung einer Wägezelle mit HX711-Verstärker für präzise Gewichtsverfolgung.
@ -19,7 +16,7 @@ Discord Server: [https://discord.gg/my7Gvaxj2v](https://discord.gg/my7Gvaxj2v)
- **OLED-Display:** Zeigt aktuelles Gewicht, Verbindungsstatus (WiFi, Bambu Lab, Spoolman). - **OLED-Display:** Zeigt aktuelles Gewicht, Verbindungsstatus (WiFi, Bambu Lab, Spoolman).
- **WLAN-Konnektivität:** WiFiManager für einfache Netzwerkkonfiguration. - **WLAN-Konnektivität:** WiFiManager für einfache Netzwerkkonfiguration.
- **MQTT-Integration:** Verbindet sich mit Bambu Lab Drucker für AMS-Steuerung. - **MQTT-Integration:** Verbindet sich mit Bambu Lab Drucker für AMS-Steuerung.
- **NFC-Tag NTAG213 NTAG215:** Verwendung von NTAG213, besser NTAG215 wegen ausreichendem Speicherplatz auf dem Tag - **NFC-Tag NTAG215:** Verwendung von NTAG215 wegen ausreichendem Speicherplatz auf dem Tag
### Weboberflächen-Funktionen ### Weboberflächen-Funktionen
- **Echtzeit-Updates:** WebSocket-Verbindung für Live-Daten-Updates. - **Echtzeit-Updates:** WebSocket-Verbindung für Live-Daten-Updates.
@ -36,7 +33,6 @@ Discord Server: [https://discord.gg/my7Gvaxj2v](https://discord.gg/my7Gvaxj2v)
- Filtern und Auswählen von Filamenten. - Filtern und Auswählen von Filamenten.
- Automatische Aktualisierung der Spulengewichte. - Automatische Aktualisierung der Spulengewichte.
- Verfolgung von NFC-Tag-Zuweisungen. - 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 ### 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> <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,22 +51,19 @@ Discord Server: [https://discord.gg/my7Gvaxj2v](https://discord.gg/my7Gvaxj2v)
## Hardware-Anforderungen ## Hardware-Anforderungen
### Komponenten ### Komponenten
- **ESP32 Development Board:** Any ESP32 variant. - **ESP32 Entwicklungsboard:** Jede ESP32-Variante.
[Amazon Link](https://amzn.to/3FHea6D) [Amazon Link](https://amzn.eu/d/aXThslf)
- **HX711 5kg Load Cell Amplifier:** For weight measurement. - **HX711 Wägezellen-Verstärker:** Für Gewichtsmessung.
[Amazon Link](https://amzn.to/4ja1KTe) [Amazon Link](https://amzn.eu/d/1wZ4v0x)
- **OLED 0.96 Zoll I2C white/yellow Display:** 128x64 SSD1306. - **OLED Display:** 128x64 SSD1306.
[Amazon Link](https://amzn.to/445aaa9) [Amazon Link](https://amzn.eu/d/dozAYDU)
- **PN532 NFC NXP RFID-Modul V3:** For NFC tag operations. - **PN532 NFC Modul:** Für NFC-Tag-Operationen.
[Amazon Link](https://amzn.to/4iO6CO4) [Amazon Link](https://amzn.eu/d/8205DDh)
- **NFC Tags NTAG213 NTAG215:** RFID Tag - **NFC-Tag:** NTAG215
[Amazon Link](https://amzn.to/3E071xO) [Amazon Link](https://amzn.eu/d/fywy4c4)
- **TTP223 Touch Sensor (optional):** For reTARE per Button/Touch
[Amazon Link](https://amzn.to/4hTChMK)
### Pin-Konfiguration
### Pin Konfiguration | Komponente | ESP32 Pin |
| Component | ESP32 Pin |
|-------------------|-----------| |-------------------|-----------|
| HX711 DOUT | 16 | | HX711 DOUT | 16 |
| HX711 SCK | 17 | | HX711 SCK | 17 |
@ -78,17 +71,10 @@ Discord Server: [https://discord.gg/my7Gvaxj2v](https://discord.gg/my7Gvaxj2v)
| OLED SCL | 22 | | OLED SCL | 22 |
| PN532 IRQ | 32 | | PN532 IRQ | 32 |
| PN532 RESET | 33 | | PN532 RESET | 33 |
| PN532 SDA | 21 | | PN532 SCK | 14 |
| PN532 SCL | 22 | | PN532 MOSI | 13 |
| TTP223 I/O | 25 | | PN532 MISO | 12 |
| PN532 CS/SS | 15 |
**!! Achte darauf, dass am PN532 die DIP-Schalter auf I2C gestellt sind**
**Nutze den 3V Pin vom ESP für den Touch Sensor**
![Wiring](./img/Schaltplan.png)
![myWiring](./img/IMG_2589.jpeg)
![myWiring](./img/IMG_2590.jpeg)
## Software-Abhängigkeiten ## Software-Abhängigkeiten
@ -115,31 +101,7 @@ Discord Server: [https://discord.gg/my7Gvaxj2v](https://discord.gg/my7Gvaxj2v)
- PN532 NFC Modul - PN532 NFC Modul
- Verbindungskabel - Verbindungskabel
## Wichtiger Hinweis ### Schritt-für-Schritt Installation
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:** 1. **Repository klonen:**
```bash ```bash
git clone https://github.com/ManuelW77/Filaman.git git clone https://github.com/ManuelW77/Filaman.git

View File

@ -6,16 +6,12 @@ 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. Users can manage filament spools, monitor the status of the Automatic Material System (AMS) and make settings via a web interface.
The system integrates seamlessly with [Bambulab](https://bambulab.com/en-us) 3D printers and [Spoolman](https://github.com/Donkie/Spoolman) filament management as well as the [Openspool](https://github.com/spuder/OpenSpool) NFC-TAG format. The system integrates seamlessly with [Bambulab](https://bambulab.com/en-us) 3D printers and [Spoolman](https://github.com/Donkie/Spoolman) filament management as well as the [Openspool](https://github.com/spuder/OpenSpool) NFC-TAG format.
![Scale](./img/scale_trans.png) ![Scale](./img/scale_trans.png)
More Images can be found in the [img Folder](/img/) More Images can be found in the [img Folder](/img/)
or my website:[FilaMan Website](https://www.filaman.app) or my website:[FilaMan Website](https://www.filaman.app)
german explanatory video: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaOHU) 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 ### ESP32 Hardware Features
- **Weight Measurement:** Using a load cell with HX711 amplifier for precise weight tracking. - **Weight Measurement:** Using a load cell with HX711 amplifier for precise weight tracking.
@ -23,7 +19,7 @@ Discord Server: [https://discord.gg/my7Gvaxj2v](https://discord.gg/my7Gvaxj2v)
- **OLED Display:** Shows current weight, connection status (WiFi, Bambu Lab, Spoolman). - **OLED Display:** Shows current weight, connection status (WiFi, Bambu Lab, Spoolman).
- **WiFi Connectivity:** WiFiManager for easy network configuration. - **WiFi Connectivity:** WiFiManager for easy network configuration.
- **MQTT Integration:** Connects to Bambu Lab printer for AMS control. - **MQTT Integration:** Connects to Bambu Lab printer for AMS control.
- **NFC-Tag NTAG213 NTAG215:** Use NTAG213, better NTAG215 because of enaught space on the Tag - **NFC-Tag NTAG215:** Use NTAG215 because of enaught space on the Tag
### Web Interface Features ### Web Interface Features
- **Real-time Updates:** WebSocket connection for live data updates. - **Real-time Updates:** WebSocket connection for live data updates.
@ -40,7 +36,6 @@ Discord Server: [https://discord.gg/my7Gvaxj2v](https://discord.gg/my7Gvaxj2v)
- Filter and select filaments. - Filter and select filaments.
- Update spool weights automatically. - Update spool weights automatically.
- Track NFC tag assignments. - Track NFC tag assignments.
- Supports Spoolman Octoprint Plugin
### If you want to support my work, i would be happy to get a coffe ### 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> <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>
@ -60,17 +55,15 @@ Discord Server: [https://discord.gg/my7Gvaxj2v](https://discord.gg/my7Gvaxj2v)
### Components ### Components
- **ESP32 Development Board:** Any ESP32 variant. - **ESP32 Development Board:** Any ESP32 variant.
[Amazon Link](https://amzn.to/3FHea6D) [Amazon Link](https://amzn.eu/d/aXThslf)
- **HX711 5kg Load Cell Amplifier:** For weight measurement. - **HX711 Load Cell Amplifier:** For weight measurement.
[Amazon Link](https://amzn.to/4ja1KTe) [Amazon Link](https://amzn.eu/d/1wZ4v0x)
- **OLED 0.96 Zoll I2C white/yellow Display:** 128x64 SSD1306. - **OLED Display:** 128x64 SSD1306.
[Amazon Link](https://amzn.to/445aaa9) [Amazon Link](https://amzn.eu/d/dozAYDU)
- **PN532 NFC NXP RFID-Modul V3:** For NFC tag operations. - **PN532 NFC Module:** For NFC tag operations.
[Amazon Link](https://amzn.to/4iO6CO4) [Amazon Link](https://amzn.eu/d/8205DDh)
- **NFC Tags NTAG213 NTAG215:** RFID Tag - **NFC-Tag:** NTAG215
[Amazon Link](https://amzn.to/3E071xO) [Amazon Link](https://amzn.eu/d/fywy4c4)
- **TTP223 Touch Sensor (optional):** For reTARE per Button/Touch
[Amazon Link](https://amzn.to/4hTChMK)
### Pin Configuration ### Pin Configuration
@ -82,17 +75,10 @@ Discord Server: [https://discord.gg/my7Gvaxj2v](https://discord.gg/my7Gvaxj2v)
| OLED SCL | 22 | | OLED SCL | 22 |
| PN532 IRQ | 32 | | PN532 IRQ | 32 |
| PN532 RESET | 33 | | PN532 RESET | 33 |
| PN532 SDA | 21 | | PN532 SCK | 14 |
| PN532 SCL | 22 | | PN532 MOSI | 13 |
| TTP223 I/O | 25 | | PN532 MISO | 12 |
| PN532 CS/SS | 15 |
**!! Make sure that the DIP switches on the PN532 are set to I2C**
**Use the 3V pin from the ESP for the touch sensor**
![Wiring](./img/Schaltplan.png)
![myWiring](./img/IMG_2589.jpeg)
![myWiring](./img/IMG_2590.jpeg)
## Software Dependencies ## Software Dependencies
@ -105,9 +91,9 @@ Discord Server: [https://discord.gg/my7Gvaxj2v](https://discord.gg/my7Gvaxj2v)
- `Adafruit_SSD1306`: OLED display control - `Adafruit_SSD1306`: OLED display control
- `HX711`: Load cell communication - `HX711`: Load cell communication
### Installation ## Installation
## Prerequisites ### Prerequisites
- **Software:** - **Software:**
- [PlatformIO](https://platformio.org/) in VS Code - [PlatformIO](https://platformio.org/) in VS Code
- [Spoolman](https://github.com/Donkie/Spoolman) instance - [Spoolman](https://github.com/Donkie/Spoolman) instance
@ -119,32 +105,7 @@ Discord Server: [https://discord.gg/my7Gvaxj2v](https://discord.gg/my7Gvaxj2v)
- PN532 NFC Module - PN532 NFC Module
- Connecting wires - Connecting wires
## Important Note ### Step-by-Step Installation
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:** 1. **Clone the Repository:**
```bash ```bash
git clone https://github.com/ManuelW77/Filaman.git git clone https://github.com/ManuelW77/Filaman.git
@ -163,6 +124,25 @@ You have to activate Spoolman in debug mode, because you are not able to set COR
- Configure WiFi settings through the captive portal. - Configure WiFi settings through the captive portal.
- Access the web interface at `http://filaman.local` or the IP address. - 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 ## Documentation
### Relevant Links ### Relevant Links

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -1,31 +1,7 @@
{ {
"GFU99": "TPU", "GFU99": "Generic TPU",
"GFN99": "PA", "GFN99": "Generic PA",
"GFN98": "PA-CF", "GFN98": "Generic 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", "GFA01": "Bambu PLA Matte",
"GFA00": "Bambu PLA Basic", "GFA00": "Bambu PLA Basic",
"GFA09": "Bambu PLA Tough", "GFA09": "Bambu PLA Tough",
@ -37,11 +13,15 @@
"GFL03": "eSUN PLA+", "GFL03": "eSUN PLA+",
"GFL01": "PolyTerra PLA", "GFL01": "PolyTerra PLA",
"GFL00": "PolyLite PLA", "GFL00": "PolyLite PLA",
"GFL99": "Generic PLA",
"GFL96": "Generic PLA Silk",
"GFL98": "Generic PLA-CF",
"GFA50": "Bambu PLA-CF", "GFA50": "Bambu PLA-CF",
"GFS02": "Bambu Support For PLA", "GFS02": "Bambu Support For PLA",
"GFA11": "Bambu PLA Aero", "GFA11": "Bambu PLA Aero",
"GFL04": "Overture PLA", "GFL04": "Overture PLA",
"GFL05": "Overture Matte PLA", "GFL05": "Overture Matte PLA",
"GFL95": "Generic PLA High Speed",
"GFA12": "Bambu PLA Glow", "GFA12": "Bambu PLA Glow",
"GFA13": "Bambu PLA Dynamic", "GFA13": "Bambu PLA Dynamic",
"GFA15": "Bambu PLA Galaxy", "GFA15": "Bambu PLA Galaxy",
@ -50,21 +30,41 @@
"GFU00": "Bambu TPU 95A HF", "GFU00": "Bambu TPU 95A HF",
"GFG00": "Bambu PETG Basic", "GFG00": "Bambu PETG Basic",
"GFT01": "Bambu PET-CF", "GFT01": "Bambu PET-CF",
"GFG99": "Generic PETG",
"GFG98": "Generic PETG-CF",
"GFG50": "Bambu PETG-CF", "GFG50": "Bambu PETG-CF",
"GFG60": "PolyLite PETG", "GFG60": "PolyLite PETG",
"GFG01": "Bambu PETG Translucent", "GFG01": "Bambu PETG Translucent",
"GFG97": "Generic PCTG",
"GFB00": "Bambu ABS", "GFB00": "Bambu ABS",
"GFB99": "Generic ABS",
"GFB60": "PolyLite ABS", "GFB60": "PolyLite ABS",
"GFB50": "Bambu ABS-GF", "GFB50": "Bambu ABS-GF",
"GFC00": "Bambu PC", "GFC00": "Bambu PC",
"GFC99": "Generic PC",
"GFB98": "Generic ASA",
"GFB01": "Bambu ASA", "GFB01": "Bambu ASA",
"GFB61": "PolyLite ASA", "GFB61": "PolyLite ASA",
"GFB02": "Bambu ASA-Aero", "GFB02": "Bambu ASA-Aero",
"GFS99": "Generic PVA",
"GFS04": "Bambu PVA", "GFS04": "Bambu PVA",
"GFS01": "Bambu Support G", "GFS01": "Bambu Support G",
"GFN03": "Bambu PA-CF", "GFN03": "Bambu PA-CF",
"GFN04": "Bambu PAHT-CF", "GFN04": "Bambu PAHT-CF",
"GFS03": "Bambu Support For PA/PET", "GFS03": "Bambu Support For PA/PET",
"GFN05": "Bambu PA6-CF", "GFN05": "Bambu PA6-CF",
"GFN08": "Bambu PA6-GF" "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"
} }

View File

@ -6,24 +6,13 @@
<title>FilaMan - Filament Management Tool</title> <title>FilaMan - Filament Management Tool</title>
<link rel="icon" type="image/png" href="/favicon.ico"> <link rel="icon" type="image/png" href="/favicon.ico">
<link rel="stylesheet" href="style.css"> <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> </head>
<body> <body>
<div class="navbar"> <div class="navbar">
<div style="display: flex; align-items: center; gap: 2rem;"> <div style="display: flex; align-items: center; gap: 2rem;">
<img src="/logo.png" alt="FilaMan Logo" class="logo"> <img src="/logo.png" alt="FilaMan Logo" class="logo">
<div class="logo-text"> <div class="logo-text">
<h1>FilaMan<span class="version"></span></h1> <h1>FilaMan<span class="version">v1.2.76</span></h1>
<h4>Filament Management Tool</h4> <h4>Filament Management Tool</h4>
</div> </div>
</div> </div>

View File

@ -6,24 +6,13 @@
<title>FilaMan - Filament Management Tool</title> <title>FilaMan - Filament Management Tool</title>
<link rel="icon" type="image/png" href="/favicon.ico"> <link rel="icon" type="image/png" href="/favicon.ico">
<link rel="stylesheet" href="style.css"> <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> </head>
<body> <body>
<div class="navbar"> <div class="navbar">
<div style="display: flex; align-items: center; gap: 2rem;"> <div style="display: flex; align-items: center; gap: 2rem;">
<img src="/logo.png" alt="FilaMan Logo" class="logo"> <img src="/logo.png" alt="FilaMan Logo" class="logo">
<div class="logo-text"> <div class="logo-text">
<h1>FilaMan<span class="version"></span></h1> <h1>FilaMan<span class="version">v1.2.76</span></h1>
<h4>Filament Management Tool</h4> <h4>Filament Management Tool</h4>
</div> </div>
</div> </div>
@ -47,7 +36,7 @@
<!-- head --> <!-- head -->
<div class="content"> <div class="container">
<h1>FilaMan</h1> <h1>FilaMan</h1>
<p>Filament Management Tool</p> <p>Filament Management Tool</p>
<p>Your smart solution for <strong>Filament Management</strong> in 3D printing.</p> <p>Your smart solution for <strong>Filament Management</strong> in 3D printing.</p>
@ -55,11 +44,10 @@
<h2>About FilaMan</h2> <h2>About FilaMan</h2>
<p> <p>
FilaMan is a tool designed to simplify filament spool management. It allows you to identify and weigh filament spools, 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. 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.
</p> </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="features">
<div class="feature"> <div class="feature">
<h3>Spool Identification</h3> <h3>Spool Identification</h3>
@ -74,6 +62,12 @@
<p>Works with OpenSpool to recognize and activate spools on Bambu printers.</p> <p>Works with OpenSpool to recognize and activate spools on Bambu printers.</p>
</div> </div>
</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> </div>
</body> </body>
</html> </html>

View File

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

View File

@ -6,24 +6,13 @@
<title>FilaMan - Filament Management Tool</title> <title>FilaMan - Filament Management Tool</title>
<link rel="icon" type="image/png" href="/favicon.ico"> <link rel="icon" type="image/png" href="/favicon.ico">
<link rel="stylesheet" href="style.css"> <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> </head>
<body> <body>
<div class="navbar"> <div class="navbar">
<div style="display: flex; align-items: center; gap: 2rem;"> <div style="display: flex; align-items: center; gap: 2rem;">
<img src="/logo.png" alt="FilaMan Logo" class="logo"> <img src="/logo.png" alt="FilaMan Logo" class="logo">
<div class="logo-text"> <div class="logo-text">
<h1>FilaMan<span class="version"></span></h1> <h1>FilaMan<span class="version">v1.2.76</span></h1>
<h4>Filament Management Tool</h4> <h4>Filament Management Tool</h4>
</div> </div>
</div> </div>
@ -141,6 +130,15 @@
</div> </div>
</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>
</div> </div>

View File

@ -150,13 +150,6 @@ function initWebSocket() {
ramStatus.textContent = `${data.freeHeap}k`; 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) { } catch (error) {
isConnected = false; isConnected = false;
@ -292,14 +285,6 @@ function displayAmsData(amsData) {
<img src="spool_in.png" alt="Spool In" style="width: 48px; height: 48px; transform: rotate(180deg) scaleX(-1);"> <img src="spool_in.png" alt="Spool In" style="width: 48px; height: 48px; transform: rotate(180deg) scaleX(-1);">
</button>`; </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) { if (!hasAnyContent) {
return ` return `
<div class="tray"> <div class="tray">
@ -363,7 +348,6 @@ function displayAmsData(amsData) {
${trayDetails} ${trayDetails}
${tempHTML} ${tempHTML}
${(ams.ams_id === 255 && tray.tray_type !== '') ? outButtonHtml : ''} ${(ams.ams_id === 255 && tray.tray_type !== '') ? outButtonHtml : ''}
${(tray.setting_id != "" && tray.setting_id != "null") ? spoolmanButtonHtml : ''}
</div> </div>
</div>`; </div>`;
@ -389,36 +373,6 @@ 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() { function handleSpoolOut() {
// Erstelle Payload // Erstelle Payload
const payload = { const payload = {
@ -490,7 +444,7 @@ function handleSpoolIn(amsId, trayId) {
nozzle_temp_max: parseInt(maxTemp), nozzle_temp_max: parseInt(maxTemp),
type: selectedSpool.filament.material, type: selectedSpool.filament.material,
brand: selectedSpool.filament.vendor.name, brand: selectedSpool.filament.vendor.name,
tray_info_idx: selectedSpool.filament.extra.bambu_idx?.replace(/['"]+/g, '').trim() || '', tray_info_idx: selectedSpool.filament.extra.bambu_idx.replace(/['"]+/g, '').trim(),
cali_idx: "-1" // Default-Wert setzen cali_idx: "-1" // Default-Wert setzen
} }
}; };
@ -640,6 +594,8 @@ function writeNfcTag() {
// Erstelle das NFC-Datenpaket mit korrekten Datentypen // Erstelle das NFC-Datenpaket mit korrekten Datentypen
const nfcData = { const nfcData = {
version: "2.0",
protocol: "openspool",
color_hex: selectedSpool.filament.color_hex || "FFFFFF", color_hex: selectedSpool.filament.color_hex || "FFFFFF",
type: selectedSpool.filament.material, type: selectedSpool.filament.material,
min_temp: minTemp, min_temp: minTemp,

View File

@ -1,160 +0,0 @@
<!-- 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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -6,24 +6,13 @@
<title>FilaMan - Filament Management Tool</title> <title>FilaMan - Filament Management Tool</title>
<link rel="icon" type="image/png" href="/favicon.ico"> <link rel="icon" type="image/png" href="/favicon.ico">
<link rel="stylesheet" href="style.css"> <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> </head>
<body> <body>
<div class="navbar"> <div class="navbar">
<div style="display: flex; align-items: center; gap: 2rem;"> <div style="display: flex; align-items: center; gap: 2rem;">
<img src="/logo.png" alt="FilaMan Logo" class="logo"> <img src="/logo.png" alt="FilaMan Logo" class="logo">
<div class="logo-text"> <div class="logo-text">
<h1>FilaMan<span class="version"></span></h1> <h1>FilaMan<span class="version">v1.2.76</span></h1>
<h4>Filament Management Tool</h4> <h4>Filament Management Tool</h4>
</div> </div>
</div> </div>
@ -52,43 +41,11 @@
if (spoolmanUrl && spoolmanUrl.trim() !== "") { if (spoolmanUrl && spoolmanUrl.trim() !== "") {
document.getElementById('spoolmanUrl').value = spoolmanUrl; 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() { function checkSpoolmanInstance() {
const url = document.getElementById('spoolmanUrl').value; const url = document.getElementById('spoolmanUrl').value;
const spoolmanOctoEnabled = document.getElementById('spoolmanOctoEnabled').checked; fetch(`/api/checkSpoolman?url=${encodeURIComponent(url)}`)
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(response => response.json())
.then(data => { .then(data => {
if (data.healthy) { if (data.healthy) {
@ -106,19 +63,12 @@
const ip = document.getElementById('bambuIp').value; const ip = document.getElementById('bambuIp').value;
const serial = document.getElementById('bambuSerial').value; const serial = document.getElementById('bambuSerial').value;
const code = document.getElementById('bambuCode').value; const code = document.getElementById('bambuCode').value;
const autoSend = document.getElementById('autoSend').checked;
const autoSendTime = document.getElementById('autoSendTime').value;
fetch(`/api/bambu?bambu_ip=${encodeURIComponent(ip)}&bambu_serialnr=${encodeURIComponent(serial)}&bambu_accesscode=${encodeURIComponent(code)}&autoSend=${autoSend}&autoSendTime=${autoSendTime}`) fetch(`/api/bambu?bambu_ip=${encodeURIComponent(ip)}&bambu_serialnr=${encodeURIComponent(serial)}&bambu_accesscode=${encodeURIComponent(code)}`)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.healthy) { if (data.healthy) {
document.getElementById('bambuStatusMessage').innerText = 'Bambu Credentials saved!'; 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 { } else {
document.getElementById('bambuStatusMessage').innerText = 'Error while saving Bambu Credentials.'; document.getElementById('bambuStatusMessage').innerText = 'Error while saving Bambu Credentials.';
} }
@ -127,15 +77,6 @@
document.getElementById('bambuStatusMessage').innerText = 'Error while saving: ' + error.message; 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>
<script> <script>
var spoolmanUrl = "{{spoolmanUrl}}"; var spoolmanUrl = "{{spoolmanUrl}}";
@ -143,30 +84,12 @@
<div class="content"> <div class="content">
<h1>Spoolman API URL / Bambu Credentials</h1> <h1>Spoolman API URL / Bambu Credentials</h1>
<label for="spoolmanUrl">Set URL/IP to your Spoolman-Instanz:</label>
<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"> <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> <button onclick="checkSpoolmanInstance()">Save Spoolman URL</button>
<p id="statusMessage"></p> <p id="statusMessage"></p>
</div>
</div>
<div class="card"> <h2>Bambu Lab Printer Credentials</h2>
<div class="card-body">
<h5 class="card-title">Bambu Lab Printer Credentials</h5>
<div class="bambu-settings"> <div class="bambu-settings">
<div class="input-group"> <div class="input-group">
<label for="bambuIp">Bambu Drucker IP-Adresse:</label> <label for="bambuIp">Bambu Drucker IP-Adresse:</label>
@ -180,23 +103,9 @@
<label for="bambuCode">Access Code:</label> <label for="bambuCode">Access Code:</label>
<input type="text" id="bambuCode" placeholder="Access Code vom Drucker" value="{{bambuCode}}"> <input type="text" id="bambuCode" placeholder="Access Code vom Drucker" value="{{bambuCode}}">
</div> </div>
<hr> <button onclick="saveBambuCredentials()">Save Bambu Credentials</button>
<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> <p id="bambuStatusMessage"></p>
</div> </div>
</div> </div>
</div>
</div>
</body> </body>
</html> </html>

View File

@ -86,10 +86,10 @@ function populateVendorDropdown(data, selectedSmId = null) {
}); });
// Nach der Schleife: Formatierung der Gesamtlänge // Nach der Schleife: Formatierung der Gesamtlänge
const lengthInM = totalLength / 1000; // erst in m umrechnen console.log("Total Lenght: ", totalLength);
const formattedLength = lengthInM > 1000 const formattedLength = totalLength > 1000
? (lengthInM / 1000).toFixed(2) + " km" ? (totalLength / 1000).toFixed(2) + " km"
: lengthInM.toFixed(2) + " m"; : totalLength.toFixed(2) + " m";
// Formatierung des Gesamtgewichts (von g zu kg zu t) // Formatierung des Gesamtgewichts (von g zu kg zu t)
const weightInKg = totalWeight / 1000; // erst in kg umrechnen const weightInKg = totalWeight / 1000; // erst in kg umrechnen
@ -97,10 +97,8 @@ function populateVendorDropdown(data, selectedSmId = null) {
? (weightInKg / 1000).toFixed(2) + " t" ? (weightInKg / 1000).toFixed(2) + " t"
: weightInKg.toFixed(2) + " kg"; : weightInKg.toFixed(2) + " kg";
// Dropdown mit gefilterten Herstellern befüllen - alphabetisch sortiert // Dropdown mit gefilterten Herstellern befüllen
Object.entries(filteredVendors) Object.entries(filteredVendors).forEach(([id, name]) => {
.sort(([, nameA], [, nameB]) => nameA.localeCompare(nameB)) // Sort vendors alphabetically by name
.forEach(([id, name]) => {
const option = document.createElement("option"); const option = document.createElement("option");
option.value = id; option.value = id;
option.textContent = name; option.textContent = name;
@ -147,13 +145,6 @@ function updateFilamentDropdown(selectedSmId = null) {
if (vendorId) { if (vendorId) {
const filteredFilaments = spoolsData.filter(spool => { 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 && const hasValidNfcId = spool.extra &&
spool.extra.nfc_id && spool.extra.nfc_id &&
spool.extra.nfc_id !== '""' && spool.extra.nfc_id !== '""' &&
@ -247,6 +238,18 @@ 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 // Event Listener
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
initSpoolman(); initSpoolman();

View File

@ -188,18 +188,14 @@ label {
font-weight: bold; font-weight: bold;
} }
input[type="text"], input[type="submit"], input[type="number"] { input[type="text"], input[type="submit"] {
padding: 10px; padding: 10px;
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 5px; border-radius: 5px;
font-size: 16px; font-size: 16px;
} }
input[type="number"] { input[type="text"]:focus {
width: 108px !important;
}
input[type="text"]:focus, input[type="number"]:focus {
border-color: #007bff; border-color: #007bff;
outline: none; outline: none;
} }
@ -283,10 +279,9 @@ a:hover {
/* Karten-Stil für optische Trennung */ /* Karten-Stil für optische Trennung */
.card { .card {
background: var(--primary-color); background: #f9f9f9;
width: 500px;
padding: 15px; padding: 15px;
margin: 20px auto; margin: 20px 0;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
} }
@ -765,19 +760,17 @@ a:hover {
right: 20px; right: 20px;
padding: 15px 25px; padding: 15px 25px;
border-radius: 4px; border-radius: 4px;
color: black; color: white;
z-index: 1000; z-index: 1000;
animation: slideIn 0.3s ease-out; animation: slideIn 0.3s ease-out;
} }
.notification.success { .notification.success {
background-color: #28a745; background-color: #28a745;
color: black !important;
} }
.notification.error { .notification.error {
background-color: #dc3545; background-color: #dc3545;
color: white !important;
} }
.notification.fade-out { .notification.fade-out {
@ -966,6 +959,7 @@ input[type="submit"]:disabled,
/* Bambu Settings Erweiterung */ /* Bambu Settings Erweiterung */
.bambu-settings { .bambu-settings {
background: white;
padding: 20px; padding: 20px;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
@ -1019,7 +1013,6 @@ input[type="submit"]:disabled,
color: #000; color: #000;
vertical-align: middle; vertical-align: middle;
margin-left: 0.5rem; margin-left: 0.5rem;
text-shadow: none !important;
} }
.progress-container { .progress-container {
@ -1058,10 +1051,9 @@ input[type="submit"]:disabled,
} }
.update-form { .update-form {
background: var(--primary-color); background: var(--primary-color);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
border: var(--glass-border);
padding: 20px; padding: 20px;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin: 0 auto; margin: 0 auto;
width: 400px; width: 400px;
text-align: center; text-align: center;
@ -1072,7 +1064,7 @@ input[type="submit"]:disabled,
padding: 8px; padding: 8px;
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 4px; border-radius: 4px;
background-color: #4CAF50; background: white;
} }
.update-form input[type="submit"] { .update-form input[type="submit"] {
background-color: #4CAF50; background-color: #4CAF50;
@ -1094,66 +1086,10 @@ input[type="submit"]:disabled,
.warning { .warning {
background-color: var(--primary-color); background-color: var(--primary-color);
border: 1px solid #ffe0b2; border: 1px solid #ffe0b2;
color: white;
padding: 15px;
margin: 20px auto; margin: 20px auto;
border-radius: 4px; border-radius: 4px;
max-width: 600px; max-width: 600px;
text-align: center; 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;
} }

View File

@ -6,24 +6,13 @@
<title>FilaMan - Filament Management Tool</title> <title>FilaMan - Filament Management Tool</title>
<link rel="icon" type="image/png" href="/favicon.ico"> <link rel="icon" type="image/png" href="/favicon.ico">
<link rel="stylesheet" href="style.css"> <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> </head>
<body> <body>
<div class="navbar"> <div class="navbar">
<div style="display: flex; align-items: center; gap: 2rem;"> <div style="display: flex; align-items: center; gap: 2rem;">
<img src="/logo.png" alt="FilaMan Logo" class="logo"> <img src="/logo.png" alt="FilaMan Logo" class="logo">
<div class="logo-text"> <div class="logo-text">
<h1>FilaMan<span class="version"></span></h1> <h1>FilaMan<span class="version">v1.2.76</span></h1>
<h4>Filament Management Tool</h4> <h4>Filament Management Tool</h4>
</div> </div>
</div> </div>
@ -51,34 +40,18 @@
<h1>Firmware Upgrade</h1> <h1>Firmware Upgrade</h1>
<div class="warning"> <div class="warning">
<strong>Warning:</strong> Do not power off the device during update. <strong>Warning:</strong> Please do not turn off or restart the device during the update.
The device will restart automatically after the update.
</div> </div>
<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"> <div class="update-form">
<form id="firmwareForm" enctype='multipart/form-data' data-type="firmware"> <form id="updateForm" enctype='multipart/form-data'>
<input type='file' name='update' accept='.bin' required> <input type='file' name='update' accept='.bin' required>
<input type='submit' value='Start Firmware Update'> <input type='submit' value='Start Firmware Update'>
</form> </form>
</div> </div>
</div>
<div class="update-section"> <div class="progress-container">
<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" style="display: none;">
<div class="progress-bar">0%</div> <div class="progress-bar">0%</div>
</div> </div>
<div class="status"></div> <div class="status"></div>
@ -91,165 +64,91 @@
statusContainer.style.display = 'none'; statusContainer.style.display = 'none';
} }
const progress = document.querySelector('.progress-bar'); document.getElementById('updateForm').addEventListener('submit', async (e) => {
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(); e.preventDefault();
const form = e.target; const form = e.target;
const file = form.update.files[0]; const file = form.update.files[0];
const updateType = form.dataset.type;
if (!file) { if (!file) {
alert('Please select a file.'); alert('Please select a firmware file.');
return; return;
} }
// Validate file name pattern const formData = new FormData();
if (updateType === 'firmware' && !file.name.startsWith('upgrade_filaman_firmware_')) { formData.append('update', file);
alert('Please select a valid firmware file (upgrade_filaman_firmware_*.bin)');
return; const progress = document.querySelector('.progress-bar');
} const progressContainer = document.querySelector('.progress-container');
if (updateType === 'webpage' && !file.name.startsWith('upgrade_filaman_website_')) { const status = document.querySelector('.status');
alert('Please select a valid webpage file (upgrade_filaman_website_*.bin)');
return;
}
// Reset UI
updateInProgress = true;
progressContainer.style.display = 'block'; progressContainer.style.display = 'block';
status.style.display = 'none'; status.style.display = 'none';
status.className = 'status'; status.className = 'status';
progress.style.width = '0%'; form.querySelector('input[type=submit]').disabled = true;
progress.textContent = '0%';
// Disable submit buttons
document.querySelectorAll('form input[type=submit]').forEach(btn => btn.disabled = true);
// Send update
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open('POST', '/update', true); 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() { xhr.onload = function() {
if (xhr.status !== 200 && !progress.textContent.startsWith('100')) { try {
status.textContent = "Update failed: " + (xhr.responseText || "Unknown error"); let response = this.responseText;
status.className = 'status error'; 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');
status.style.display = 'block'; status.style.display = 'block';
updateInProgress = false;
document.querySelectorAll('form input[type=submit]').forEach(btn => btn.disabled = false); if (xhr.status !== 200) {
form.querySelector('input[type=submit]').disabled = false;
}
} catch (error) {
status.textContent = 'Error: ' + error.message;
status.classList.add('error');
status.style.display = 'block';
form.querySelector('input[type=submit]').disabled = false;
} }
}; };
xhr.onerror = function() { xhr.onerror = function() {
if (!progress.textContent.startsWith('100')) { status.textContent = 'Update failed: Network error';
status.textContent = "Network error during update"; status.classList.add('error');
status.className = 'status error';
status.style.display = 'block'; status.style.display = 'block';
updateInProgress = false; form.querySelector('input[type=submit]').disabled = false;
document.querySelectorAll('form input[type=submit]').forEach(btn => btn.disabled = false);
}
}; };
const formData = new FormData();
formData.append('update', file);
xhr.send(formData); xhr.send(formData);
} });
document.getElementById('firmwareForm').addEventListener('submit', handleUpdate);
document.getElementById('webpageForm').addEventListener('submit', handleUpdate);
</script> </script>
</body> </body>
</html> </html>

View File

@ -6,24 +6,13 @@
<title>FilaMan - Filament Management Tool</title> <title>FilaMan - Filament Management Tool</title>
<link rel="icon" type="image/png" href="/favicon.ico"> <link rel="icon" type="image/png" href="/favicon.ico">
<link rel="stylesheet" href="style.css"> <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> </head>
<body> <body>
<div class="navbar"> <div class="navbar">
<div style="display: flex; align-items: center; gap: 2rem;"> <div style="display: flex; align-items: center; gap: 2rem;">
<img src="/logo.png" alt="FilaMan Logo" class="logo"> <img src="/logo.png" alt="FilaMan Logo" class="logo">
<div class="logo-text"> <div class="logo-text">
<h1>FilaMan<span class="version"></span></h1> <h1>FilaMan<span class="version">v1.2.76</span></h1>
<h4>Filament Management Tool</h4> <h4>Filament Management Tool</h4>
</div> </div>
</div> </div>
@ -55,7 +44,6 @@
<h5 class="card-title">Sacle Calibration</h5> <h5 class="card-title">Sacle Calibration</h5>
<button id="calibrateBtn" class="btn btn-primary">Calibrate Scale</button> <button id="calibrateBtn" class="btn btn-primary">Calibrate Scale</button>
<button id="tareBtn" class="btn btn-secondary">Tare Scale</button> <button id="tareBtn" class="btn btn-secondary">Tare Scale</button>
&nbsp;&nbsp;&nbsp;Enable Auto-TARE <input type="checkbox" id="autoTareCheckbox" onchange="setAutoTare(this.checked);" {{autoTare}}>
<div id="statusMessage" class="mt-3"></div> <div id="statusMessage" class="mt-3"></div>
</div> </div>
</div> </div>
@ -141,15 +129,6 @@
})); }));
}); });
// Add auto-tare function
function setAutoTare(enabled) {
ws.send(JSON.stringify({
type: 'scale',
payload: 'setAutoTare',
enabled: enabled
}));
}
// WebSocket-Verbindung beim Laden der Seite initiieren // WebSocket-Verbindung beim Laden der Seite initiieren
connectWebSocket(); connectWebSocket();
</script> </script>

View File

@ -6,24 +6,13 @@
<title>FilaMan - Filament Management Tool</title> <title>FilaMan - Filament Management Tool</title>
<link rel="icon" type="image/png" href="/favicon.ico"> <link rel="icon" type="image/png" href="/favicon.ico">
<link rel="stylesheet" href="style.css"> <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> </head>
<body> <body>
<div class="navbar"> <div class="navbar">
<div style="display: flex; align-items: center; gap: 2rem;"> <div style="display: flex; align-items: center; gap: 2rem;">
<img src="/logo.png" alt="FilaMan Logo" class="logo"> <img src="/logo.png" alt="FilaMan Logo" class="logo">
<div class="logo-text"> <div class="logo-text">
<h1>FilaMan<span class="version"></span></h1> <h1>FilaMan<span class="version">v1.2.76</span></h1>
<h4>Filament Management Tool</h4> <h4>Filament Management Tool</h4>
</div> </div>
</div> </div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 KiB

View File

@ -9,10 +9,8 @@
; https://docs.platformio.org/page/projectconf.html ; https://docs.platformio.org/page/projectconf.html
[common] [common]
version = "1.5.1" version = "1.2.76"
to_old_version = "1.5.0"
##
[env:esp32dev] [env:esp32dev]
platform = espressif32 platform = espressif32
board = esp32dev board = esp32dev
@ -22,10 +20,7 @@ monitor_speed = 115200
lib_deps = lib_deps =
tzapu/WiFiManager @ ^2.0.17 tzapu/WiFiManager @ ^2.0.17
https://github.com/me-no-dev/ESPAsyncWebServer.git#master 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 bogde/HX711 @ ^0.7.5
adafruit/Adafruit SSD1306 @ ^2.5.13 adafruit/Adafruit SSD1306 @ ^2.5.13
adafruit/Adafruit GFX Library @ ^1.11.11 adafruit/Adafruit GFX Library @ ^1.11.11
@ -35,8 +30,7 @@ lib_deps =
digitaldragon/SSLClient @ ^1.3.2 digitaldragon/SSLClient @ ^1.3.2
; Enable SPIFFS upload ; Enable SPIFFS upload
#board_build.filesystem = spiffs board_build.filesystem = spiffs
board_build.filesystem = littlefs
; Update partition settings ; Update partition settings
board_build.partitions = partitions.csv board_build.partitions = partitions.csv
board_upload.flash_size = 4MB board_upload.flash_size = 4MB
@ -47,29 +41,43 @@ build_flags =
-Os -Os
-ffunction-sections -ffunction-sections
-fdata-sections -fdata-sections
#-DNDEBUG -DNDEBUG
-mtext-section-literals -mtext-section-literals
-DVERSION=\"${common.version}\" '-D VERSION="${common.version}"'
-DTOOLDVERSION=\"${common.to_old_version}\"
-DASYNCWEBSERVER_REGEX -DASYNCWEBSERVER_REGEX
#-DCORE_DEBUG_LEVEL=3 -DCORE_DEBUG_LEVEL=1
-DCONFIG_ARDUHAL_LOG_COLORS=1 -DCONFIG_ARDUHAL_LOG_COLORS=1
#-DOTA_DEBUG=1 -DOTA_DEBUG=1
-DARDUINO_RUNNING_CORE=1
-DARDUINO_EVENT_RUNNING_CORE=1
-DCONFIG_OPTIMIZATION_LEVEL_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
-DCONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=1
-DCONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP=1
-DCONFIG_BOOTLOADER_SKIP_VALIDATE_ON_POWER_ON=1
-DCONFIG_BOOTLOADER_RESERVE_RTC_SIZE=0x1000
-DCONFIG_PARTITION_TABLE_OFFSET=0x8000
-DCONFIG_PARTITION_TABLE_MD5=y
-DBOOT_APP_PARTITION_OTA_0=1 -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 = extra_scripts =
scripts/extra_script.py scripts/extra_script.py
${env:buildfs.extra_scripts} 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] ; Remove or comment out the targets line
extra_scripts = ;targets = buildfs, build
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] [platformio]
default_envs = esp32dev default_envs = esp32dev

View File

@ -14,7 +14,7 @@ def copy_file(input_file, output_file):
def should_compress(file): def should_compress(file):
# Skip compression for spoolman.html # Skip compression for spoolman.html
if file == 'spoolman.html' or file == 'waage.html': if file == 'spoolman.html':
return False return False
# Komprimiere nur bestimmte Dateitypen # Komprimiere nur bestimmte Dateitypen
return file.endswith(('.js', '.png', '.css', '.html')) return file.endswith(('.js', '.png', '.css', '.html'))

View File

@ -1,39 +1,7 @@
Import("env") 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 # Wiederverwendung der replace_version Funktion
exec(open("./scripts/pre_build.py").read()) exec(open("./scripts/pre_build.py").read())
# Bind to SPIFFS build # 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.")

View File

@ -64,10 +64,29 @@ def get_changes_from_git():
return changes 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(): def update_changelog():
print("Starting changelog update...") print("Starting changelog update...") # Add this line
version = get_version() version = get_version()
print(f"Current version: {version}") print(f"Current version: {version}") # Add this line
today = datetime.now().strftime('%Y-%m-%d') today = datetime.now().strftime('%Y-%m-%d')
script_dir = os.path.dirname(os.path.abspath(__file__)) script_dir = os.path.dirname(os.path.abspath(__file__))
@ -92,7 +111,7 @@ def update_changelog():
if not os.path.exists(changelog_path): if not os.path.exists(changelog_path):
with open(changelog_path, 'w') as f: with open(changelog_path, 'w') as f:
f.write(f"# Changelog\n\n{changelog_entry}") f.write(f"# Changelog\n\n{changelog_entry}")
print(f"Created new changelog file with version {version}") push_changes(version)
else: else:
with open(changelog_path, 'r') as f: with open(changelog_path, 'r') as f:
content = f.read() content = f.read()
@ -101,30 +120,9 @@ def update_changelog():
updated_content = content.replace("# Changelog\n", f"# Changelog\n\n{changelog_entry}") updated_content = content.replace("# Changelog\n", f"# Changelog\n\n{changelog_entry}")
with open(changelog_path, 'w') as f: with open(changelog_path, 'w') as f:
f.write(updated_content) f.write(updated_content)
print(f"Added new version {version} to changelog") push_changes(version)
else: else:
# Version existiert bereits, aktualisiere die bestehenden Einträge print(f"Version {version} already exists in changelog")
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__": if __name__ == "__main__":
update_changelog() update_changelog()

View File

@ -3,23 +3,43 @@
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include "commonFS.h" #include "commonFS.h"
volatile spoolmanApiStateType spoolmanApiState = API_INIT; bool spoolman_connected = false;
//bool spoolman_connected = false;
String spoolmanUrl = ""; String spoolmanUrl = "";
bool octoEnabled = false;
String octoUrl = "";
String octoToken = "";
struct SendToApiParams { struct SendToApiParams {
String httpType; String httpType;
String spoolsUrl; String spoolsUrl;
String updatePayload; String updatePayload;
String octoToken;
}; };
JsonDocument fetchSingleSpoolInfo(int spoolId) { /*
// 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() {
HTTPClient http; HTTPClient http;
String spoolsUrl = spoolmanUrl + apiUrl + "/spool/" + spoolId; String spoolsUrl = spoolmanUrl + apiUrl + "/spool";
Serial.print("Rufe Spool-Daten von: "); Serial.print("Rufe Spool-Daten von: ");
Serial.println(spoolsUrl); Serial.println(spoolsUrl);
@ -36,45 +56,84 @@ JsonDocument fetchSingleSpoolInfo(int spoolId) {
Serial.print("Fehler beim Parsen der JSON-Antwort: "); Serial.print("Fehler beim Parsen der JSON-Antwort: ");
Serial.println(error.c_str()); Serial.println(error.c_str());
} else { } else {
String filamentType = doc["filament"]["material"].as<String>(); JsonArray spools = doc.as<JsonArray>();
String filamentBrand = doc["filament"]["vendor"]["name"].as<String>(); JsonArray filteredSpools = filteredDoc.to<JsonArray>();
int nozzle_temp_min = 0; for (JsonObject spool : spools) {
int nozzle_temp_max = 0; JsonObject filteredSpool = filteredSpools.createNestedObject();
if (doc["filament"]["extra"]["nozzle_temperature"].is<String>()) { filteredSpool["extra"]["nfc_id"] = spool["extra"]["nfc_id"];
String tempString = doc["filament"]["extra"]["nozzle_temperature"].as<String>();
tempString.replace("[", "");
tempString.replace("]", "");
int commaIndex = tempString.indexOf(',');
if (commaIndex != -1) { JsonObject filament = filteredSpool.createNestedObject("filament");
nozzle_temp_min = tempString.substring(0, commaIndex).toInt(); filament["sm_id"] = spool["id"];
nozzle_temp_max = tempString.substring(commaIndex + 1).toInt(); 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"];
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 filamentColor = doc["filament"]["color_hex"].as<String>(); http.end();
filamentColor.toUpperCase(); return filteredDoc;
}
String tray_info_idx = doc["filament"]["extra"]["bambu_idx"].as<String>(); JsonDocument fetchAllSpoolsInfo() {
tray_info_idx.replace("\"", ""); HTTPClient http;
String spoolsUrl = spoolmanUrl + apiUrl + "/spool";
String cali_idx = doc["filament"]["extra"]["bambu_cali_id"].as<String>(); // "\"153\"" Serial.print("Rufe Spool-Daten von: ");
cali_idx.replace("\"", ""); Serial.println(spoolsUrl);
String bambu_setting_id = doc["filament"]["extra"]["bambu_setting_id"].as<String>(); // "\"PFUSf40e9953b40d3d\"" http.begin(spoolsUrl);
bambu_setting_id.replace("\"", ""); int httpCode = http.GET();
doc.clear(); 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>();
filteredDoc["color"] = filamentColor; for (JsonObject spool : spools) {
filteredDoc["type"] = filamentType; JsonObject filteredSpool = filteredSpools.createNestedObject();
filteredDoc["nozzle_temp_min"] = nozzle_temp_min; filteredSpool["price"] = spool["price"];
filteredDoc["nozzle_temp_max"] = nozzle_temp_max; filteredSpool["remaining_weight"] = spool["remaining_weight"];
filteredDoc["brand"] = filamentBrand; filteredSpool["used_weight"] = spool["used_weight"];
filteredDoc["tray_info_idx"] = tray_info_idx; filteredSpool["extra"]["nfc_id"] = spool["extra"]["nfc_id"];
filteredDoc["cali_idx"] = cali_idx;
filteredDoc["bambu_setting_id"] = bambu_setting_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"];
}
} }
} else { } else {
Serial.print("Fehler beim Abrufen der Spool-Daten. HTTP-Code: "); Serial.print("Fehler beim Abrufen der Spool-Daten. HTTP-Code: ");
@ -86,42 +145,34 @@ JsonDocument fetchSingleSpoolInfo(int spoolId) {
} }
void sendToApi(void *parameter) { void sendToApi(void *parameter) {
spoolmanApiState = API_TRANSMITTING;
SendToApiParams* params = (SendToApiParams*)parameter; SendToApiParams* params = (SendToApiParams*)parameter;
// Extrahiere die Werte // Extrahiere die Werte
String httpType = params->httpType; String httpType = params->httpType;
String spoolsUrl = params->spoolsUrl; String spoolsUrl = params->spoolsUrl;
String updatePayload = params->updatePayload; String updatePayload = params->updatePayload;
String octoToken = params->octoToken;
HTTPClient http; HTTPClient http;
http.setReuse(false);
http.begin(spoolsUrl); http.begin(spoolsUrl);
http.addHeader("Content-Type", "application/json"); http.addHeader("Content-Type", "application/json");
if (octoEnabled && octoToken != "") http.addHeader("X-Api-Key", octoToken);
int httpCode; int httpCode = http.PUT(updatePayload);
if (httpType == "PATCH") httpCode = http.PATCH(updatePayload); 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) { if (httpCode == HTTP_CODE_OK) {
Serial.println("Spoolman erfolgreich aktualisiert"); Serial.println("Gewicht der Spule erfolgreich aktualisiert");
} else { } else {
Serial.println("Fehler beim Senden an Spoolman!"); Serial.println("Fehler beim Aktualisieren des Gewichts der Spule");
oledShowMessage("Spoolman update failed"); oledShowMessage("Spoolman update failed");
vTaskDelay(2000 / portTICK_PERIOD_MS); vTaskDelay(2000 / portTICK_PERIOD_MS);
} }
http.end(); http.end();
vTaskDelay(50 / portTICK_PERIOD_MS);
// Speicher freigeben // Speicher freigeben
delete params; delete params;
vTaskDelete(NULL); vTaskDelete(NULL);
spoolmanApiState = API_IDLE;
} }
bool updateSpoolTagId(String uidString, const char* payload) { bool updateSpoolTagId(String uidString, const char* payload) {
@ -135,7 +186,7 @@ bool updateSpoolTagId(String uidString, const char* payload) {
} }
// Überprüfe, ob die erforderlichen Felder vorhanden sind // Überprüfe, ob die erforderlichen Felder vorhanden sind
if (!doc["sm_id"].is<String>() || doc["sm_id"].as<String>() == "") { if (!doc.containsKey("sm_id") || doc["sm_id"] == "") {
Serial.println("Keine Spoolman-ID gefunden."); Serial.println("Keine Spoolman-ID gefunden.");
return false; return false;
} }
@ -166,14 +217,12 @@ bool updateSpoolTagId(String uidString, const char* payload) {
BaseType_t result = xTaskCreate( BaseType_t result = xTaskCreate(
sendToApi, // Task-Funktion sendToApi, // Task-Funktion
"SendToApiTask", // Task-Name "SendToApiTask", // Task-Name
6144, // Stackgröße in Bytes 4096, // Stackgröße in Bytes
(void*)params, // Parameter (void*)params, // Parameter
0, // Priorität 0, // Priorität
NULL // Task-Handle (nicht benötigt) NULL // Task-Handle (nicht benötigt)
); );
updateDoc.clear();
return true; return true;
} }
@ -204,102 +253,15 @@ uint8_t updateSpoolWeight(String spoolId, uint16_t weight) {
BaseType_t result = xTaskCreate( BaseType_t result = xTaskCreate(
sendToApi, // Task-Funktion sendToApi, // Task-Funktion
"SendToApiTask", // Task-Name "SendToApiTask", // Task-Name
6144, // Stackgröße in Bytes 4096, // Stackgröße in Bytes
(void*)params, // Parameter (void*)params, // Parameter
0, // Priorität 0, // Priorität
NULL // Task-Handle (nicht benötigt) NULL // Task-Handle (nicht benötigt)
); );
updateDoc.clear();
return 1; 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 // #### Spoolman init
bool checkSpoolmanExtraFields() { bool checkSpoolmanExtraFields() {
HTTPClient http; HTTPClient http;
@ -406,7 +368,7 @@ bool checkSpoolmanExtraFields() {
for (uint8_t s = 0; s < extraLength; s++) { for (uint8_t s = 0; s < extraLength; s++) {
bool found = false; bool found = false;
for (JsonObject field : doc.as<JsonArray>()) { for (JsonObject field : doc.as<JsonArray>()) {
if (field["key"].is<String>() && field["key"] == extraFields[s]) { if (field.containsKey("key") && field["key"] == extraFields[s]) {
Serial.println("Feld gefunden: " + extraFields[s]); Serial.println("Feld gefunden: " + extraFields[s]);
found = true; found = true;
break; break;
@ -441,13 +403,12 @@ bool checkSpoolmanExtraFields() {
} }
} }
} }
http.end();
} }
Serial.println("-------- ENDE Prüfe Felder --------"); Serial.println("-------- ENDE Prüfe Felder --------");
Serial.println(); Serial.println();
http.end();
return true; return true;
} }
@ -469,7 +430,7 @@ bool checkSpoolmanInstance(const String& url) {
String payload = http.getString(); String payload = http.getString();
JsonDocument doc; JsonDocument doc;
DeserializationError error = deserializeJson(doc, payload); DeserializationError error = deserializeJson(doc, payload);
if (!error && doc["status"].is<String>()) { if (!error && doc.containsKey("status")) {
const char* status = doc["status"]; const char* status = doc["status"];
http.end(); http.end();
@ -482,8 +443,7 @@ bool checkSpoolmanInstance(const String& url) {
return false; return false;
} }
spoolmanApiState = API_IDLE; spoolman_connected = true;
oledShowTopRow();
return strcmp(status, "healthy") == 0; return strcmp(status, "healthy") == 0;
} }
} }
@ -492,38 +452,24 @@ bool checkSpoolmanInstance(const String& url) {
return false; return false;
} }
bool saveSpoolmanUrl(const String& url, bool octoOn, const String& octoWh, const String& octoTk) { bool saveSpoolmanUrl(const String& url) {
if (!checkSpoolmanInstance(url)) return false; if (!checkSpoolmanInstance(url)) return false;
JsonDocument doc; JsonDocument doc;
doc["url"] = url; doc["url"] = url;
doc["octoEnabled"] = octoOn; Serial.print("Speichere URL in Datei: ");
doc["octoUrl"] = octoWh; Serial.println(url);
doc["octoToken"] = octoTk;
Serial.print("Speichere Spoolman Data in Datei: ");
Serial.println(doc.as<String>());
if (!saveJsonValue("/spoolman_url.json", doc)) { if (!saveJsonValue("/spoolman_url.json", doc)) {
Serial.println("Fehler beim Speichern der Spoolman-URL."); Serial.println("Fehler beim Speichern der Spoolman-URL.");
return false;
} }
spoolmanUrl = url; spoolmanUrl = url;
octoEnabled = octoOn;
octoUrl = octoWh;
octoToken = octoTk;
return true; return true;
} }
String loadSpoolmanUrl() { String loadSpoolmanUrl() {
JsonDocument doc; JsonDocument doc;
if (loadJsonValue("/spoolman_url.json", doc) && doc["url"].is<String>()) { if (loadJsonValue("/spoolman_url.json", doc) && doc.containsKey("url")) {
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>(); return doc["url"].as<String>();
} }
Serial.println("Keine gültige Spoolman-URL gefunden."); Serial.println("Keine gültige Spoolman-URL gefunden.");

View File

@ -6,28 +6,19 @@
#include "website.h" #include "website.h"
#include "display.h" #include "display.h"
#include <ArduinoJson.h> #include <ArduinoJson.h>
typedef enum {
API_INIT,
API_IDLE,
API_TRANSMITTING
} spoolmanApiStateType;
extern volatile spoolmanApiStateType spoolmanApiState;
extern bool spoolman_connected; extern bool spoolman_connected;
extern String spoolmanUrl; extern String spoolmanUrl;
extern bool octoEnabled;
extern String octoUrl;
extern String octoToken;
bool checkSpoolmanInstance(const String& url); bool checkSpoolmanInstance(const String& url);
bool saveSpoolmanUrl(const String& url, bool octoOn, const String& octoWh, const String& octoTk); bool saveSpoolmanUrl(const String& url);
String loadSpoolmanUrl(); // Neue Funktion zum Laden der URL String loadSpoolmanUrl(); // Neue Funktion zum Laden der URL
bool checkSpoolmanExtraFields(); // Neue Funktion zum Überprüfen der Extrafelder bool checkSpoolmanExtraFields(); // Neue Funktion zum Überprüfen der Extrafelder
JsonDocument fetchSingleSpoolInfo(int spoolId); // API-Funktion für die Webseite JsonDocument fetchSpoolsForWebsite(); // API-Funktion für die Webseite
JsonDocument fetchAllSpoolsInfo();
void sendAmsData(AsyncWebSocketClient *client); // Neue Funktion zum Senden von AMS-Daten
bool updateSpoolTagId(String uidString, const char* payload); // Neue Funktion zum Aktualisieren eines Spools bool updateSpoolTagId(String uidString, const char* payload); // Neue Funktion zum Aktualisieren eines Spools
uint8_t updateSpoolWeight(String spoolId, uint16_t weight); // Neue Funktion zum Aktualisieren des Gewichts uint8_t updateSpoolWeight(String spoolId, uint16_t weight); // Neue Funktion zum Aktualisieren des Gewichts
bool initSpoolman(); // Neue Funktion zum Initialisieren von Spoolman 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 #endif

View File

@ -17,54 +17,20 @@ PubSubClient client(sslClient);
TaskHandle_t BambuMqttTask; TaskHandle_t BambuMqttTask;
String topic = ""; String report_topic = "";
//String request_topic = ""; //String request_topic = "";
const char* bambu_username = "bblp"; const char* bambu_username = "bblp";
const char* bambu_ip = nullptr; const char* bambu_ip = nullptr;
const char* bambu_accesscode = nullptr; const char* bambu_accesscode = nullptr;
const char* bambu_serialnr = 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 bambu_connected = false;
bool autoSendToBambu = false;
int autoSetToBambuSpoolId = 0;
// Globale Variablen für AMS-Daten // Globale Variablen für AMS-Daten
int ams_count = 0; int ams_count = 0;
String amsJsonData; // Speichert das fertige JSON für WebSocket-Clients 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 removeBambuCredentials() { bool saveBambuCredentials(const String& ip, const String& serialnr, const String& accesscode) {
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) { if (BambuMqttTask) {
vTaskDelete(BambuMqttTask); vTaskDelete(BambuMqttTask);
} }
@ -73,8 +39,6 @@ bool saveBambuCredentials(const String& ip, const String& serialnr, const String
doc["bambu_ip"] = ip; doc["bambu_ip"] = ip;
doc["bambu_accesscode"] = accesscode; doc["bambu_accesscode"] = accesscode;
doc["bambu_serialnr"] = serialnr; doc["bambu_serialnr"] = serialnr;
doc["autoSendToBambu"] = autoSend;
doc["autoSendTime"] = (autoSendTime != "") ? autoSendTime.toInt() : autoSetBambuAmsCounter;
if (!saveJsonValue("/bambu_credentials.json", doc)) { if (!saveJsonValue("/bambu_credentials.json", doc)) {
Serial.println("Fehler beim Speichern der Bambu-Credentials."); Serial.println("Fehler beim Speichern der Bambu-Credentials.");
@ -85,8 +49,6 @@ bool saveBambuCredentials(const String& ip, const String& serialnr, const String
bambu_ip = ip.c_str(); bambu_ip = ip.c_str();
bambu_accesscode = accesscode.c_str(); bambu_accesscode = accesscode.c_str();
bambu_serialnr = serialnr.c_str(); bambu_serialnr = serialnr.c_str();
autoSendToBambu = autoSend;
autoSetBambuAmsCounter = autoSendTime.toInt();
vTaskDelay(100 / portTICK_PERIOD_MS); vTaskDelay(100 / portTICK_PERIOD_MS);
if (!setupMqtt()) return false; if (!setupMqtt()) return false;
@ -96,29 +58,22 @@ bool saveBambuCredentials(const String& ip, const String& serialnr, const String
bool loadBambuCredentials() { bool loadBambuCredentials() {
JsonDocument doc; JsonDocument doc;
if (loadJsonValue("/bambu_credentials.json", doc) && doc["bambu_ip"].is<String>()) { if (loadJsonValue("/bambu_credentials.json", doc) && doc.containsKey("bambu_ip")) {
// Temporäre Strings für die Werte // Temporäre Strings für die Werte
String ip = doc["bambu_ip"].as<String>(); String ip = doc["bambu_ip"].as<String>();
String code = doc["bambu_accesscode"].as<String>(); String code = doc["bambu_accesscode"].as<String>();
String serial = doc["bambu_serialnr"].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(); ip.trim();
code.trim(); code.trim();
serial.trim(); serial.trim();
// Dynamische Speicherallokation für die globalen Pointer // Dynamische Speicherallokation für die globalen Pointer
bambu_ip = g_bambu_ip.c_str(); bambu_ip = strdup(ip.c_str());
bambu_accesscode = g_bambu_accesscode.c_str(); bambu_accesscode = strdup(code.c_str());
bambu_serialnr = g_bambu_serialnr.c_str(); bambu_serialnr = strdup(serial.c_str());
topic = "device/" + String(bambu_serialnr); report_topic = "device/" + String(bambu_serialnr) + "/report";
//request_topic = "device/" + String(bambu_serialnr) + "/request"; //request_topic = "device/" + String(bambu_serialnr) + "/request";
return true; return true;
} }
@ -126,49 +81,19 @@ bool loadBambuCredentials() {
return false; return false;
} }
struct FilamentResult { String findFilamentIdx(String brand, String type) {
String key;
String type;
};
FilamentResult findFilamentIdx(String brand, String type) {
// JSON-Dokument für die Filament-Daten erstellen // JSON-Dokument für die Filament-Daten erstellen
JsonDocument doc; 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 // 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"); Serial.println("Fehler beim Laden der Filament-Daten");
return {"GFL99", "PLA"}; // Fallback auf Generic PLA return "GFL99"; // 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; String searchKey;
// 1. Suche nach Brand + Type Kombination
if (brand == "Bambu" || brand == "Bambulab") { if (brand == "Bambu" || brand == "Bambulab") {
searchKey = "Bambu " + type; searchKey = "Bambu " + type;
} else if (brand == "PolyLite") { } else if (brand == "PolyLite") {
@ -184,49 +109,26 @@ FilamentResult findFilamentIdx(String brand, String type) {
// Durchsuche alle Einträge nach der Brand + Type Kombination // Durchsuche alle Einträge nach der Brand + Type Kombination
for (JsonPair kv : doc.as<JsonObject>()) { for (JsonPair kv : doc.as<JsonObject>()) {
if (kv.value().as<String>() == searchKey) { if (kv.value().as<String>() == searchKey) {
return {kv.key().c_str(), kv.value().as<String>()}; return kv.key().c_str();
} }
} }
// 2. Wenn nicht gefunden, zerlege den type String in Wörter und suche nach jedem Wort // 2. Wenn nicht gefunden, suche nach Generic + Type
// Sammle alle vorhandenen Filamenttypen aus der JSON searchKey = "Generic " + type;
std::vector<String> knownTypes;
for (JsonPair kv : doc.as<JsonObject>()) { for (JsonPair kv : doc.as<JsonObject>()) {
String value = kv.value().as<String>(); if (kv.value().as<String>() == searchKey) {
// Extrahiere den Typ ohne Markennamen return kv.key().c_str();
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) // 3. Wenn immer noch nichts gefunden, gebe GFL99 zurück (Generic PLA)
return {"GFL99", "PLA"}; return "GFL99";
} }
bool sendMqttMessage(const String& payload) { bool sendMqttMessage(String payload) {
Serial.println("Sending MQTT message"); Serial.println("Sending MQTT message");
Serial.println(payload); Serial.println(payload);
if (client.publish((String(topic) + "/request").c_str(), payload.c_str())) if (client.publish(report_topic.c_str(), payload.c_str()))
{ {
return true; return true;
} }
@ -254,22 +156,15 @@ bool setBambuSpool(String payload) {
int minTemp = doc["nozzle_temp_min"]; int minTemp = doc["nozzle_temp_min"];
int maxTemp = doc["nozzle_temp_max"]; int maxTemp = doc["nozzle_temp_max"];
String type = doc["type"].as<String>(); String type = doc["type"].as<String>();
(type == "PLA+") ? type = "PLA" : type;
String brand = doc["brand"].as<String>(); String brand = doc["brand"].as<String>();
String tray_info_idx = (doc["tray_info_idx"].as<String>() != "-1") ? doc["tray_info_idx"].as<String>() : ""; String tray_info_idx = (doc["tray_info_idx"].as<String>() != "-1") ? doc["tray_info_idx"].as<String>() : "";
if (tray_info_idx == "") { if (tray_info_idx == "") tray_info_idx = (brand != "" && type != "") ? findFilamentIdx(brand, type) : "";
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 setting_id = doc["bambu_setting_id"].as<String>();
String cali_idx = doc["cali_idx"].as<String>(); String cali_idx = doc["cali_idx"].as<String>();
doc.clear(); doc.clear();
doc["print"]["sequence_id"] = "0"; doc["print"]["sequence_id"] = 0;
doc["print"]["command"] = "ams_filament_setting"; doc["print"]["command"] = "ams_filament_setting";
doc["print"]["ams_id"] = amsId < 200 ? amsId : 255; doc["print"]["ams_id"] = amsId < 200 ? amsId : 255;
doc["print"]["tray_id"] = trayId < 200 ? trayId : 254; doc["print"]["tray_id"] = trayId < 200 ? trayId : 254;
@ -277,7 +172,7 @@ bool setBambuSpool(String payload) {
doc["print"]["nozzle_temp_min"] = minTemp; doc["print"]["nozzle_temp_min"] = minTemp;
doc["print"]["nozzle_temp_max"] = maxTemp; doc["print"]["nozzle_temp_max"] = maxTemp;
doc["print"]["tray_type"] = type; doc["print"]["tray_type"] = type;
//doc["print"]["cali_idx"] = (cali_idx != "") ? cali_idx : ""; doc["print"]["cali_idx"] = (cali_idx != "") ? cali_idx : "";
doc["print"]["tray_info_idx"] = tray_info_idx; doc["print"]["tray_info_idx"] = tray_info_idx;
doc["print"]["setting_id"] = setting_id; doc["print"]["setting_id"] = setting_id;
@ -299,13 +194,13 @@ bool setBambuSpool(String payload) {
if (cali_idx != "") { if (cali_idx != "") {
yield(); yield();
doc["print"]["sequence_id"] = "0"; doc["print"]["sequence_id"] = 0;
doc["print"]["command"] = "extrusion_cali_sel"; doc["print"]["command"] = "extrusion_cali_sel";
doc["print"]["filament_id"] = tray_info_idx; doc["print"]["filament_id"] = tray_info_idx;
doc["print"]["nozzle_diameter"] = "0.4"; doc["print"]["nozzle_diameter"] = "0.4";
doc["print"]["cali_idx"] = cali_idx.toInt(); doc["print"]["cali_idx"] = cali_idx.toInt();
doc["print"]["tray_id"] = trayId < 200 ? trayId : 254; 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 // Serialize the JSON
String output; String output;
@ -323,120 +218,44 @@ bool setBambuSpool(String payload) {
doc.clear(); doc.clear();
yield(); 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;
return true; // Serialize the JSON
} String output;
serializeJson(doc, output);
void autoSetSpool(int spoolId, uint8_t trayId) { if (sendMqttMessage(output)) {
// wenn neue spule erkannt und autoSetToBambu > 0 Serial.println("Filament Setting successfully set");
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 else
{ {
ams_data[extIdx].trays[0].setting_id = ""; Serial.println("Failed to set Filament setting");
ams_data[extIdx].trays[0].cali_idx = ""; return false;
}
ams_count++; // Erhöhe ams_count für die externe Spule
} }
// Erstelle JSON für WebSocket-Clients doc.clear();
JsonDocument wsDoc; yield();
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); return true;
wsDoc.clear();
Serial.println("AMS data updated");
sendAmsData(nullptr);
} }
// init // init
void mqtt_callback(char* topic, byte* payload, unsigned int length) { void mqtt_callback(char* topic, byte* payload, unsigned int length) {
String message; String message;
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
message += (char)payload[i]; message += (char)payload[i];
} }
@ -444,20 +263,16 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
// JSON-Dokument parsen // JSON-Dokument parsen
JsonDocument doc; JsonDocument doc;
DeserializationError error = deserializeJson(doc, message); DeserializationError error = deserializeJson(doc, message);
message = ""; if (error) {
if (error)
{
Serial.print("Fehler beim Parsen des JSON: "); Serial.print("Fehler beim Parsen des JSON: ");
Serial.println(error.c_str()); Serial.println(error.c_str());
return; return;
} }
// Prüfen, ob "print->upgrade_state" und "print.ams.ams" existieren // Prüfen, ob "print->upgrade_state" und "print.ams.ams" existieren
if (doc["print"]["upgrade_state"].is<JsonObject>() || (doc["print"]["command"].is<String>() && doc["print"]["command"] == "push_status")) if (doc["print"].containsKey("upgrade_state")) {
{
// Prüfen ob AMS-Daten vorhanden sind // Prüfen ob AMS-Daten vorhanden sind
if (!doc["print"]["ams"].is<JsonObject>() || !doc["print"]["ams"]["ams"].is<JsonArray>()) if (!doc["print"].containsKey("ams") || !doc["print"]["ams"].containsKey("ams")) {
{
return; return;
} }
@ -489,81 +304,154 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
// Vergleiche die Trays // Vergleiche die Trays
for (int j = 0; j < trayArray.size() && j < 4 && !hasChanges; j++) { for (int j = 0; j < trayArray.size() && j < 4 && !hasChanges; j++) {
JsonObject trayObj = trayArray[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 || if (trayObj["tray_info_idx"].as<String>() != ams_data[storedIndex].trays[j].tray_info_idx ||
trayObj["tray_type"].as<String>() != ams_data[storedIndex].trays[j].tray_type || trayObj["tray_type"].as<String>() != ams_data[storedIndex].trays[j].tray_type ||
trayObj["tray_color"].as<String>() != ams_data[storedIndex].trays[j].tray_color || trayObj["tray_color"].as<String>() != ams_data[storedIndex].trays[j].tray_color ||
(trayObj["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) { trayObj["cali_idx"].as<String>() != ams_data[storedIndex].trays[j].cali_idx) {
hasChanges = true; hasChanges = true;
if (autoSendToBambu && autoSetToBambuSpoolId > 0 && hasChanges)
{
autoSetSpool(autoSetToBambuSpoolId, ams_data[storedIndex].trays[j].id);
}
break; break;
} }
} }
} }
// Prüfe die externe Spule // Prüfe die externe Spule
if (!hasChanges && doc["print"].containsKey("vt_tray")) {
JsonObject vtTray = doc["print"]["vt_tray"]; JsonObject vtTray = doc["print"]["vt_tray"];
if (doc["print"]["vt_tray"].is<JsonObject>()) { bool foundExternal = false;
for (int i = 0; i < ams_count; i++) { for (int i = 0; i < ams_count; i++) {
if (ams_data[i].ams_id == 255) { if (ams_data[i].ams_id == 255) {
if (vtTray["tray_type"].as<String>() == "") ams_data[i].trays[0].setting_id = ""; foundExternal = true;
if (vtTray["setting_id"].isNull()) vtTray["setting_id"] = "";
if (vtTray["tray_info_idx"].as<String>() != ams_data[i].trays[0].tray_info_idx || if (vtTray["tray_info_idx"].as<String>() != ams_data[i].trays[0].tray_info_idx ||
vtTray["tray_type"].as<String>() != ams_data[i].trays[0].tray_type || vtTray["tray_type"].as<String>() != ams_data[i].trays[0].tray_type ||
vtTray["tray_color"].as<String>() != ams_data[i].trays[0].tray_color || vtTray["tray_color"].as<String>() != ams_data[i].trays[0].tray_color ||
(vtTray["setting_id"].as<String>() != "" && vtTray["setting_id"].as<String>() != ams_data[i].trays[0].setting_id) || vtTray["cali_idx"].as<String>() != ams_data[i].trays[0].cali_idx) {
(vtTray["tray_type"].as<String>() != "" && vtTray["cali_idx"].as<String>() != ams_data[i].trays[0].cali_idx)) {
hasChanges = true; hasChanges = true;
if (autoSendToBambu && autoSetToBambuSpoolId > 0 && hasChanges)
{
autoSetSpool(autoSetToBambuSpoolId, 254);
}
} }
break; break;
} }
} }
if (!foundExternal) hasChanges = true;
} }
if (!hasChanges) return; if (!hasChanges) return;
updateAmsWsData(doc, amsArray, ams_count, 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>();
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);
}
// Neue Bedingung für ams_filament_setting // Neue Bedingung für ams_filament_setting
if (doc["print"]["command"] == "ams_filament_setting") { else if (doc["print"]["command"] == "ams_filament_setting") {
int amsId = doc["print"]["ams_id"].as<int>(); int amsId = doc["print"]["ams_id"].as<int>();
int trayId = doc["print"]["tray_id"].as<int>(); int trayId = doc["print"]["tray_id"].as<int>();
String settingId = (doc["print"]["setting_id"].is<String>()) ? doc["print"]["setting_id"].as<String>() : ""; String settingId = doc["print"]["setting_id"].as<String>();
// Finde das entsprechende AMS und Tray // Finde das entsprechende AMS und Tray
for (int i = 0; i < ams_count; i++) { for (int i = 0; i < ams_count; i++) {
if (ams_data[i].ams_id == amsId) { if (ams_data[i].ams_id == amsId) {
if (trayId == 254) // Update setting_id im entsprechenden Tray
{
// 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;
}
}
}
else
{
ams_data[i].trays[trayId].setting_id = settingId; ams_data[i].trays[trayId].setting_id = settingId;
// Erstelle neues JSON für WebSocket-Clients
JsonDocument wsDoc;
JsonArray wsArray = wsDoc.to<JsonArray>();
for (int j = 0; j < ams_count; j++) {
JsonObject amsObj = wsArray.createNestedObject();
amsObj["ams_id"] = ams_data[j].ams_id;
JsonArray trays = amsObj.createNestedArray("tray");
int maxTrays = (ams_data[j].ams_id == 255) ? 1 : 4;
for (int k = 0; k < maxTrays; k++) {
JsonObject trayObj = trays.createNestedObject();
trayObj["id"] = ams_data[j].trays[k].id;
trayObj["tray_info_idx"] = ams_data[j].trays[k].tray_info_idx;
trayObj["tray_type"] = ams_data[j].trays[k].tray_type;
trayObj["tray_sub_brands"] = ams_data[j].trays[k].tray_sub_brands;
trayObj["tray_color"] = ams_data[j].trays[k].tray_color;
trayObj["nozzle_temp_min"] = ams_data[j].trays[k].nozzle_temp_min;
trayObj["nozzle_temp_max"] = ams_data[j].trays[k].nozzle_temp_max;
trayObj["setting_id"] = ams_data[j].trays[k].setting_id;
trayObj["cali_idx"] = ams_data[j].trays[k].cali_idx;
}
} }
// Aktualisiere das globale amsJsonData
amsJsonData = "";
serializeJson(wsArray, amsJsonData);
// Sende an WebSocket Clients // Sende an WebSocket Clients
Serial.println("Filament setting updated");
sendAmsData(nullptr); sendAmsData(nullptr);
break; break;
} }
@ -573,18 +461,16 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
void reconnect() { void reconnect() {
// Loop until we're reconnected // Loop until we're reconnected
uint8_t retries = 0;
while (!client.connected()) { while (!client.connected()) {
Serial.println("Attempting MQTT re/connection..."); Serial.print("Attempting MQTT connection...");
bambu_connected = false; bambu_connected = false;
oledShowTopRow(); oledShowTopRow();
// Attempt to connect // Attempt to connect
String clientId = String(bambu_serialnr) + "_" + String(random(0, 100)); if (client.connect(bambu_serialnr, bambu_username, bambu_accesscode)) {
if (client.connect(clientId.c_str(), bambu_username, bambu_accesscode)) { Serial.println("... re-connected");
Serial.println("MQTT re/connected"); // ... and resubscribe
client.subscribe(report_topic.c_str());
client.subscribe((String(topic) + "/report").c_str());
bambu_connected = true; bambu_connected = true;
oledShowTopRow(); oledShowTopRow();
} else { } else {
@ -593,23 +479,14 @@ void reconnect() {
Serial.println(" try again in 5 seconds"); Serial.println(" try again in 5 seconds");
bambu_connected = false; bambu_connected = false;
oledShowTopRow(); oledShowTopRow();
// Wait 5 seconds before retrying
yield(); yield();
vTaskDelay(5000 / portTICK_PERIOD_MS); 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) { void mqtt_loop(void * parameter) {
Serial.println("Bambu MQTT Task gestartet");
for(;;) { for(;;) {
if (pauseBambuMqttTask) { if (pauseBambuMqttTask) {
vTaskDelay(10000); vTaskDelay(10000);
@ -623,7 +500,6 @@ void mqtt_loop(void * parameter) {
} }
client.loop(); client.loop();
yield(); yield();
esp_task_wdt_reset();
vTaskDelay(100); vTaskDelay(100);
} }
} }
@ -631,27 +507,30 @@ void mqtt_loop(void * parameter) {
bool setupMqtt() { bool setupMqtt() {
// Wenn Bambu Daten vorhanden // Wenn Bambu Daten vorhanden
bool success = loadBambuCredentials(); bool success = loadBambuCredentials();
vTaskDelay(100 / portTICK_PERIOD_MS);
if (!success) { if (!success) {
bambuDisabled = true; Serial.println("Failed to load Bambu credentials");
oledShowMessage("Bambu Credentials Missing");
vTaskDelay(2000 / portTICK_PERIOD_MS);
return false; return false;
} }
if (success && bambu_ip != "" && bambu_accesscode != "" && bambu_serialnr != "") if (success && bambu_ip != "" && bambu_accesscode != "" && bambu_serialnr != "")
{ {
bambuDisabled = false;
sslClient.setCACert(root_ca); sslClient.setCACert(root_ca);
sslClient.setInsecure(); sslClient.setInsecure();
client.setServer(bambu_ip, 8883); client.setServer(bambu_ip, 8883);
// Verbinden mit dem MQTT-Server // Verbinden mit dem MQTT-Server
bool connected = true; bool connected = true;
String clientId = String(bambu_serialnr) + "_" + String(random(0, 100)); if (client.connect(bambu_serialnr, bambu_username, bambu_accesscode))
if (client.connect(clientId.c_str(), bambu_username, bambu_accesscode))
{ {
client.setCallback(mqtt_callback); client.setCallback(mqtt_callback);
client.setBufferSize(15488); client.setBufferSize(5120);
client.subscribe((String(topic) + "/report").c_str()); // Optional: Topic abonnieren
client.subscribe(report_topic.c_str());
//client.subscribe(request_topic.c_str());
Serial.println("MQTT-Client initialisiert"); Serial.println("MQTT-Client initialisiert");
oledShowMessage("Bambu Connected"); oledShowMessage("Bambu Connected");
@ -661,7 +540,7 @@ bool setupMqtt() {
xTaskCreatePinnedToCore( xTaskCreatePinnedToCore(
mqtt_loop, /* Function to implement the task */ mqtt_loop, /* Function to implement the task */
"BambuMqtt", /* Name of the task */ "BambuMqtt", /* Name of the task */
8192, /* Stack size in words */ 10000, /* Stack size in words */
NULL, /* Task input parameter */ NULL, /* Task input parameter */
mqttTaskPrio, /* Priority of the task */ mqttTaskPrio, /* Priority of the task */
&BambuMqttTask, /* Task handle. */ &BambuMqttTask, /* Task handle. */
@ -680,7 +559,10 @@ bool setupMqtt() {
} }
else else
{ {
bambuDisabled = true; Serial.println("Fehler: Keine MQTT-Daten vorhanden");
oledShowMessage("Bambu Credentials Missing");
oledShowTopRow();
vTaskDelay(2000 / portTICK_PERIOD_MS);
return false; return false;
} }
return true; return true;
@ -689,7 +571,6 @@ bool setupMqtt() {
void bambu_restart() { void bambu_restart() {
if (BambuMqttTask) { if (BambuMqttTask) {
vTaskDelete(BambuMqttTask); vTaskDelete(BambuMqttTask);
delay(10);
} }
setupMqtt(); setupMqtt();
} }

View File

@ -28,13 +28,9 @@ extern bool bambu_connected;
extern int ams_count; extern int ams_count;
extern AMSData ams_data[MAX_AMS]; extern AMSData ams_data[MAX_AMS];
extern bool autoSendToBambu;
extern int autoSetToBambuSpoolId;
extern bool bambuDisabled;
bool removeBambuCredentials();
bool loadBambuCredentials(); bool loadBambuCredentials();
bool saveBambuCredentials(const String& bambu_ip, const String& bambu_serialnr, const String& bambu_accesscode, const bool autoSend, const String& autoSendTime); bool saveBambuCredentials(const String& bambu_ip, const String& bambu_serialnr, const String& bambu_accesscode);
bool setupMqtt(); bool setupMqtt();
void mqtt_loop(void * parameter); void mqtt_loop(void * parameter);
bool setBambuSpool(String payload); bool setBambuSpool(String payload);

View File

@ -1,22 +1,7 @@
#include "commonFS.h" #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) { bool saveJsonValue(const char* filename, const JsonDocument& doc) {
File file = LittleFS.open(filename, "w"); File file = SPIFFS.open(filename, "w");
if (!file) { if (!file) {
Serial.print("Fehler beim Öffnen der Datei zum Schreiben: "); Serial.print("Fehler beim Öffnen der Datei zum Schreiben: ");
Serial.println(filename); Serial.println(filename);
@ -34,7 +19,7 @@ bool saveJsonValue(const char* filename, const JsonDocument& doc) {
} }
bool loadJsonValue(const char* filename, JsonDocument& doc) { bool loadJsonValue(const char* filename, JsonDocument& doc) {
File file = LittleFS.open(filename, "r"); File file = SPIFFS.open(filename, "r");
if (!file) { if (!file) {
Serial.print("Fehler beim Öffnen der Datei zum Lesen: "); Serial.print("Fehler beim Öffnen der Datei zum Lesen: ");
Serial.println(filename); Serial.println(filename);
@ -50,12 +35,23 @@ bool loadJsonValue(const char* filename, JsonDocument& doc) {
return true; return true;
} }
void initializeFileSystem() { bool initializeSPIFFS() {
if (!LittleFS.begin(true)) { // Erster Versuch
Serial.println("LittleFS Mount Failed"); if (SPIFFS.begin(true)) {
return; Serial.println("SPIFFS mounted successfully.");
return true;
} }
Serial.printf("LittleFS Total: %u bytes\n", LittleFS.totalBytes());
Serial.printf("LittleFS Used: %u bytes\n", LittleFS.usedBytes()); // Formatierung versuchen
Serial.printf("LittleFS Free: %u bytes\n", LittleFS.totalBytes() - LittleFS.usedBytes()); 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;
} }

View File

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

View File

@ -19,12 +19,6 @@ const uint16_t SCALE_LEVEL_WEIGHT = 500;
uint16_t defaultScaleCalibrationValue = 430; uint16_t defaultScaleCalibrationValue = 430;
// ***** HX711 // ***** HX711
// ***** TTP223 (Touch Sensor)
// TTP223 circuit wiring
const uint8_t TTP223_PIN = 25;
// ***** TTP223
// ***** Display // ***** Display
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins) // Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// On an ESP32: 21(SDA), 22(SCL) // On an ESP32: 21(SDA), 22(SCL)
@ -46,10 +40,6 @@ const uint8_t webserverPort = 80;
const char* apiUrl = "/api/v1"; const char* apiUrl = "/api/v1";
// ***** API // ***** API
// ***** Bambu Auto Set Spool
uint8_t autoSetBambuAmsCounter = 60;
// ***** Bambu Auto Set Spool
// ***** Task Prios // ***** Task Prios
uint8_t rfidTaskCore = 1; uint8_t rfidTaskCore = 1;
uint8_t rfidTaskPrio = 1; uint8_t rfidTaskPrio = 1;

View File

@ -11,8 +11,6 @@ extern const uint8_t LOADCELL_SCK_PIN;
extern const uint8_t calVal_eepromAdress; extern const uint8_t calVal_eepromAdress;
extern const uint16_t SCALE_LEVEL_WEIGHT; extern const uint16_t SCALE_LEVEL_WEIGHT;
extern const uint8_t TTP223_PIN;
extern const int8_t OLED_RESET; extern const int8_t OLED_RESET;
extern const uint8_t SCREEN_ADDRESS; extern const uint8_t SCREEN_ADDRESS;
extern const uint8_t SCREEN_WIDTH; extern const uint8_t SCREEN_WIDTH;
@ -25,8 +23,6 @@ extern const uint8_t OLED_DATA_END;
extern const char* apiUrl; extern const char* apiUrl;
extern const uint8_t webserverPort; extern const uint8_t webserverPort;
extern uint8_t autoSetBambuAmsCounter;
extern const unsigned char wifi_on[]; extern const unsigned char wifi_on[];
extern const unsigned char wifi_off[]; extern const unsigned char wifi_off[];
extern const unsigned char cloud_on[]; extern const unsigned char cloud_on[];

View File

@ -20,9 +20,9 @@ void setupDisplay() {
// the library initializes this with an Adafruit splash screen. // the library initializes this with an Adafruit splash screen.
display.setTextColor(WHITE); display.setTextColor(WHITE);
display.display(); display.display();
delay(1000); // Pause for 2 seconds
oledShowTopRow(); oledShowTopRow();
oledShowMessage("FilaMan v" + String(VERSION)); delay(2000);
vTaskDelay(2000 / portTICK_PERIOD_MS);
} }
void oledclearline() { void oledclearline() {
@ -117,6 +117,7 @@ std::vector<String> splitTextIntoLines(String text, uint8_t textSize) {
lines.push_back(currentLine); lines.push_back(currentLine);
} }
Serial.println(lines.size());
return lines; return lines;
} }
@ -139,9 +140,8 @@ void oledShowMultilineMessage(String message, uint8_t size) {
int totalHeight = lines.size() * lineHeight; int totalHeight = lines.size() * lineHeight;
int startY = OLED_DATA_START + ((OLED_DATA_END - OLED_DATA_START - totalHeight) / 2); 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++) { for (size_t i = 0; i < lines.size(); i++) {
display.setCursor(oled_center_h(lines[i]), startY + (i * lineHeight) + (i == 1 ? lineDistance : 0)); display.setCursor(oled_center_h(lines[i]), startY + (i * lineHeight));
display.print(lines[i]); display.print(lines[i]);
} }
@ -177,7 +177,7 @@ void oledShowTopRow() {
display.drawBitmap(50, 0, bitmap_off , 16, 16, WHITE); display.drawBitmap(50, 0, bitmap_off , 16, 16, WHITE);
} }
if (spoolmanApiState != API_INIT) { if (spoolman_connected == 1) {
display.drawBitmap(80, 0, bitmap_spoolman_on , 16, 16, WHITE); display.drawBitmap(80, 0, bitmap_spoolman_on , 16, 16, WHITE);
} else { } else {
display.drawBitmap(80, 0, bitmap_off , 16, 16, WHITE); display.drawBitmap(80, 0, bitmap_off , 16, 16, WHITE);

View File

@ -1,4 +1,6 @@
#include <Arduino.h> #include <Arduino.h>
#include <DNSServer.h>
#include <ESPmDNS.h>
#include <Wire.h> #include <Wire.h>
#include <WiFi.h> #include <WiFi.h>
@ -13,22 +15,12 @@
#include "esp_task_wdt.h" #include "esp_task_wdt.h"
#include "commonFS.h" #include "commonFS.h"
bool mainTaskWasPaused = 0;
uint8_t scaleTareCounter = 0;
bool touchSensorConnected = false;
// ##### SETUP ##### // ##### SETUP #####
void setup() { void setup() {
Serial.begin(115200); 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 // Initialize SPIFFS
initializeFileSystem(); initializeSPIFFS();
// Start Display // Start Display
setupDisplay(); setupDisplay();
@ -37,27 +29,30 @@ void setup() {
initWiFi(); initWiFi();
// Webserver // Webserver
Serial.println("Starte Webserver");
setupWebserver(server); setupWebserver(server);
// Spoolman API // Spoolman API
// api.cpp
initSpoolman(); initSpoolman();
// Bambu MQTT // Bambu MQTT
// bambu.cpp
setupMqtt(); setupMqtt();
// NFC Reader // 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");
startNfc(); startNfc();
// Touch Sensor start_scale();
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 // WDT initialisieren mit 10 Sekunden Timeout
bool panic = true; // Wenn true, löst ein WDT-Timeout einen System-Panik aus bool panic = true; // Wenn true, löst ein WDT-Timeout einen System-Panik aus
@ -65,123 +60,55 @@ void setup() {
// Aktuellen Task (loopTask) zum Watchdog hinzufügen // Aktuellen Task (loopTask) zum Watchdog hinzufügen
esp_task_wdt_add(NULL); 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; unsigned long lastWeightReadTime = 0;
const unsigned long weightReadInterval = 1000; // 1 second const unsigned long weightReadInterval = 1000; // 1 second
unsigned long lastAutoSetBambuAmsTime = 0; unsigned long lastAmsSendTime = 0;
const unsigned long autoSetBambuAmsInterval = 1000; // 1 second const unsigned long amsSendInterval = 60000; // 1 minute
uint8_t autoAmsCounter = 0;
uint8_t weightSend = 0; uint8_t weightSend = 0;
int16_t lastWeight = 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 ##### // ##### PROGRAM START #####
void loop() { 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(); unsigned long currentMillis = millis();
// Überprüfe den Status des Touch Sensors // Send AMS Data min every Minute
if (touchSensorConnected && digitalRead(TTP223_PIN) == HIGH && currentMillis - lastButtonPress > debounceDelay) if (currentMillis - lastAmsSendTime >= amsSendInterval) {
{ lastAmsSendTime = currentMillis;
lastButtonPress = currentMillis; sendAmsData(nullptr);
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 // Ausgabe der Waage auf Display
if(pauseMainTask == 0) if (pauseMainTask == 0 && weight != lastWeight && hasReadRfidTag == 0)
{ {
if (mainTaskWasPaused || (weight != lastWeight && nfcReaderState == NFC_IDLE && (!autoSendToBambu || autoSetToBambuSpoolId == 0))) (weight < 0) ? oledShowMessage("!! -1") : oledShowWeight(weight);
{
(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 // Wenn Timer abgelaufen und nicht gerade ein RFID-Tag geschrieben wird
if (currentMillis - lastWeightReadTime >= weightReadInterval && nfcReaderState < NFC_WRITING) if (currentMillis - lastWeightReadTime >= weightReadInterval && hasReadRfidTag < 3)
{ {
lastWeightReadTime = currentMillis; lastWeightReadTime = currentMillis;
// Prüfen ob die Waage korrekt genullt ist // Prüfen ob die Waage korrekt genullt ist
// Abweichung von 2g ignorieren if ((weight > 0 && weight < 5) || weight < 0)
if (autoTare && (weight > 2 && weight < 7) || weight < -2)
{ {
scale_tare_counter++; scale_tare_counter++;
} }
@ -191,7 +118,7 @@ void loop() {
} }
// Prüfen ob das Gewicht gleich bleibt und dann senden // Prüfen ob das Gewicht gleich bleibt und dann senden
if (abs(weight - lastWeight) <= 2 && weight > 5) if (weight == lastWeight && weight > 5)
{ {
weigthCouterToApi++; weigthCouterToApi++;
} }
@ -203,8 +130,7 @@ void loop() {
} }
// reset weight counter after writing tag // reset weight counter after writing tag
// TBD: what exactly is the logic behind this? if (currentMillis - lastWeightReadTime >= weightReadInterval && hasReadRfidTag > 1)
if (currentMillis - lastWeightReadTime >= weightReadInterval && nfcReaderState != NFC_IDLE && nfcReaderState != NFC_READ_SUCCESS)
{ {
weigthCouterToApi = 0; weigthCouterToApi = 0;
} }
@ -212,19 +138,13 @@ void loop() {
lastWeight = weight; lastWeight = weight;
// Wenn ein Tag mit SM id erkannte wurde und der Waage Counter anspricht an SM Senden // Wenn ein Tag mit SM id erkannte wurde und der Waage Counter anspricht an SM Senden
if (spoolId != "" && weigthCouterToApi > 3 && weightSend == 0 && nfcReaderState == NFC_READ_SUCCESS) { if (spoolId != "" && weigthCouterToApi > 3 && weightSend == 0 && hasReadRfidTag == 1) {
oledShowIcon("loading"); oledShowIcon("loading");
if (updateSpoolWeight(spoolId, weight)) if (updateSpoolWeight(spoolId, weight))
{ {
oledShowIcon("success"); oledShowIcon("success");
vTaskDelay(2000 / portTICK_PERIOD_MS); vTaskDelay(2000 / portTICK_PERIOD_MS);
weightSend = 1; weightSend = 1;
autoSetToBambuSpoolId = spoolId.toInt();
if (octoEnabled)
{
updateSpoolOcto(autoSetToBambuSpoolId);
}
} }
else else
{ {
@ -233,5 +153,6 @@ void loop() {
} }
} }
yield();
esp_task_wdt_reset(); esp_task_wdt_reset();
} }

View File

@ -18,7 +18,7 @@ String spoolId = "";
String nfcJsonData = ""; String nfcJsonData = "";
volatile bool pauseBambuMqttTask = false; volatile bool pauseBambuMqttTask = false;
volatile nfcReaderStateType nfcReaderState = NFC_IDLE; volatile uint8_t hasReadRfidTag = 0;
// 0 = nicht gelesen // 0 = nicht gelesen
// 1 = erfolgreich gelesen // 1 = erfolgreich gelesen
// 2 = fehler beim Lesen // 2 = fehler beim Lesen
@ -44,6 +44,8 @@ void payloadToJson(uint8_t *data) {
DeserializationError error = deserializeJson(doc, jsonString); DeserializationError error = deserializeJson(doc, jsonString);
if (!error) { if (!error) {
const char* version = doc["version"];
const char* protocol = doc["protocol"];
const char* color_hex = doc["color_hex"]; const char* color_hex = doc["color_hex"];
const char* type = doc["type"]; const char* type = doc["type"];
int min_temp = doc["min_temp"]; int min_temp = doc["min_temp"];
@ -53,6 +55,8 @@ void payloadToJson(uint8_t *data) {
Serial.println(); Serial.println();
Serial.println("-----------------"); Serial.println("-----------------");
Serial.println("JSON-Parsed Data:"); Serial.println("JSON-Parsed Data:");
Serial.println(version);
Serial.println(protocol);
Serial.println(color_hex); Serial.println(color_hex);
Serial.println(type); Serial.println(type);
Serial.println(min_temp); Serial.println(min_temp);
@ -89,16 +93,8 @@ bool formatNdefTag() {
return success; 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 ntag2xx_WriteNDEF(const char *payload) {
uint16_t tagSize = readTagSize(); uint8_t tagSize = 240; // 144 bytes is maximum for NTAG213
Serial.print("Tag Size: ");Serial.println(tagSize); Serial.print("Tag Size: ");Serial.println(tagSize);
uint8_t pageBuffer[4] = {0, 0, 0, 0}; uint8_t pageBuffer[4] = {0, 0, 0, 0};
@ -140,8 +136,6 @@ uint8_t ntag2xx_WriteNDEF(const char *payload) {
if (combinedData == NULL) if (combinedData == NULL)
{ {
Serial.println("Fehler: Nicht genug Speicher vorhanden."); Serial.println("Fehler: Nicht genug Speicher vorhanden.");
oledShowMessage("Tag too small");
vTaskDelay(2000 / portTICK_PERIOD_MS);
return 0; return 0;
} }
@ -242,14 +236,12 @@ void writeJsonToTag(void *parameter) {
Serial.println("Erstelle NDEF-Message..."); Serial.println("Erstelle NDEF-Message...");
Serial.println(payload); Serial.println(payload);
nfcReaderState = NFC_WRITING; hasReadRfidTag = 3;
vTaskSuspend(RfidReaderTask); vTaskSuspend(RfidReaderTask);
vTaskDelay(50 / portTICK_PERIOD_MS); vTaskDelay(500 / portTICK_PERIOD_MS);
//pauseBambuMqttTask = true; //pauseBambuMqttTask = true;
// aktualisieren der Website wenn sich der Status ändert // aktualisieren der Website wenn sich der Status ändert
sendNfcData(nullptr); sendNfcData(nullptr);
vTaskDelay(100 / portTICK_PERIOD_MS);
oledShowMessage("Waiting for NFC-Tag"); oledShowMessage("Waiting for NFC-Tag");
// Wait 10sec for tag // Wait 10sec for tag
@ -288,7 +280,7 @@ void writeJsonToTag(void *parameter) {
//oledShowMessage("NFC-Tag written"); //oledShowMessage("NFC-Tag written");
oledShowIcon("success"); oledShowIcon("success");
vTaskDelay(1000 / portTICK_PERIOD_MS); vTaskDelay(1000 / portTICK_PERIOD_MS);
nfcReaderState = NFC_WRITE_SUCCESS; hasReadRfidTag = 5;
// aktualisieren der Website wenn sich der Status ändert // aktualisieren der Website wenn sich der Status ändert
sendNfcData(nullptr); sendNfcData(nullptr);
pauseBambuMqttTask = false; pauseBambuMqttTask = false;
@ -310,7 +302,7 @@ void writeJsonToTag(void *parameter) {
Serial.println("Fehler beim Schreiben der NDEF-Message auf den Tag"); Serial.println("Fehler beim Schreiben der NDEF-Message auf den Tag");
oledShowIcon("failed"); oledShowIcon("failed");
vTaskDelay(2000 / portTICK_PERIOD_MS); vTaskDelay(2000 / portTICK_PERIOD_MS);
nfcReaderState = NFC_WRITE_ERROR; hasReadRfidTag = 4;
} }
} }
else else
@ -318,7 +310,7 @@ void writeJsonToTag(void *parameter) {
Serial.println("Fehler: Kein Tag zu schreiben gefunden."); Serial.println("Fehler: Kein Tag zu schreiben gefunden.");
oledShowMessage("No NFC-Tag found"); oledShowMessage("No NFC-Tag found");
vTaskDelay(2000 / portTICK_PERIOD_MS); vTaskDelay(2000 / portTICK_PERIOD_MS);
nfcReaderState = NFC_IDLE; hasReadRfidTag = 0;
} }
sendWriteResult(nullptr, success); sendWriteResult(nullptr, success);
@ -334,12 +326,12 @@ void startWriteJsonToTag(const char* payload) {
char* payloadCopy = strdup(payload); char* payloadCopy = strdup(payload);
// Task nicht mehrfach starten // Task nicht mehrfach starten
if (nfcReaderState != NFC_WRITING) { if (hasReadRfidTag != 3) {
// Erstelle die Task // Erstelle die Task
xTaskCreate( xTaskCreate(
writeJsonToTag, // Task-Funktion writeJsonToTag, // Task-Funktion
"WriteJsonToTagTask", // Task-Name "WriteJsonToTagTask", // Task-Name
5115, // Stackgröße in Bytes 4096, // Stackgröße in Bytes
(void*)payloadCopy, // Parameter (void*)payloadCopy, // Parameter
rfidWriteTaskPrio, // Priorität rfidWriteTaskPrio, // Priorität
NULL // Task-Handle (nicht benötigt) NULL // Task-Handle (nicht benötigt)
@ -351,7 +343,7 @@ void scanRfidTask(void * parameter) {
Serial.println("RFID Task gestartet"); Serial.println("RFID Task gestartet");
for(;;) { for(;;) {
// Wenn geschrieben wird Schleife aussetzen // Wenn geschrieben wird Schleife aussetzen
if (nfcReaderState != NFC_WRITING) if (hasReadRfidTag != 3)
{ {
yield(); yield();
@ -363,31 +355,33 @@ void scanRfidTask(void * parameter) {
foundNfcTag(nullptr, success); foundNfcTag(nullptr, success);
if (success && nfcReaderState != NFC_READ_SUCCESS) if (success && hasReadRfidTag != 1)
{ {
// Display some basic information about the card // Display some basic information about the card
Serial.println("Found an ISO14443A card"); Serial.println("Found an ISO14443A card");
nfcReaderState = NFC_READING; hasReadRfidTag = 6;
oledShowIcon("transfer"); oledShowIcon("transfer");
vTaskDelay(500 / portTICK_PERIOD_MS); vTaskDelay(500 / portTICK_PERIOD_MS);
if (uidLength == 7) if (uidLength == 7)
{ {
uint16_t tagSize = readTagSize(); uint8_t data[256];
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) // We probably have an NTAG2xx card (though it could be Ultralight as well)
Serial.println("Seems to be an NTAG2xx tag (7 byte UID)"); Serial.println("Seems to be an NTAG2xx tag (7 byte UID)");
uint8_t numPages = readTagSize()/4; for (uint8_t i = 0; i < 45; i++) {
for (uint8_t i = 4; i < 4+numPages; i++) { /*
if (!nfc.ntag2xx_ReadPage(i, data+(i-4) * 4)) if (i < uidLength) {
uidString += String(uid[i], HEX);
if (i < uidLength - 1) {
uidString += ":"; // Optional: Trennzeichen hinzufügen
}
}
*/
if (!nfc.mifareclassic_ReadDataBlock(i, data + (i - 4) * 4))
{ {
break; // Stop if reading fails break; // Stop if reading fails
} }
@ -406,20 +400,13 @@ void scanRfidTask(void * parameter) {
{ {
oledShowMessage("NFC-Tag unknown"); oledShowMessage("NFC-Tag unknown");
vTaskDelay(2000 / portTICK_PERIOD_MS); vTaskDelay(2000 / portTICK_PERIOD_MS);
nfcReaderState = NFC_READ_ERROR; hasReadRfidTag = 2;
} }
else else
{ {
nfcReaderState = NFC_READ_SUCCESS; hasReadRfidTag = 1;
} }
free(data);
}
else
{
oledShowMessage("NFC-Tag read error");
nfcReaderState = NFC_READ_ERROR;
}
} }
else else
{ {
@ -427,13 +414,13 @@ void scanRfidTask(void * parameter) {
} }
} }
if (!success && nfcReaderState != NFC_IDLE) if (!success && hasReadRfidTag > 0)
{ {
nfcReaderState = NFC_IDLE; hasReadRfidTag = 0;
//uidString = ""; //uidString = "";
nfcJsonData = ""; nfcJsonData = "";
Serial.println("Tag entfernt"); Serial.println("Tag entfernt");
if (!autoSendToBambu) oledShowWeight(weight); oledShowWeight(0);
} }
// aktualisieren der Website wenn sich der Status ändert // aktualisieren der Website wenn sich der Status ändert
@ -469,7 +456,7 @@ void startNfc() {
BaseType_t result = xTaskCreatePinnedToCore( BaseType_t result = xTaskCreatePinnedToCore(
scanRfidTask, /* Function to implement the task */ scanRfidTask, /* Function to implement the task */
"RfidReader", /* Name of the task */ "RfidReader", /* Name of the task */
5115, /* Stack size in words */ 10000, /* Stack size in words */
NULL, /* Task input parameter */ NULL, /* Task input parameter */
rfidTaskPrio, /* Priority of the task */ rfidTaskPrio, /* Priority of the task */
&RfidReaderTask, /* Task handle. */ &RfidReaderTask, /* Task handle. */

View File

@ -3,16 +3,6 @@
#include <Arduino.h> #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 startNfc();
void scanRfidTask(void * parameter); void scanRfidTask(void * parameter);
void startWriteJsonToTag(const char* payload); void startWriteJsonToTag(const char* payload);
@ -20,9 +10,7 @@ void startWriteJsonToTag(const char* payload);
extern TaskHandle_t RfidReaderTask; extern TaskHandle_t RfidReaderTask;
extern String nfcJsonData; extern String nfcJsonData;
extern String spoolId; extern String spoolId;
extern volatile nfcReaderStateType nfcReaderState; extern volatile uint8_t hasReadRfidTag;
extern volatile bool pauseBambuMqttTask; extern volatile bool pauseBambuMqttTask;
#endif #endif

View File

@ -1,266 +1,78 @@
#include <Arduino.h> #include <Arduino.h>
#include <website.h> #include "ota.h"
#include <commonFS.h> #include <Update.h>
#include "scale.h" #include <SPIFFS.h>
#include "commonFS.h"
#include "bambu.h" #include "bambu.h"
#include "scale.h"
#include "nfc.h" #include "nfc.h"
static bool tasksAreStopped = false;
// Globale Variablen für Config Backups hinzufügen void stopAllTasks() {
String bambuCredentialsBackup; Serial.println("Stopping RFID Reader");
String spoolmanUrlBackup; if (RfidReaderTask) vTaskSuspend(RfidReaderTask);
Serial.println("Stopping Bambu");
// Globale Variable für den Update-Typ if (BambuMqttTask) vTaskSuspend(BambuMqttTask);
static int currentUpdateCommand = 0; Serial.println("Stopping Scale");
if (ScaleTask) vTaskSuspend(ScaleTask);
// Globale Update-Variablen vTaskDelay(100 / portTICK_PERIOD_MS);
static size_t updateTotalSize = 0; Serial.println("All tasks stopped");
static size_t updateWritten = 0;
static bool isSpiffsUpdate = false;
/**
* Compares two version strings and determines if version1 is less than version2
*
* @param version1 First version string (format: x.y.z)
* @param version2 Second version string (format: x.y.z)
* @return true if version1 is less than version2
*/
bool isVersionLessThan(const String& version1, const String& version2) {
int major1 = 0, minor1 = 0, patch1 = 0;
int major2 = 0, minor2 = 0, patch2 = 0;
// Parse version1
sscanf(version1.c_str(), "%d.%d.%d", &major1, &minor1, &patch1);
// Parse version2
sscanf(version2.c_str(), "%d.%d.%d", &major2, &minor2, &patch2);
// Compare major version
if (major1 < major2) return true;
if (major1 > major2) return false;
// Major versions equal, compare minor
if (minor1 < minor2) return true;
if (minor1 > minor2) return false;
// Minor versions equal, compare patch
return patch1 < patch2;
} }
void backupJsonConfigs() { void handleOTAUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
// Bambu Credentials backup static size_t contentLength = 0;
if (LittleFS.exists("/bambu_credentials.json")) {
File file = LittleFS.open("/bambu_credentials.json", "r");
if (file) {
bambuCredentialsBackup = file.readString();
file.close();
Serial.println("Bambu credentials backed up");
}
}
// Spoolman URL backup
if (LittleFS.exists("/spoolman_url.json")) {
File file = LittleFS.open("/spoolman_url.json", "r");
if (file) {
spoolmanUrlBackup = file.readString();
file.close();
Serial.println("Spoolman URL backed up");
}
}
}
void restoreJsonConfigs() {
// Restore Bambu credentials
if (bambuCredentialsBackup.length() > 0) {
File file = LittleFS.open("/bambu_credentials.json", "w");
if (file) {
file.print(bambuCredentialsBackup);
file.close();
Serial.println("Bambu credentials restored");
}
bambuCredentialsBackup = ""; // Clear backup
}
// Restore Spoolman URL
if (spoolmanUrlBackup.length() > 0) {
File file = LittleFS.open("/spoolman_url.json", "w");
if (file) {
file.print(spoolmanUrlBackup);
file.close();
Serial.println("Spoolman URL restored");
}
spoolmanUrlBackup = ""; // Clear backup
}
}
void espRestart() {
yield();
vTaskDelay(5000 / portTICK_PERIOD_MS);
ESP.restart();
}
void sendUpdateProgress(int progress, const char* status = nullptr, const char* message = nullptr) {
static int lastSentProgress = -1;
// Verhindere zu häufige Updates
if (progress == lastSentProgress && !status && !message) {
return;
}
String progressMsg = "{\"type\":\"updateProgress\",\"progress\":" + String(progress);
if (status) {
progressMsg += ",\"status\":\"" + String(status) + "\"";
}
if (message) {
progressMsg += ",\"message\":\"" + String(message) + "\"";
}
progressMsg += "}";
if (progress >= 100) {
// Sende die Nachricht nur einmal für den Abschluss
ws.textAll("{\"type\":\"updateProgress\",\"progress\":100,\"status\":\"success\",\"message\":\"Update successful! Restarting device...\"}");
delay(50);
}
// Sende die Nachricht mehrmals mit Verzögerung für wichtige Updates
if (status || abs(progress - lastSentProgress) >= 10 || progress == 100) {
for (int i = 0; i < 2; i++) {
ws.textAll(progressMsg);
delay(100); // Längerer Delay zwischen Nachrichten
}
} else {
ws.textAll(progressMsg);
delay(50);
}
lastSentProgress = progress;
}
void handleUpdate(AsyncWebServer &server) {
AsyncCallbackWebHandler* updateHandler = new AsyncCallbackWebHandler();
updateHandler->setUri("/update");
updateHandler->setMethod(HTTP_POST);
// Check if current version is less than defined TOOLVERSION before proceeding with update
if (isVersionLessThan(VERSION, TOOLDVERSION)) {
updateHandler->onRequest([](AsyncWebServerRequest *request) {
request->send(400, "application/json",
"{\"success\":false,\"message\":\"Your current version is too old. Please perform a full upgrade.\"}");
});
server.addHandler(updateHandler);
return;
}
updateHandler->onUpload([](AsyncWebServerRequest *request, String filename,
size_t index, uint8_t *data, size_t len, bool final) {
// 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) { if (!index) {
updateTotalSize = request->contentLength(); contentLength = request->contentLength();
updateWritten = 0; Serial.printf("Update Start: %s (size: %u bytes)\n", filename.c_str(), contentLength);
isSpiffsUpdate = (filename.indexOf("website") > -1);
if (isSpiffsUpdate) { if (contentLength == 0) {
// Backup vor dem Update request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"Invalid file size\"}");
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; return;
} }
sendUpdateProgress(5, "starting", "Starting SPIFFS update...");
vTaskDelay(200 / portTICK_PERIOD_MS); // Stoppe alle Tasks vor dem Update
} else { if (!tasksAreStopped && (RfidReaderTask || BambuMqttTask || ScaleTask)) {
if (!Update.begin(updateTotalSize)) { stopAllTasks();
request->send(400, "application/json", "{\"success\":false,\"message\":\"Update initialization failed\"}"); tasksAreStopped = true;
return;
}
sendUpdateProgress(0, "starting", "Starting firmware update...");
vTaskDelay(200 / portTICK_PERIOD_MS);
}
} }
if (len) { // Für full.bin keine Magic Byte Prüfung
bool isFullImage = (contentLength > 0x300000);
if (!isFullImage && data[0] != 0xE9) {
Serial.printf("Wrong magic byte: 0x%02X (expected 0xE9)\n", data[0]);
request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"Invalid firmware format\"}");
return;
}
// Bei full.bin UPDATE_SIZE_UNKNOWN verwenden
if (!Update.begin(isFullImage ? UPDATE_SIZE_UNKNOWN : contentLength)) {
Update.printError(Serial);
request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"OTA could not begin\"}");
return;
}
Serial.printf("Starting %s update\n", isFullImage ? "full" : "firmware");
}
// Schreibe Update-Daten
if (Update.write(data, len) != len) { if (Update.write(data, len) != len) {
request->send(400, "application/json", "{\"success\":false,\"message\":\"Write failed\"}"); Update.printError(Serial);
request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"OTA write failed\"}");
return; return;
} }
updateWritten += len; // Update abschließen
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 (final) {
if (Update.end(true)) { if (!Update.end(true)) {
if (isSpiffsUpdate) { Update.printError(Serial);
restoreJsonConfigs(); request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"OTA end failed\"}");
}
} 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; return;
} }
Serial.println("Update successful, restarting...");
// Erste 100% Nachricht request->send(200, "application/json", "{\"status\":\"success\",\"message\":\"Update successful! Device will restart...\",\"restart\":true}");
ws.textAll("{\"type\":\"updateProgress\",\"progress\":100,\"status\":\"success\",\"message\":\"Update successful! Restarting device...\"}"); delay(500);
vTaskDelay(2000 / portTICK_PERIOD_MS); ESP.restart();
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);
} }
}

View File

@ -1,9 +1,14 @@
#ifndef OTA_H #ifndef OTA_H
#define OTA_H #define OTA_H
#include <ArduinoOTA.h>
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
void handleUpdate(AsyncWebServer &server); // Update size unknown constant, falls nicht bereits definiert
#ifndef UPDATE_SIZE_UNKNOWN
#define UPDATE_SIZE_UNKNOWN 0xFFFFFFFF
#endif
void stopAllTasks();
void handleOTAUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
#endif #endif

View File

@ -3,9 +3,9 @@
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include "config.h" #include "config.h"
#include "HX711.h" #include "HX711.h"
#include <EEPROM.h>
#include "display.h" #include "display.h"
#include "esp_task_wdt.h" #include "esp_task_wdt.h"
#include <Preferences.h>
HX711 scale; HX711 scale;
@ -15,30 +15,9 @@ int16_t weight = 0;
uint8_t weigthCouterToApi = 0; uint8_t weigthCouterToApi = 0;
uint8_t scale_tare_counter = 0; uint8_t scale_tare_counter = 0;
bool scaleTareRequest = false;
uint8_t pauseMainTask = 0; 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 ##### // ##### 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() { uint8_t tareScale() {
Serial.println("Tare scale"); Serial.println("Tare scale");
scale.tare(); scale.tare();
@ -50,69 +29,39 @@ void scale_loop(void * parameter) {
Serial.println("++++++++++++++++++++++++++++++"); Serial.println("++++++++++++++++++++++++++++++");
Serial.println("Scale Loop started"); Serial.println("Scale Loop started");
Serial.println("++++++++++++++++++++++++++++++"); Serial.println("++++++++++++++++++++++++++++++");
for(;;) { for(;;) {
if (scale.is_ready()) if (scale.is_ready())
{ {
// Waage automatisch Taren, wenn zu lange Abweichung // Waage nochmal Taren, wenn zu lange Abweichung
if (autoTare && scale_tare_counter >= 5) if (scale_tare_counter >= 5)
{ {
Serial.println("Auto Tare scale");
scale.tare(); scale.tare();
scale_tare_counter = 0; 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()); weight = round(scale.get_units());
} }
vTaskDelay(pdMS_TO_TICKS(100)); vTaskDelay(pdMS_TO_TICKS(100)); // Verzögerung, um die CPU nicht zu überlasten
} }
} }
void start_scale(bool touchSensorConnected) { void start_scale() {
Serial.println("Prüfe Calibration Value"); Serial.println("Prüfe Calibration Value");
float calibrationValue; 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
// NVS lesen EEPROM.begin(512);
preferences.begin(NVS_NAMESPACE, true); // true = readonly EEPROM.get(calVal_eepromAdress, calibrationValue); // uncomment this if you want to fetch the calibration value from eeprom
calibrationValue = preferences.getFloat(NVS_KEY_CALIBRATION, defaultScaleCalibrationValue);
// auto Tare //calibrationValue = EEPROM.read(calVal_eepromAdress);
// Wenn Touch Sensor verbunden, dann autoTare auf false setzen
// Danach prüfen was in NVS gespeichert ist
autoTare = (touchSensorConnected) ? false : true;
autoTare = preferences.getBool(NVS_KEY_AUTOTARE, autoTare);
preferences.end();
Serial.print("Read Scale Calibration Value "); Serial.print("Read Scale Calibration Value ");
Serial.println(calibrationValue); Serial.println(calibrationValue);
scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN); scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
if (isnan(calibrationValue) || calibrationValue < 1) { if (isnan(calibrationValue) || calibrationValue < 1) calibrationValue = defaultScaleCalibrationValue;
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"); oledShowMessage("Scale Tare Please remove all");
for (uint16_t i = 0; i < 2000; i++) { for (uint16_t i = 0; i < 2000; i++) {
@ -134,7 +83,7 @@ void start_scale(bool touchSensorConnected) {
BaseType_t result = xTaskCreatePinnedToCore( BaseType_t result = xTaskCreatePinnedToCore(
scale_loop, /* Function to implement the task */ scale_loop, /* Function to implement the task */
"ScaleLoop", /* Name of the task */ "ScaleLoop", /* Name of the task */
2048, /* Stack size in words */ 10000, /* Stack size in words */
NULL, /* Task input parameter */ NULL, /* Task input parameter */
scaleTaskPrio, /* Priority of the task */ scaleTaskPrio, /* Priority of the task */
&ScaleTask, /* Task handle. */ &ScaleTask, /* Task handle. */
@ -148,18 +97,15 @@ void start_scale(bool touchSensorConnected) {
} }
uint8_t calibrate_scale() { uint8_t calibrate_scale() {
uint8_t returnState = 0; long newCalibrationValue;
float newCalibrationValue;
vTaskSuspend(RfidReaderTask);
vTaskSuspend(ScaleTask);
//vTaskSuspend(RfidReaderTask);
vTaskDelete(RfidReaderTask);
pauseBambuMqttTask = true; pauseBambuMqttTask = true;
pauseMainTask = 1; pauseMainTask = 1;
if (scale.wait_ready_timeout(1000)) if (scale.wait_ready_timeout(1000))
{ {
scale.set_scale(); scale.set_scale();
oledShowMessage("Step 1 empty Scale"); oledShowMessage("Step 1 empty Scale");
@ -181,7 +127,7 @@ uint8_t calibrate_scale() {
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
float newCalibrationValue = scale.get_units(10); long newCalibrationValue = scale.get_units(10);
Serial.print("Result: "); Serial.print("Result: ");
Serial.println(newCalibrationValue); Serial.println(newCalibrationValue);
@ -191,36 +137,30 @@ uint8_t calibrate_scale() {
{ {
Serial.print("New calibration value has been set to: "); Serial.print("New calibration value has been set to: ");
Serial.println(newCalibrationValue); Serial.println(newCalibrationValue);
Serial.print("Save this value to EEPROM adress ");
Serial.println(calVal_eepromAdress);
// Speichern mit NVS //EEPROM.put(calVal_eepromAdress, newCalibrationValue);
preferences.begin(NVS_NAMESPACE, false); // false = readwrite EEPROM.put(calVal_eepromAdress, newCalibrationValue);
preferences.putFloat(NVS_KEY_CALIBRATION, newCalibrationValue); EEPROM.commit();
preferences.end();
// Verifizieren EEPROM.get(calVal_eepromAdress, newCalibrationValue);
preferences.begin(NVS_NAMESPACE, true); //newCalibrationValue = EEPROM.read(calVal_eepromAdress);
float verifyValue = preferences.getFloat(NVS_KEY_CALIBRATION, 0);
preferences.end();
Serial.print("Verified stored value: "); Serial.print("Read Value ");
Serial.println(verifyValue); Serial.println(newCalibrationValue);
Serial.println("End calibration, remove weight"); Serial.println("End calibration, revome weight");
oledShowMessage("Remove weight"); oledShowMessage("Remove weight");
scale.set_scale(newCalibrationValue);
for (uint16_t i = 0; i < 2000; i++) { for (uint16_t i = 0; i < 2000; i++) {
yield(); yield();
vTaskDelay(pdMS_TO_TICKS(1)); vTaskDelay(pdMS_TO_TICKS(1));
esp_task_wdt_reset(); 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++) { for (uint16_t i = 0; i < 2000; i++) {
yield(); yield();
@ -228,9 +168,8 @@ uint8_t calibrate_scale() {
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
returnState = 1; //ESP.restart();
} }
else else
{ {
{ {
@ -243,7 +182,7 @@ uint8_t calibrate_scale() {
vTaskDelay(pdMS_TO_TICKS(1)); vTaskDelay(pdMS_TO_TICKS(1));
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
returnState = 0; return 0;
} }
} }
} }
@ -258,13 +197,17 @@ uint8_t calibrate_scale() {
vTaskDelay(pdMS_TO_TICKS(1)); vTaskDelay(pdMS_TO_TICKS(1));
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
returnState = 0; return 0;
} }
vTaskResume(RfidReaderTask); oledShowMessage("Scale Ready");
vTaskResume(ScaleTask);
Serial.println("starte Scale Task");
start_scale();
pauseBambuMqttTask = false; pauseBambuMqttTask = false;
pauseMainTask = 0; pauseMainTask = 0;
return returnState; return 1;
} }

View File

@ -4,8 +4,8 @@
#include <Arduino.h> #include <Arduino.h>
#include "HX711.h" #include "HX711.h"
uint8_t setAutoTare(bool autoTareValue);
uint8_t start_scale(bool touchSensorConnected); void start_scale();
uint8_t calibrate_scale(); uint8_t calibrate_scale();
uint8_t tareScale(); uint8_t tareScale();
@ -13,10 +13,7 @@ extern HX711 scale;
extern int16_t weight; extern int16_t weight;
extern uint8_t weigthCouterToApi; extern uint8_t weigthCouterToApi;
extern uint8_t scale_tare_counter; extern uint8_t scale_tare_counter;
extern uint8_t scaleTareRequest;
extern uint8_t pauseMainTask; extern uint8_t pauseMainTask;
extern uint8_t scaleCalibrated;
extern bool autoTare;
extern TaskHandle_t ScaleTask; extern TaskHandle_t ScaleTask;

View File

@ -7,57 +7,44 @@
#include "nfc.h" #include "nfc.h"
#include "scale.h" #include "scale.h"
#include "esp_task_wdt.h" #include "esp_task_wdt.h"
#include <Update.h>
#include "display.h"
#include "ota.h" #include "ota.h"
#ifndef VERSION
#define VERSION "1.1.0"
#endif
// Cache-Control Header definieren // Cache-Control Header definieren
#define CACHE_CONTROL "max-age=604800" // Cache für 1 Woche #define CACHE_CONTROL "max-age=31536000" // Cache für 1 Jahr
AsyncWebServer server(webserverPort); AsyncWebServer server(webserverPort);
AsyncWebSocket ws("/ws"); AsyncWebSocket ws("/ws");
uint8_t lastSuccess = 0; uint8_t lastSuccess = 0;
nfcReaderStateType lastnfcReaderState = NFC_IDLE; uint8_t lastHasReadRfidTag = 0;
void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) { void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
if (type == WS_EVT_CONNECT) { if (type == WS_EVT_CONNECT) {
Serial.println("Neuer Client verbunden!"); Serial.println("Neuer Client verbunden!");
// Sende die AMS-Daten an den neuen Client // Sende die AMS-Daten an den neuen Client
if (!bambuDisabled) sendAmsData(client); sendAmsData(client);
sendNfcData(client); sendNfcData(client);
foundNfcTag(client, 0); foundNfcTag(client, 0);
sendWriteResult(client, 3); sendWriteResult(client, 3);
} else if (type == WS_EVT_DISCONNECT) { } else if (type == WS_EVT_DISCONNECT) {
Serial.println("Client getrennt."); 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) { } else if (type == WS_EVT_DATA) {
String message = String((char*)data); String message = String((char*)data);
JsonDocument doc; JsonDocument doc;
deserializeJson(doc, message); deserializeJson(doc, message);
bool spoolmanConnected = (spoolmanApiState != API_INIT);
if (doc["type"] == "heartbeat") { if (doc["type"] == "heartbeat") {
// Sende Heartbeat-Antwort // Sende Heartbeat-Antwort
ws.text(client->id(), "{" ws.text(client->id(), "{"
"\"type\":\"heartbeat\"," "\"type\":\"heartbeat\","
"\"freeHeap\":" + String(ESP.getFreeHeap()/1024) + "," "\"freeHeap\":" + String(ESP.getFreeHeap()/1024) + ","
"\"bambu_connected\":" + String(bambu_connected) + "," "\"bambu_connected\":" + String(bambu_connected) + ","
"\"spoolman_connected\":" + String(spoolmanConnected) + "" "\"spoolman_connected\":" + String(spoolman_connected) + ""
"}"); "}");
} }
else if (doc["type"] == "writeNfcTag") { else if (doc["type"] == "writeNfcTag") {
if (doc["payload"].is<JsonObject>()) { if (doc.containsKey("payload")) {
// Versuche NFC-Daten zu schreiben // Versuche NFC-Daten zu schreiben
String payloadString; String payloadString;
serializeJson(doc["payload"], payloadString); serializeJson(doc["payload"], payloadString);
@ -75,10 +62,6 @@ void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventTyp
success = calibrate_scale(); success = calibrate_scale();
} }
if (doc["payload"] == "setAutoTare") {
success = setAutoTare(doc["enabled"].as<bool>());
}
if (success) { if (success) {
ws.textAll("{\"type\":\"scale\",\"payload\":\"success\"}"); ws.textAll("{\"type\":\"scale\",\"payload\":\"success\"}");
} else { } else {
@ -101,15 +84,6 @@ void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventTyp
setBambuSpool(doc["payload"]); 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 { else {
Serial.println("Unbekannter WebSocket-Typ: " + doc["type"].as<String>()); Serial.println("Unbekannter WebSocket-Typ: " + doc["type"].as<String>());
} }
@ -119,12 +93,12 @@ void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventTyp
// Funktion zum Laden und Ersetzen des Headers in einer HTML-Datei // Funktion zum Laden und Ersetzen des Headers in einer HTML-Datei
String loadHtmlWithHeader(const char* filename) { String loadHtmlWithHeader(const char* filename) {
Serial.println("Lade HTML-Datei: " + String(filename)); Serial.println("Lade HTML-Datei: " + String(filename));
if (!LittleFS.exists(filename)) { if (!SPIFFS.exists(filename)) {
Serial.println("Fehler: Datei nicht gefunden!"); Serial.println("Fehler: Datei nicht gefunden!");
return "Fehler: Datei nicht gefunden!"; return "Fehler: Datei nicht gefunden!";
} }
File file = LittleFS.open(filename, "r"); File file = SPIFFS.open(filename, "r");
String html = file.readString(); String html = file.readString();
file.close(); file.close();
@ -145,31 +119,34 @@ void foundNfcTag(AsyncWebSocketClient *client, uint8_t success) {
} }
void sendNfcData(AsyncWebSocketClient *client) { void sendNfcData(AsyncWebSocketClient *client) {
if (lastnfcReaderState == nfcReaderState) return; if (lastHasReadRfidTag == hasReadRfidTag) return;
// TBD: Why is there no status for reading the tag? if (hasReadRfidTag == 0) {
switch(nfcReaderState){
case NFC_IDLE:
ws.textAll("{\"type\":\"nfcData\", \"payload\":{}}"); ws.textAll("{\"type\":\"nfcData\", \"payload\":{}}");
break; }
case NFC_READ_SUCCESS: else if (hasReadRfidTag == 1) {
ws.textAll("{\"type\":\"nfcData\", \"payload\":" + nfcJsonData + "}"); ws.textAll("{\"type\":\"nfcData\", \"payload\":" + nfcJsonData + "}");
break; }
case NFC_READ_ERROR: else if (hasReadRfidTag == 2)
{
ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"error\":\"Empty Tag or Data not readable\"}}"); ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"error\":\"Empty Tag or Data not readable\"}}");
break; }
case NFC_WRITING: else if (hasReadRfidTag == 3)
{
ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"info\":\"Schreibe Tag...\"}}"); ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"info\":\"Schreibe Tag...\"}}");
break; }
case NFC_WRITE_SUCCESS: else if (hasReadRfidTag == 4)
ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"info\":\"Tag erfolgreich geschrieben\"}}"); {
break;
case NFC_WRITE_ERROR:
ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"error\":\"Error writing to Tag\"}}"); ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"error\":\"Error writing to Tag\"}}");
break; }
case DEFAULT: else if (hasReadRfidTag == 5)
{
ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"info\":\"Tag erfolgreich geschrieben\"}}");
}
else
{
ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"error\":\"Something went wrong\"}}"); ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"error\":\"Something went wrong\"}}");
} }
lastnfcReaderState = nfcReaderState; lastHasReadRfidTag = hasReadRfidTag;
} }
void sendAmsData(AsyncWebSocketClient *client) { void sendAmsData(AsyncWebSocketClient *client) {
@ -179,17 +156,6 @@ void sendAmsData(AsyncWebSocketClient *client) {
} }
void setupWebserver(AsyncWebServer &server) { 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 // Lade die Spoolman-URL beim Booten
spoolmanUrl = loadSpoolmanUrl(); spoolmanUrl = loadSpoolmanUrl();
Serial.print("Geladene Spoolman-URL: "); Serial.print("Geladene Spoolman-URL: ");
@ -198,7 +164,7 @@ void setupWebserver(AsyncWebServer &server) {
// Route für about // Route für about
server.on("/about", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/about", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /about erhalten"); Serial.println("Anfrage für /about erhalten");
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/index.html.gz", "text/html"); AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/index.html.gz", "text/html");
response->addHeader("Content-Encoding", "gzip"); response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL); response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response); request->send(response);
@ -207,29 +173,33 @@ void setupWebserver(AsyncWebServer &server) {
// Route für Waage // Route für Waage
server.on("/waage", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/waage", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /waage erhalten"); Serial.println("Anfrage für /waage erhalten");
//AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/waage.html.gz", "text/html"); AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/waage.html.gz", "text/html");
//response->addHeader("Content-Encoding", "gzip"); response->addHeader("Content-Encoding", "gzip");
//response->addHeader("Cache-Control", CACHE_CONTROL); response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
String html = loadHtmlWithHeader("/waage.html");
html.replace("{{autoTare}}", (autoTare) ? "checked" : "");
request->send(200, "text/html", html);
}); });
// Route für RFID // Route für RFID
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /rfid erhalten"); Serial.println("Anfrage für /rfid erhalten");
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("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL); response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response); request->send(response);
Serial.println("RFID-Seite gesendet"); 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){ server.on("/api/url", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("API-Aufruf: /api/url"); Serial.println("API-Aufruf: /api/url");
String jsonResponse = "{\"spoolman_url\": \"" + String(spoolmanUrl) + "\"}"; String jsonResponse = "{\"spoolman_url\": \"" + String(spoolmanUrl) + "\"}";
@ -239,7 +209,7 @@ void setupWebserver(AsyncWebServer &server) {
// Route für WiFi // Route für WiFi
server.on("/wifi", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/wifi", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /wifi erhalten"); Serial.println("Anfrage für /wifi erhalten");
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/wifi.html.gz", "text/html"); AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/wifi.html.gz", "text/html");
response->addHeader("Content-Encoding", "gzip"); response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL); response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response); request->send(response);
@ -249,18 +219,13 @@ void setupWebserver(AsyncWebServer &server) {
server.on("/spoolman", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/spoolman", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /spoolman erhalten"); Serial.println("Anfrage für /spoolman erhalten");
String html = loadHtmlWithHeader("/spoolman.html"); String html = loadHtmlWithHeader("/spoolman.html");
html.replace("{{spoolmanUrl}}", (spoolmanUrl != "") ? spoolmanUrl : ""); html.replace("{{spoolmanUrl}}", spoolmanUrl);
html.replace("{{spoolmanOctoEnabled}}", octoEnabled ? "checked" : "");
html.replace("{{spoolmanOctoUrl}}", (octoUrl != "") ? octoUrl : "");
html.replace("{{spoolmanOctoToken}}", (octoToken != "") ? octoToken : "");
JsonDocument doc; JsonDocument doc;
if (loadJsonValue("/bambu_credentials.json", doc) && doc["bambu_ip"].is<String>()) if (loadJsonValue("/bambu_credentials.json", doc) && doc.containsKey("bambu_ip")) {
{
String bambuIp = doc["bambu_ip"].as<String>(); String bambuIp = doc["bambu_ip"].as<String>();
String bambuSerial = doc["bambu_serialnr"].as<String>(); String bambuSerial = doc["bambu_serialnr"].as<String>();
String bambuCode = doc["bambu_accesscode"].as<String>(); String bambuCode = doc["bambu_accesscode"].as<String>();
autoSendToBambu = doc["autoSendToBambu"].as<bool>();
bambuIp.trim(); bambuIp.trim();
bambuSerial.trim(); bambuSerial.trim();
bambuCode.trim(); bambuCode.trim();
@ -268,16 +233,6 @@ void setupWebserver(AsyncWebServer &server) {
html.replace("{{bambuIp}}", bambuIp ? bambuIp : ""); html.replace("{{bambuIp}}", bambuIp ? bambuIp : "");
html.replace("{{bambuSerial}}", bambuSerial ? bambuSerial : ""); html.replace("{{bambuSerial}}", bambuSerial ? bambuSerial : "");
html.replace("{{bambuCode}}", bambuCode ? bambuCode : ""); 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); request->send(200, "text/html", html);
@ -290,45 +245,17 @@ void setupWebserver(AsyncWebServer &server) {
return; 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(); String url = request->getParam("url")->value();
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 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(); url.trim();
octoUrl.trim();
octoToken.trim();
bool healthy = saveSpoolmanUrl(url, octoEnabled, octoUrl, octoToken); bool healthy = saveSpoolmanUrl(url);
String jsonResponse = "{\"healthy\": " + String(healthy ? "true" : "false") + "}"; String jsonResponse = "{\"healthy\": " + String(healthy ? "true" : "false") + "}";
request->send(200, "application/json", jsonResponse); request->send(200, "application/json", jsonResponse);
}); });
// Route für das Überprüfen der Bambu-Instanz // Route für das Überprüfen der Spoolman-Instanz
server.on("/api/bambu", HTTP_GET, [](AsyncWebServerRequest *request){ 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")) { if (!request->hasParam("bambu_ip") || !request->hasParam("bambu_serialnr") || !request->hasParam("bambu_accesscode")) {
request->send(400, "application/json", "{\"success\": false, \"error\": \"Missing parameter\"}"); request->send(400, "application/json", "{\"success\": false, \"error\": \"Missing parameter\"}");
return; return;
@ -337,20 +264,16 @@ void setupWebserver(AsyncWebServer &server) {
String bambu_ip = request->getParam("bambu_ip")->value(); String bambu_ip = request->getParam("bambu_ip")->value();
String bambu_serialnr = request->getParam("bambu_serialnr")->value(); String bambu_serialnr = request->getParam("bambu_serialnr")->value();
String bambu_accesscode = request->getParam("bambu_accesscode")->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_ip.trim();
bambu_serialnr.trim(); bambu_serialnr.trim();
bambu_accesscode.trim(); bambu_accesscode.trim();
autoSendTime.trim();
if (bambu_ip.length() == 0 || bambu_serialnr.length() == 0 || bambu_accesscode.length() == 0) { if (bambu_ip.length() == 0 || bambu_serialnr.length() == 0 || bambu_accesscode.length() == 0) {
request->send(400, "application/json", "{\"success\": false, \"error\": \"Empty parameter\"}"); request->send(400, "application/json", "{\"success\": false, \"error\": \"Empty parameter\"}");
return; return;
} }
bool success = saveBambuCredentials(bambu_ip, bambu_serialnr, bambu_accesscode, autoSend, autoSendTime); bool success = saveBambuCredentials(bambu_ip, bambu_serialnr, bambu_accesscode);
request->send(200, "application/json", "{\"healthy\": " + String(success ? "true" : "false") + "}"); request->send(200, "application/json", "{\"healthy\": " + String(success ? "true" : "false") + "}");
}); });
@ -363,7 +286,7 @@ void setupWebserver(AsyncWebServer &server) {
// Route für das Laden der CSS-Datei // Route für das Laden der CSS-Datei
server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Lade style.css"); Serial.println("Lade style.css");
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/style.css.gz", "text/css"); AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/style.css.gz", "text/css");
response->addHeader("Content-Encoding", "gzip"); response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL); response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response); request->send(response);
@ -372,7 +295,7 @@ void setupWebserver(AsyncWebServer &server) {
// Route für das Logo // Route für das Logo
server.on("/logo.png", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/logo.png", HTTP_GET, [](AsyncWebServerRequest *request){
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/logo.png.gz", "image/png"); AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/logo.png.gz", "image/png");
response->addHeader("Content-Encoding", "gzip"); response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL); response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response); request->send(response);
@ -381,7 +304,7 @@ void setupWebserver(AsyncWebServer &server) {
// Route für Favicon // Route für Favicon
server.on("/favicon.ico", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/favicon.ico", HTTP_GET, [](AsyncWebServerRequest *request){
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/favicon.ico", "image/x-icon"); AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/favicon.ico", "image/x-icon");
response->addHeader("Cache-Control", CACHE_CONTROL); response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response); request->send(response);
Serial.println("favicon.ico gesendet"); Serial.println("favicon.ico gesendet");
@ -389,26 +312,17 @@ void setupWebserver(AsyncWebServer &server) {
// Route für spool_in.png // Route für spool_in.png
server.on("/spool_in.png", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/spool_in.png", HTTP_GET, [](AsyncWebServerRequest *request){
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/spool_in.png.gz", "image/png"); AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/spool_in.png.gz", "image/png");
response->addHeader("Content-Encoding", "gzip"); response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL); response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response); request->send(response);
Serial.println("spool_in.png gesendet"); 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 // Route für JavaScript Dateien
server.on("/spoolman.js", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/spoolman.js", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /spoolman.js erhalten"); Serial.println("Anfrage für /spoolman.js erhalten");
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/spoolman.js.gz", "text/javascript"); AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/spoolman.js.gz", "text/javascript");
response->addHeader("Content-Encoding", "gzip"); response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL); response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response); request->send(response);
@ -417,29 +331,37 @@ void setupWebserver(AsyncWebServer &server) {
server.on("/rfid.js", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/rfid.js", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /rfid.js erhalten"); Serial.println("Anfrage für /rfid.js erhalten");
AsyncWebServerResponse *response = request->beginResponse(LittleFS,"/rfid.js.gz", "text/javascript"); AsyncWebServerResponse *response = request->beginResponse(SPIFFS,"/rfid.js.gz", "text/javascript");
response->addHeader("Content-Encoding", "gzip"); response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL); response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response); request->send(response);
Serial.println("RFID.js gesendet"); Serial.println("RFID.js gesendet");
}); });
// Vereinfachter Update-Handler // Route for Firmware Update
server.on("/upgrade", HTTP_GET, [](AsyncWebServerRequest *request) { server.on("/upgrade", HTTP_GET, [](AsyncWebServerRequest *request) {
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/upgrade.html.gz", "text/html"); // 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");
response->addHeader("Content-Encoding", "gzip"); response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", "no-store"); response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response); request->send(response);
}); });
// Update-Handler registrieren server.on("/update", HTTP_POST,
handleUpdate(server); [](AsyncWebServerRequest *request) {
// The response will be sent from handleOTAUpload when the upload is complete
server.on("/api/version", HTTP_GET, [](AsyncWebServerRequest *request){ },
String fm_version = VERSION; [](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) {
String jsonResponse = "{\"version\": \""+ fm_version +"\"}"; // Free memory before handling update
request->send(200, "application/json", jsonResponse); ws.enable(false);
}); ws.cleanupClients();
handleOTAUpload(request, filename, index, data, len, final);
}
);
// Fehlerbehandlung für nicht gefundene Seiten // Fehlerbehandlung für nicht gefundene Seiten
server.onNotFound([](AsyncWebServerRequest *request){ server.onNotFound([](AsyncWebServerRequest *request){

View File

@ -6,8 +6,8 @@
#include "commonFS.h" #include "commonFS.h"
#include "api.h" #include "api.h"
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <Update.h> #include <ESPAsyncWebServer.h>
#include <AsyncTCP.h> #include <AsyncWebSocket.h>
#include "bambu.h" #include "bambu.h"
#include "nfc.h" #include "nfc.h"
#include "scale.h" #include "scale.h"
@ -17,12 +17,7 @@ extern String spoolmanUrl;
extern AsyncWebServer server; extern AsyncWebServer server;
extern AsyncWebSocket ws; 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); void setupWebserver(AsyncWebServer &server);
// WebSocket-Funktionen
void sendAmsData(AsyncWebSocketClient *client); void sendAmsData(AsyncWebSocketClient *client);
void sendNfcData(AsyncWebSocketClient *client); void sendNfcData(AsyncWebSocketClient *client);
void foundNfcTag(AsyncWebSocketClient *client, uint8_t success); void foundNfcTag(AsyncWebSocketClient *client, uint8_t success);

View File

@ -3,69 +3,29 @@
#include <WiFi.h> #include <WiFi.h>
#include <esp_wifi.h> #include <esp_wifi.h>
#include <WiFiManager.h> #include <WiFiManager.h>
#include <DNSServer.h>
#include <ESPmDNS.h>
#include "display.h" #include "display.h"
#include "config.h" #include "config.h"
WiFiManager wm; WiFiManager wm;
bool wm_nonblocking = false; 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() { void initWiFi() {
// load Wifi settings WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP
wifiSettings();
wm.setAPCallback(configModeCallback); //esp_wifi_set_max_tx_power(72); // Setze maximale Sendeleistung auf 20dBm
wm.setSaveConfigCallback([]() {
Serial.println("Configurations updated");
ESP.restart();
});
if(wm_nonblocking) wm.setConfigPortalBlocking(false); if(wm_nonblocking) wm.setConfigPortalBlocking(false);
//wm.setConfigPortalTimeout(320); // Portal nach 5min schließen wm.setConfigPortalTimeout(320); // Portal nach 5min schließen
wm.setWiFiAutoReconnect(true);
wm.setConnectTimeout(10);
oledShowTopRow(); oledShowTopRow();
oledShowMessage("WiFi Setup"); oledShowMessage("WiFi Setup");
//bool res = wm.autoConnect("FilaMan"); // anonymous ap bool res;
if(!wm.autoConnect("FilaMan")) { // 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"); Serial.println("Failed to connect or hit timeout");
// ESP.restart(); // ESP.restart();
oledShowTopRow(); oledShowTopRow();
@ -81,49 +41,5 @@ void initWiFi() {
oledShowTopRow(); oledShowTopRow();
display.display(); display.display();
vTaskDelay(500 / portTICK_PERIOD_MS);
// mDNS
startMDNS();
}
}
void checkWiFiConnection() {
if (WiFi.status() != WL_CONNECTED)
{
Serial.println("WiFi connection lost. Reconnecting...");
wifiOn = false;
oledShowTopRow();
oledShowMessage("WiFi reconnecting");
WiFi.reconnect(); // Versuche, die Verbindung wiederherzustellen
vTaskDelay(5000 / portTICK_PERIOD_MS); // Warte 5 Sekunden, bevor erneut geprüft wird
if (WiFi.status() != WL_CONNECTED)
{
Serial.println("Failed to reconnect. Restarting WiFi...");
WiFi.disconnect();
Serial.println("WiFi disconnected.");
vTaskDelay(1000 / portTICK_PERIOD_MS);
wifiErrorCounter++;
//wifiSettings();
WiFi.reconnect();
Serial.println("WiFi reconnecting...");
WiFi.waitForConnectResult();
}
else
{
Serial.println("WiFi reconnected.");
wifiErrorCounter = 0;
wifiOn = true;
oledShowTopRow();
startMDNS();
}
}
if (wifiErrorCounter >= 5)
{
Serial.println("Too many WiFi errors. Restarting...");
ESP.restart();
} }
} }

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 540 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 525 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

View File

@ -1,12 +0,0 @@
## **Heat insert location**
Housing:
- every hole is made to fit a heat insert
![](./Housing_Heatinsert_Location_usermod_spitzbirne32_.png)
---
Scale top:
- two heat inserts for the NFC Reader
![](./ScaleTop_Heatinsert_Location_usermod_spitzbirne32_.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 491 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 834 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

View File

@ -1,69 +0,0 @@
## 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.
![](./Images/Showcase_usermod_spitzbirne32.gif)
---
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 &#8594; [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.