Compare commits

..

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

52 changed files with 1258 additions and 18314 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,129 @@
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 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: |
pio run -e esp32dev -t buildfs # Build SPIFFS
pio run -e esp32dev # Build firmware
cp .pio/build/esp32dev/firmware.bin .pio/build/esp32dev/filaman.bin
cp .pio/build/esp32dev/spiffs.bin .pio/build/esp32dev/filaman_spiffs.bin
- name: Prepare binaries
run: |
# Ensure we're in the project root
cd $GITHUB_WORKSPACE
# Create SPIFFS directory if it doesn't exist
mkdir -p .pio/build/esp32dev/spiffs
# Copy firmware to SPIFFS directory
cp .pio/build/esp32dev/firmware.bin .pio/build/esp32dev/spiffs/firmware.bin
# Build new SPIFFS image with firmware included
pio run -t buildfs
cd .pio/build/esp32dev
# Create release files
cp spiffs.bin filaman_spiffs.bin
# Create full binary
echo "Creating full binary..."
esptool.py --chip esp32 merge_bin \
--fill-flash-size 4MB \
--flash_mode dio \
--flash_freq 40m \
--flash_size 4MB \
-o filaman_full.bin \
0x0000 bootloader.bin \
0x8000 partitions.bin \
0x10000 firmware.bin \
0x390000 spiffs.bin
# Verify file sizes
echo "File sizes:"
ls -lh *.bin
- 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
cd .pio/build/esp32dev
# Check if files exist before uploading
for file in filaman_spiffs.bin filaman_full.bin; do
if [ -f "$file" ]; then
echo "Uploading $file..."
curl -k -s \
-X POST \
-H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/octet-stream" \
--data-binary "@$file" \
"${API_URL}/repos/${REPO}/releases/${RELEASE_ID}/assets?name=$file"
else
echo "Warning: $file not found"
fi
done
else
echo "Failed to create release. Response:"
echo "$RESPONSE"
exit 1
fi

View File

@ -0,0 +1,109 @@
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 esptool
- name: Install xxd
run: |
sudo apt-get update
sudo apt-get install xxd
- name: Build Firmware
run: |
pio run -e esp32dev -t buildfs # Build SPIFFS
pio run -e esp32dev # Build firmware
cp .pio/build/esp32dev/firmware.bin .pio/build/esp32dev/filaman.bin
cp .pio/build/esp32dev/spiffs.bin .pio/build/esp32dev/filaman_spiffs.bin
- name: Prepare binaries
run: |
# Ensure we're in the project root
cd $GITHUB_WORKSPACE
# Create SPIFFS directory if it doesn't exist
mkdir -p .pio/build/esp32dev/spiffs
# Copy firmware to SPIFFS directory
cp .pio/build/esp32dev/firmware.bin .pio/build/esp32dev/spiffs/firmware.bin
# Build new SPIFFS image with firmware included
pio run -t buildfs
cd .pio/build/esp32dev
# Create release files
cp spiffs.bin filaman_spiffs.bin
# Create full binary
echo "Creating full binary..."
esptool.py --chip esp32 merge_bin \
--fill-flash-size 4MB \
--flash_mode dio \
--flash_freq 40m \
--flash_size 4MB \
-o filaman_full.bin \
0x0000 bootloader.bin \
0x8000 partitions.bin \
0x10000 firmware.bin \
0x390000 spiffs.bin
# Verify file sizes
echo "File sizes:"
ls -lh *.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: |
# Check which files exist and create a list for upload
cd .pio/build/esp32dev
FILES_TO_UPLOAD=""
for file in filaman_spiffs.bin filaman_full.bin; do
if [ -f "$file" ]; then
FILES_TO_UPLOAD="$FILES_TO_UPLOAD .pio/build/esp32dev/$file"
else
echo "Warning: $file not found"
fi
done
# Create release with available files
if [ -n "$FILES_TO_UPLOAD" ]; then
gh release create "${{ github.ref_name }}" \
--title "Release ${{ steps.get_version.outputs.VERSION }}" \
--notes "${{ steps.changelog.outputs.CHANGES }}" \
$FILES_TO_UPLOAD
else
echo "Error: No files found to upload"
exit 1

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"
}
}

View File

@ -1,856 +1,5 @@
# Changelog # Changelog
## [1.4.0] - 2025-03-01
### Added
- add support for Spoolman Octoprint Plugin in README files
- add OctoPrint integration with configurable fields and update functionality
- add version comparison function and check for outdated versions before updates
- remove unused version and protocol fields from JSON output; add error message for insufficient memory
### Changed
- update NFC tag references to include NTAG213 and clarify storage capacity
- bump version to 1.4.0
- remove unused version and protocol fields from NFC data packet
- sort vendors alphabetically in the dropdown list
- Merge pull request #10 from janecker/nfc-improvements
- Improves NFC Tag handling
## [1.3.99] - 2025-02-28
### Changed
- update platformio.ini for version v1.3.99
- update workflows to build firmware with LittleFS instead of SPIFFS
## [1.3.98] - 2025-02-28
### Changed
- update platformio.ini for version v1.3.98
- migrate from SPIFFS to LittleFS for file handling
- remove unused VSCode settings file
- remove commented-out spoolman and filaman data from api.cpp
## [1.3.97] - 2025-02-28
### Added
- füge Bestätigungsmeldung für Spool-Einstellung hinzu
- verbessere WLAN-Konfiguration und füge mDNS-Unterstützung hinzu
- aktualisiere OLED-Anzeige mit Versionsnummer und verbessere Textausrichtung
- füge regelmäßige WLAN-Verbindungsüberprüfung hinzu
- aktualisiere Schaltplan-Bild
- zeige Versionsnummer im OLED-Display an
### Changed
- update platformio.ini for version v1.3.97
- entferne text-shadow von deaktivierten Schaltflächen
- füge Link zum Wiki für detaillierte Informationen über die Nutzung hinzu
### Fixed
- Speichernutzung optimiert
- behebe doppelte http.end() Aufrufe in checkSpoolmanExtraFields
- optimiere Verzögerungen und Stackgrößen in NFC-Task-Funktionen
- entferne ungenutzte Bibliotheken und Debug-Ausgaben aus main.cpp
## [1.3.96] - 2025-02-25
### Added
- füge Unterstützung für Spoolman-Einstellungen hinzu und aktualisiere die Benutzeroberfläche
- entferne die sendAmsData-Funktion aus der API-Schnittstelle
- erweitere Bambu-Credentials um AutoSend-Zeit und aktualisiere die Benutzeroberfläche
- erweitere Bambu-Credentials mit AutoSend-Wartezeit und aktualisiere die Benutzeroberfläche
- add espRestart function and replace delay with vTaskDelay for OTA update process
- implement OTA update functionality with backup and restore for configurations
- add own_filaments.json and integrate custom filament loading in bambu.cpp
### Changed
- update platformio.ini for version v1.3.96
### Fixed
- aktualisiere Bedingungen für die AMS-Datenaktualisierung und entferne unnötige Aufrufe
- aktualisiere Bedingung für den Fortschritt der OTA-Update-Nachricht
- update auto set logic to check RFID tag before setting Bambu spool
## [1.3.95] - 2025-02-24
### Changed
- update webpages for version v1.3.95
### Fixed
- bind autoSendToBambu variable to checkbox in spoolman.html
## [1.3.94] - 2025-02-24
### Changed
- update webpages for version v1.3.94
### Fixed
- correct payload type check in NFC write event handling
## [1.3.93] - 2025-02-24
### Added
- implement auto send feature for Bambu spool management and update related configurations
- add debug mode instructions for Spoolman in README
- add wiring diagrams to README for PN532 I2C setup
### Changed
- update webpages for version v1.3.93
- simplify filament names in JSON configuration
- update findFilamentIdx to return structured result and improve type searching logic
- update README to reflect PN532 I2C configuration and remove SPI pin details
### Fixed
- remove debug output from splitTextIntoLines and update weight display logic in scanRfidTask
- enhance weight display logic for negative values
- remove unnecessary CPU frequency configuration from setup function
## [1.3.92] - 2025-02-24
### Changed
- update webpages for version v1.3.92
- remove commented-out code in setBambuSpool function
- update installation instructions and formatting in README files
### Fixed
- configure CPU frequency settings in setup function only for testing
- update comment to clarify NVS reading process
- adjust weight display logic to handle cases for weight less than 2
- update weight display logic to handle negative and specific weight cases
## [1.3.91] - 2025-02-23
### Added
- update GitHub Actions workflow for FTP firmware upload with improved credential checks
### Changed
- update webpages for version v1.3.91
## [1.3.90] - 2025-02-23
### Added
- update index.html for improved content structure and additional links
- improve UI for Spoolman and Bambu Lab printer credentials, enhancing layout and styling
- update README files with HSPI default PINs and add ESP32 pin diagram
- implement scale calibration checks and update start_scale function to return calibration status
- add FTP upload functionality to GitHub release workflow and update installation instructions in README
### Changed
- update webpages for version v1.3.90
### Fixed
- remove debug secrets check from Gitea release workflow
## [1.3.89] - 2025-02-23
### Changed
- update webpages for version v1.3.89
### Fixed
- update Gitea release workflow to use vars for FTP credentials
## [1.3.88] - 2025-02-23
### Changed
- update webpages for version v1.3.88
### Fixed
- update Gitea release workflow to use secrets for FTP credentials
## [1.3.87] - 2025-02-23
### Changed
- update webpages for version v1.3.87
### Fixed
- enhance FTP upload workflow with credential checks and version output
## [1.3.86] - 2025-02-23
### Changed
- update webpages for version v1.3.86
### Fixed
- streamline FTP credentials usage in Gitea release workflow
## [1.3.85] - 2025-02-23
### Added
- add FTP_USER variable for Gitea release workflow
### Changed
- update webpages for version v1.3.85
## [1.3.84] - 2025-02-23
### Added
- add FTP_HOST variable for firmware upload in Gitea release workflow
### Changed
- update webpages for version v1.3.84
## [1.3.83] - 2025-02-23
### Changed
- update webpages for version v1.3.83
### Fixed
- correct variable interpolation for FTP credentials in Gitea release workflow
## [1.3.82] - 2025-02-23
### Added
- update Gitea release workflow to use variable interpolation for FTP credentials
### Changed
- update webpages for version v1.3.82
## [1.3.81] - 2025-02-23
### Added
- update Gitea release workflow to use environment variables for FTP credentials and version
### Changed
- update webpages for version v1.3.81
## [1.3.80] - 2025-02-23
### Added
- add FTP_USER and FTP_PASSWORD secrets for firmware upload in Gitea release workflow
### Changed
- update webpages for version v1.3.80
## [1.3.79] - 2025-02-23
### Added
- add FTP_USER input for firmware upload in Gitea release workflow
### Changed
- update webpages for version v1.3.79
## [1.3.78] - 2025-02-23
### Changed
- update webpages for version v1.3.78
### Fixed
- change FTP protocol from FTPS to FTP for file upload in workflow
## [1.3.77] - 2025-02-23
### Changed
- update webpages for version v1.3.77
### Fixed
- replace ncftp with lftp for secure firmware upload
## [1.3.76] - 2025-02-23
### Changed
- update webpages for version v1.3.76
### Fixed
- replace FTP action with curl for secure firmware upload and install ncftp
## [1.3.75] - 2025-02-23
### Changed
- update webpages for version v1.3.75
### Fixed
- update FTP user and enhance SSL options in gitea-release workflow
## [1.3.74] - 2025-02-23
### Changed
- update webpages for version v1.3.74
### Fixed
- update password syntax in gitea-release workflow
## [1.3.73] - 2025-02-23
### Changed
- update webpages for version v1.3.73
- update version to 1.3.72 in platformio.ini
## [1.3.72] - 2025-02-23
### Changed
- update webpages for version v1.3.72
### Fixed
- update FTP options for Gitea release workflow
## [1.3.71] - 2025-02-23
### Added
- add FTP upload step for firmware in Gitea release workflow
### Changed
- update webpages for version v1.3.71
## [1.3.70] - 2025-02-23
### Changed
- update webpages for version v1.3.70
## [1.3.69] - 2025-02-23
### Changed
- update webpages for version v1.3.69
### Fixed
- update release note generation to use the second latest tag
## [1.3.68] - 2025-02-23
### Changed
- update webpages for version v1.3.68
### Fixed
- update release note generation to include commit hash and author
- remove commented test line from platformio.ini
## [1.3.67] - 2025-02-23
### Changed
- update webpages for version v1.3.67
- ci: update release note generation to use the latest tag
## [1.3.66] - 2025-02-23
### Changed
- update webpages for version v1.3.66
- ci: remove redundant git fetch for tags in release note generation
## [1.3.65] - 2025-02-22
### Changed
- update webpages for version v1.3.65
- ci: improve release note generation by fetching tags and sorting unique commits
## [1.3.64] - 2025-02-22
### Changed
- update webpages for version v1.3.64
- remove unnecessary closing tags from header.html
## [1.3.63] - 2025-02-22
### Added
- update update-form background and add glass border effect
### Changed
- update webpages for version v1.3.63
- update release note generation for initial release handling
## [1.3.62] - 2025-02-22
### Changed
- update webpages for version v1.3.62
- update background colors and improve layout for update sections
## [1.3.61] - 2025-02-22
### Added
- update release notes generation to use previous tag for changes
### Changed
- update webpages for version v1.3.61
## [1.3.60] - 2025-02-22
### Added
- remove automatic git push from changelog update script
- implement release notes generation with categorized changes since last tag
### Changed
- update webpages for version v1.3.60
## [1.3.59] - 2025-02-22
### Added
- implement enhanced update progress handling and WebSocket notifications
- improve update progress reporting and enhance WebSocket notifications
- enhance update progress handling and add WebSocket closure notification
- implement WebSocket for update progress and enhance update response handling
### Changed
- update webpages for version v1.3.59
## [1.3.58] - 2025-02-22
### Added
- implement backup and restore functionality for Bambu credentials and Spoolman URL
### Changed
- update webpages for version v1.3.58
- update upgrade page message and improve progress display logic
## [1.3.57] - 2025-02-22
### Changed
- update webpages for version v1.3.57
- update header title to 'Filament Management Tool' in multiple HTML files
## [1.3.56] - 2025-02-22
### Changed
- update webpages for version v1.3.56
- update header title and improve SPIFFS update error handling
- clarify comments in Gitea and GitHub release workflows
## [1.3.55] - 2025-02-22
### Changed
- update webpages for version v1.3.55
- update component descriptions in README files
## [1.3.54] - 2025-02-22
### Changed
- update webpages for version v1.3.54
- workflow: update SPIFFS binary creation to exclude header
## [1.3.53] - 2025-02-22
### Changed
- version: update to version 1.3.53
- update changelog for version 1.3.51
- update changelog for version 1.3.51
- workflow: update SPIFFS binary magic byte and revert version to 1.3.51
## [1.3.52] - 2025-02-22
### Changed
- update webpages for version v1.3.52
- workflow: update SPIFFS binary creation to use correct chip revision (0xEB for Rev 3)
## [1.3.51] - 2025-02-22
### Changed
- update changelog for version 1.3.51
- workflow: update SPIFFS binary magic byte and revert version to 1.3.51
## [1.3.50] - 2025-02-22
### Changed
- update webpages for version v1.3.50
## [1.3.49] - 2025-02-22
### Changed
- update webpages for version v1.3.49
- workflow: update SPIFFS binary header to use correct chip revision
## [1.3.48] - 2025-02-22
### Changed
- update webpages for version v1.3.48
- workflow: update SPIFFS binary header for firmware release
## [1.3.47] - 2025-02-22
### Changed
- update webpages for version v1.3.47
- workflow: optimize firmware and SPIFFS update process, improve progress handling and logging
## [1.3.46] - 2025-02-22
### Changed
- update webpages for version v1.3.46
## [1.3.45] - 2025-02-22
### Changed
- update webpages for version v1.3.45
- workflow: update SPIFFS binary creation to include minimal header and adjust update validation logic
## [1.3.44] - 2025-02-22
### Changed
- update webpages for version v1.3.44
- update header title to 'Hollo Lollo Trollo'
- update header title to 'Filament Management Tool' and improve update response messages
## [1.3.43] - 2025-02-22
### Changed
- update webpages for version v1.3.43
- update header title to 'Hollo Lollo Trollo'
## [1.3.42] - 2025-02-22
### Changed
- update webpages for version v1.3.42
### Fixed
- correct path for SPIFFS binary creation in Gitea release workflow
## [1.3.41] - 2025-02-22
### Changed
- update webpages for version v1.3.41
### Fixed
- remove redundant buffer size setting in NFC initialization
- update SPIFFS binary creation and enhance NFC buffer size
## [1.3.40] - 2025-02-22
### Changed
- update webpages for version v1.3.40
### Fixed
- update SPIFFS binary header and enhance WebSocket error handling
## [1.3.39] - 2025-02-22
### Changed
- update webpages for version v1.3.39
- workflow: update SPIFFS binary creation to set chip version to max supported
## [1.3.38] - 2025-02-22
### Changed
- update webpages for version v1.3.38
- workflow: update SPIFFS binary creation with minimal ESP32 image header
## [1.3.37] - 2025-02-22
### Changed
- update webpages for version v1.3.37
- workflow: update ESP32-WROOM image header for SPIFFS binary creation
## [1.3.36] - 2025-02-22
### Changed
- update webpages for version v1.3.36
- partition: update SPIFFS binary header and offsets in workflow files
## [1.3.35] - 2025-02-22
### Changed
- update webpages for version v1.3.35
- partition: update SPIFFS binary header and offsets in workflow files
## [1.3.34] - 2025-02-22
### Changed
- update webpages for version v1.3.34
- partition: update SPIFFS binary creation and offsets in workflow files
## [1.3.33] - 2025-02-22
### Changed
- update webpages for version v1.3.33
- partition: update spiffs offset and app sizes in partition files
- partition: update spiffs offset in partition files
- partition: update app sizes and offsets in partitions.csv
## [1.3.32] - 2025-02-22
### Changed
- update webpages for version v1.3.32
- workflow: update magic byte for SPIFFS binary creation
## [1.3.31] - 2025-02-22
### Changed
- update webpages for version v1.3.31
- workflow: remove unnecessary data and SPIFFS change checks from release workflows
## [1.3.30] - 2025-02-22
### Changed
- update webpages for version v1.3.30
- workflow: update Gitea and GitHub release workflows to create SPIFFS binary with magic byte
## [1.3.29] - 2025-02-21
### Changed
- update webpages for version v1.3.29
- workflow: update Gitea release workflow to create release before file uploads
## [1.3.28] - 2025-02-21
### Changed
- update webpages for version v1.3.28
- workflow: update Gitea release workflow to use file uploads with curl
## [1.3.27] - 2025-02-21
### Added
- workflow: add GITEA_TOKEN secret for Gitea API access in release workflows
### Changed
- update webpages for version v1.3.27
## [1.3.26] - 2025-02-21
### Changed
- update webpages for version v1.3.26
### Fixed
- workflow: improve Gitea release workflow with enhanced error handling and debug outputs
## [1.3.25] - 2025-02-21
### Changed
- update webpages for version v1.3.25
- workflow: update Gitea release workflow to include RUNNER_NAME and improve error handling
## [1.3.24] - 2025-02-21
### Changed
- update webpages for version v1.3.24
- workflow: rename update files to upgrade in GitHub release workflow
- workflow: aktualisiere bestehende Einträge im Changelog für vorhandene Versionen
### Fixed
- workflow: improve Gitea release process with dynamic URL determination and debug outputs
## [1.3.23] - 2025-02-21
### Changed
- update webpages for version v1.3.23
### Fixed
- workflow: enhance Gitea release process with debug outputs and API connection checks
## [1.3.22] - 2025-02-21
### Added
- workflow: improve Gitea release process with additional environment variables and error handling
### Changed
- update webpages for version v1.3.22
## [1.3.21] - 2025-02-21
### Changed
- update webpages for version v1.3.21
- workflow: enhance Gitea release process with API integration and token management
## [1.3.20] - 2025-02-21
### Changed
- update webpages for version v1.3.20
- workflow: enable git tagging and pushing for Gitea releases
## [1.3.19] - 2025-02-21
### Changed
- update webpages for version v1.3.19
- workflow: enable git push for version tagging in Gitea release
## [1.3.18] - 2025-02-21
### Changed
- ACHTUNG: Installiere einmal das filaman_full.bin danach kannst du über die upgrade Files aktualisieren und deine Settings bleiben auch erhalten.
- ATTENTION: Install the filaman_full.bin once, then you can update via the upgrade files and your settings will also be retained.
## [1.3.18] - 2025-02-21
### Added
- add note about filaman_full.bin installation in changelog
### Changed
- update webpages for version v1.3.18
- update changelog for version 1.3.18 and enhance update script for existing entries
## [1.3.17] - 2025-02-21
### Changed
- update webpages for version v1.3.17
- ci: comment out git tag and push commands in gitea-release workflow
## [1.3.16] - 2025-02-21
### Changed
- update webpages for version v1.3.16
- ci: update filenames for firmware and website binaries in release workflows
## [1.3.15] - 2025-02-21
### Changed
- update webpages for version v1.3.15
### Fixed
- ci: fix missing 'fi' in GitHub release workflow script
## [1.3.14] - 2025-02-21
### Changed
- update webpages for version v1.3.14
- ci: update GitHub release workflow to improve file upload handling
## [1.3.13] - 2025-02-21
### Changed
- update webpages for version v1.3.13
- ci: update GitHub release workflow to use RELEASE_TOKEN for improved security
## [1.3.12] - 2025-02-21
### Changed
- update webpages for version v1.3.12
- ci: enhance GitHub release workflow with token handling and file upload improvements
## [1.3.11] - 2025-02-21
### Changed
- update webpages for version v1.3.11
- ci: refactor Gitea release workflow by simplifying input handling and removing unnecessary checks
## [1.3.10] - 2025-02-21
### Changed
- update webpages for version v1.3.10
- ci: simplify GitHub release workflow by removing provider verification step
## [1.3.9] - 2025-02-21
### Changed
- update webpages for version v1.3.9
- ci: comment out permissions for GitHub release workflow
## [1.3.8] - 2025-02-21
### Added
- add Gitea and GitHub release workflows
### Changed
- update webpages for version v1.3.8
## [1.3.7] - 2025-02-21
### Added
- add GitHub and Gitea release workflows
### Changed
- update webpages for version v1.3.7
## [1.3.6] - 2025-02-21
### Changed
- update webpages for version v1.3.6
### Fixed
- update GitHub token reference and correct file path in release workflow
## [1.3.5] - 2025-02-21
### Added
- enhance release workflow to support Gitea alongside GitHub
### Changed
- update webpages for version v1.3.5
## [1.3.4] - 2025-02-21
### Added
- add Gitea and GitHub release workflows
### Changed
- update webpages for version v1.3.4
- Merge branch 'old'
## [1.3.3] - 2025-02-21
### Changed
- update webpages for version v1.3.3
### Fixed
- correct directory path in GitHub workflows for SPIFFS binary
## [1.3.2] - 2025-02-21
### Added
- add missing conditional exit in release workflow
### Changed
- update webpages for version v1.3.2
## [1.3.1] - 2025-02-21
### Added
- enhance GitHub and Gitea release workflows with Python setup and binary preparation
### Changed
- update webpages for version v1.3.1
## [1.3.0] - 2025-02-21
### Changed
- bump version to 1.3.0 in platformio.ini
## [1.2.102] - 2025-02-21
### Changed
- update webpages for version v1.2.102
### Fixed
- adjust bootloader offset in binary merge for Gitea and GitHub workflows
## [1.2.101] - 2025-02-21
### Changed
- update webpages for version v1.2.101
- always create SPIFFS binary in release workflows
- migrate calibration value storage from EEPROM to NVS
## [1.2.100] - 2025-02-21
### Changed
- update webpages for version v1.2.100
- remove OTA handling and JSON backup/restore functions
## [1.2.99] - 2025-02-21
### Added
- add SPIFFS change detection and binary copying to release workflows
- add backup and restore functions for JSON configurations during OTA updates
### Changed
- update webpages for version v1.2.99
- update JSON field type checks from JsonObject to String for improved validation
- update JSON handling in API and Bambu modules for improved object management
- update platformio.ini dependencies and improve version handling in website.cpp
- update Cache-Control header to reflect a 1-week duration
- remove version definition from website.cpp
- optimize WiFi and WebSocket settings; enhance TCP/IP stack configuration
- update upgrade page title and heading; adjust cache control duration
## [1.2.98] - 2025-02-21
### Changed
- update webpages for version v1.2.98
## [1.2.97] - 2025-02-21
### Changed
- update webpages for version v1.2.97
- streamline Gitea and GitHub release workflows to check for data changes and update binary handling
## [1.2.96] - 2025-02-21
### Added
- add SPIFFS build step to Gitea and GitHub release workflows
### Changed
- update webpages for version v1.2.96
## [1.2.95] - 2025-02-21
### Added
- enhance update process with separate forms for firmware and webpage uploads, including validation and improved UI
- add API endpoint for version retrieval and update HTML to display dynamic version
### Changed
- update webpages for version v1.2.95
- bump version to 1.2.94 in platformio.ini
## [1.2.91] - 2025-02-20 ## [1.2.91] - 2025-02-20
### Added ### Added
- add file existence checks before uploading binaries in release workflows - add file existence checks before uploading binaries in release workflows

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/vMAx2gf5](https://discord.gg/vMAx2gf5)
### Es gibt jetzt auch ein Wiki, dort sind nochmal alle Funktionen beschrieben: [Wiki](https://github.com/ManuelW77/Filaman/wiki)
### ESP32 Hardware-Funktionen ### 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/vMAx2gf5](https://discord.gg/vMAx2gf5)
- **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/vMAx2gf5](https://discord.gg/vMAx2gf5)
- 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>
@ -57,14 +53,14 @@ Discord Server: [https://discord.gg/vMAx2gf5](https://discord.gg/vMAx2gf5)
### Komponenten ### Komponenten
- **ESP32 Entwicklungsboard:** Jede ESP32-Variante. - **ESP32 Entwicklungsboard:** Jede ESP32-Variante.
[Amazon Link](https://amzn.eu/d/aXThslf) [Amazon Link](https://amzn.eu/d/aXThslf)
- **HX711 5kg Wägezellen-Verstärker:** Für Gewichtsmessung. - **HX711 Wägezellen-Verstärker:** Für Gewichtsmessung.
[Amazon Link](https://amzn.eu/d/06A0DLb) [Amazon Link](https://amzn.eu/d/1wZ4v0x)
- **OLED 0.96 Zoll I2C weiß/gelb Display:** 128x64 SSD1306. - **OLED Display:** 128x64 SSD1306.
[Amazon Link](https://amzn.eu/d/0AuBp2c) [Amazon Link](https://amzn.eu/d/dozAYDU)
- **PN532 NFC NXP RFID-Modul V3:** Für NFC-Tag-Operationen. - **PN532 NFC Modul:** Für NFC-Tag-Operationen.
[Amazon Link](https://amzn.eu/d/jfIuQXb) [Amazon Link](https://amzn.eu/d/8205DDh)
- **NFC Tags NTAG213 NTA215:** RFID Tag - **NFC-Tag:** NTAG215
[Amazon Link](https://amzn.eu/d/9Z6mXc1) [Amazon Link](https://amzn.eu/d/fywy4c4)
### Pin-Konfiguration ### Pin-Konfiguration
| Komponente | ESP32 Pin | | Komponente | ESP32 Pin |
@ -75,15 +71,10 @@ Discord Server: [https://discord.gg/vMAx2gf5](https://discord.gg/vMAx2gf5)
| 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 |
| PN532 MISO | 12 |
**Achte darauf, dass am PN532 die DIP-Schalter auf I2C gestellt sind** | PN532 CS/SS | 15 |
![Wiring](./img/Schaltplan.png)
![myWiring](./img/IMG_2589.jpeg)
![myWiring](./img/IMG_2590.jpeg)
## Software-Abhängigkeiten ## Software-Abhängigkeiten
@ -110,31 +101,7 @@ Discord Server: [https://discord.gg/vMAx2gf5](https://discord.gg/vMAx2gf5)
- 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/vMAx2gf5](https://discord.gg/vMAx2gf5)
### 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/vMAx2gf5](https://discord.gg/vMAx2gf5)
- **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/vMAx2gf5](https://discord.gg/vMAx2gf5)
- 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>
@ -61,14 +56,14 @@ Discord Server: [https://discord.gg/vMAx2gf5](https://discord.gg/vMAx2gf5)
### Components ### Components
- **ESP32 Development Board:** Any ESP32 variant. - **ESP32 Development Board:** Any ESP32 variant.
[Amazon Link](https://amzn.eu/d/aXThslf) [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.eu/d/06A0DLb) [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.eu/d/0AuBp2c) [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.eu/d/jfIuQXb) [Amazon Link](https://amzn.eu/d/8205DDh)
- **NFC Tags NTAG213 NTAG215:** RFID Tag - **NFC-Tag:** NTAG215
[Amazon Link](https://amzn.eu/d/9Z6mXc1) [Amazon Link](https://amzn.eu/d/fywy4c4)
### Pin Configuration ### Pin Configuration
@ -80,15 +75,10 @@ Discord Server: [https://discord.gg/vMAx2gf5](https://discord.gg/vMAx2gf5)
| 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 |
| PN532 MISO | 12 |
**Make sure that the DIP switches on the PN532 are set to I2C** | PN532 CS/SS | 15 |
![Wiring](./img/Schaltplan.png)
![myWiring](./img/IMG_2589.jpeg)
![myWiring](./img/IMG_2590.jpeg)
## Software Dependencies ## Software Dependencies
@ -101,9 +91,9 @@ Discord Server: [https://discord.gg/vMAx2gf5](https://discord.gg/vMAx2gf5)
- `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
@ -115,32 +105,7 @@ Discord Server: [https://discord.gg/vMAx2gf5](https://discord.gg/vMAx2gf5)
- 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
@ -159,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.91</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.91</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.91</span></h1>
<h4>Filament Management Tool</h4> <h4>Filament Management Tool</h4>
</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 = {
@ -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,

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.91</span></h1>
<h4>Filament Management Tool</h4> <h4>Filament Management Tool</h4>
</div> </div>
</div> </div>
@ -52,18 +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 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) {
@ -81,10 +63,8 @@
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) {
@ -97,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}}";
@ -113,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>
@ -150,22 +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>
<p id="bambuStatusMessage"></p> <p id="bambuStatusMessage"></p>
</div> </div>
</div> </div>
</div>
</div>
</body> </body>
</html> </html>

View File

@ -86,7 +86,7 @@ function populateVendorDropdown(data, selectedSmId = null) {
}); });
// Nach der Schleife: Formatierung der Gesamtlänge // Nach der Schleife: Formatierung der Gesamtlänge
console.log("Total Length: ", totalLength); console.log("Total Lenght: ", totalLength);
const formattedLength = totalLength > 1000 const formattedLength = totalLength > 1000
? (totalLength / 1000).toFixed(2) + " km" ? (totalLength / 1000).toFixed(2) + " km"
: totalLength.toFixed(2) + " m"; : totalLength.toFixed(2) + " m";
@ -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;

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.91</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,163 +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.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.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.91</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.91</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

@ -1,6 +1,6 @@
# Name, Type, SubType, Offset, Size, Flags # Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000, nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000, otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x1E0000, app0, app, ota_0, 0x10000, 0x1C0000,
app1, app, ota_1, 0x1F0000, 0x1E0000, app1, app, ota_1, 0x1D0000, 0x1C0000,
spiffs, data, spiffs, 0x3D0000, 0x30000, spiffs, data, spiffs, 0x390000, 0x60000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xe000 0x2000
4 app0 app ota_0 0x10000 0x1E0000 0x1C0000
5 app1 app ota_1 0x1F0000 0x1D0000 0x1E0000 0x1C0000
6 spiffs data spiffs 0x3D0000 0x390000 0x30000 0x60000

View File

@ -9,10 +9,8 @@
; https://docs.platformio.org/page/projectconf.html ; https://docs.platformio.org/page/projectconf.html
[common] [common]
version = "1.4.0" version = "1.2.91"
to_old_version = "1.4.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,44 @@ 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=3
-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_LOG_DEFAULT_LEVEL=3
-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

@ -1,23 +1,5 @@
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())

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

@ -5,20 +5,41 @@
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);
@ -35,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: ");
@ -91,21 +151,19 @@ void sendToApi(void *parameter) {
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.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 = http.PUT(updatePayload); int httpCode = http.PUT(updatePayload);
if (httpType == "PATCH") httpCode = http.PATCH(updatePayload); if (httpType == "PATCH") httpCode = http.PATCH(updatePayload);
if (httpType == "POST") httpCode = http.POST(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);
} }
@ -128,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;
} }
@ -204,89 +262,6 @@ uint8_t updateSpoolWeight(String spoolId, uint16_t weight) {
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
4096, // Stackgröße in Bytes
(void*)params, // Parameter
0, // Priorität
NULL // Task-Handle (nicht benötigt)
);
return true;
}
bool updateSpoolBambuData(String payload) {
JsonDocument doc;
DeserializationError error = deserializeJson(doc, payload);
if (error) {
Serial.print("Fehler beim JSON-Parsing: ");
Serial.println(error.c_str());
return false;
}
String spoolsUrl = spoolmanUrl + apiUrl + "/filament/" + doc["filament_id"].as<String>();
Serial.print("Update Spule mit URL: ");
Serial.println(spoolsUrl);
JsonDocument updateDoc;
updateDoc["extra"]["bambu_setting_id"] = "\"" + doc["setting_id"].as<String>() + "\"";
updateDoc["extra"]["bambu_cali_id"] = "\"" + doc["cali_idx"].as<String>() + "\"";
updateDoc["extra"]["bambu_idx"] = "\"" + doc["tray_info_idx"].as<String>() + "\"";
updateDoc["extra"]["nozzle_temperature"] = "[" + doc["temp_min"].as<String>() + "," + doc["temp_max"].as<String>() + "]";
String updatePayload;
serializeJson(updateDoc, updatePayload);
Serial.print("Update Payload: ");
Serial.println(updatePayload);
SendToApiParams* params = new SendToApiParams();
if (params == nullptr) {
Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren.");
return false;
}
params->httpType = "PATCH";
params->spoolsUrl = spoolsUrl;
params->updatePayload = updatePayload;
// Erstelle die Task
BaseType_t result = xTaskCreate(
sendToApi, // Task-Funktion
"SendToApiTask", // Task-Name
4096, // Stackgröße in Bytes
(void*)params, // Parameter
0, // Priorität
NULL // Task-Handle (nicht benötigt)
);
return true;
}
// #### Spoolman init // #### Spoolman init
bool checkSpoolmanExtraFields() { bool checkSpoolmanExtraFields() {
HTTPClient http; HTTPClient http;
@ -393,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;
@ -428,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;
} }
@ -456,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();
@ -478,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

@ -9,19 +9,16 @@
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

@ -23,21 +23,14 @@ 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 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 saveBambuCredentials(const String& ip, const String& serialnr, const String& accesscode, bool autoSend, const String& autoSendTime) { bool saveBambuCredentials(const String& ip, const String& serialnr, const String& accesscode) {
if (BambuMqttTask) { if (BambuMqttTask) {
vTaskDelete(BambuMqttTask); vTaskDelete(BambuMqttTask);
} }
@ -46,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.");
@ -58,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;
@ -69,27 +58,20 @@ 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());
report_topic = "device/" + String(bambu_serialnr) + "/report"; report_topic = "device/" + String(bambu_serialnr) + "/report";
//request_topic = "device/" + String(bambu_serialnr) + "/request"; //request_topic = "device/" + String(bambu_serialnr) + "/request";
@ -99,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") {
@ -157,46 +109,23 @@ 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(report_topic.c_str(), payload.c_str())) if (client.publish(report_topic.c_str(), payload.c_str()))
@ -227,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;
@ -250,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;
@ -272,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;
@ -296,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];
} }
@ -417,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;
} }
@ -462,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;
} }
@ -546,16 +461,15 @@ 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
if (client.connect(bambu_serialnr, bambu_username, bambu_accesscode)) { if (client.connect(bambu_serialnr, bambu_username, bambu_accesscode)) {
Serial.println("MQTT re/connected"); Serial.println("... re-connected");
// ... and resubscribe
client.subscribe(report_topic.c_str()); client.subscribe(report_topic.c_str());
bambu_connected = true; bambu_connected = true;
oledShowTopRow(); oledShowTopRow();
@ -565,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);
@ -595,7 +500,6 @@ void mqtt_loop(void * parameter) {
} }
client.loop(); client.loop();
yield(); yield();
esp_task_wdt_reset();
vTaskDelay(100); vTaskDelay(100);
} }
} }
@ -603,6 +507,7 @@ 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) {
Serial.println("Failed to load Bambu credentials"); Serial.println("Failed to load Bambu credentials");
@ -635,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. */
@ -666,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,11 +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;
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,8 +1,7 @@
#include "commonFS.h" #include "commonFS.h"
#include <LittleFS.h>
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);
@ -20,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);
@ -36,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,11 +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 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

@ -40,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

@ -23,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]);
} }

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>
@ -17,14 +19,8 @@
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();
@ -33,6 +29,7 @@ void setup() {
initWiFi(); initWiFi();
// Webserver // Webserver
Serial.println("Starte Webserver");
setupWebserver(server); setupWebserver(server);
// Spoolman API // Spoolman API
@ -40,27 +37,22 @@ void setup() {
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();
uint8_t scaleCalibrated = start_scale(); start_scale();
if (scaleCalibrated == 3) {
oledShowMessage("Scale not calibrated!");
for (uint16_t i = 0; i < 50000; i++) {
yield();
vTaskDelay(pdMS_TO_TICKS(1));
esp_task_wdt_reset();
}
} else if (scaleCalibrated == 0) {
oledShowMessage("HX711 not found");
for (uint16_t i = 0; i < 50000; i++) {
yield();
vTaskDelay(pdMS_TO_TICKS(1));
esp_task_wdt_reset();
}
}
// 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
@ -74,85 +66,42 @@ void setup() {
} }
/**
* Safe interval check that handles millis() overflow
* @param currentTime Current millis() value
* @param lastTime Last recorded time
* @param interval Desired interval in milliseconds
* @return True if interval has elapsed
*/
bool intervalElapsed(unsigned long currentTime, unsigned long &lastTime, unsigned long interval) {
if (currentTime - lastTime >= interval || currentTime < lastTime) {
lastTime = currentTime;
return true;
}
return false;
}
unsigned long lastWeightReadTime = 0; 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;
unsigned long lastWifiCheckTime = 0;
const unsigned long wifiCheckInterval = 60000; // Überprüfe alle 60 Sekunden (60000 ms)
// ##### 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 regelmäßig die WLAN-Verbindung // Send AMS Data min every Minute
if (intervalElapsed(currentMillis, lastWifiCheckTime, wifiCheckInterval)) { if (currentMillis - lastAmsSendTime >= amsSendInterval) {
checkWiFiConnection(); lastAmsSendTime = currentMillis;
} sendAmsData(nullptr);
// Wenn Bambu auto set Spool aktiv
if (autoSendToBambu && autoSetToBambuSpoolId > 0) {
if (intervalElapsed(currentMillis, lastAutoSetBambuAmsTime, autoSetBambuAmsInterval))
{
if (hasReadRfidTag == 0)
{
lastAutoSetBambuAmsTime = currentMillis;
oledShowMessage("Auto Set " + String(autoSetBambuAmsCounter - autoAmsCounter) + "s");
autoAmsCounter++;
if (autoAmsCounter >= autoSetBambuAmsCounter)
{
autoSetToBambuSpoolId = 0;
autoAmsCounter = 0;
oledShowWeight(weight);
}
}
else
{
autoAmsCounter = 0;
}
}
}
// Wenn Waage nicht Kalibriert
if (scaleCalibrated == 3)
{
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 && weight != lastWeight && hasReadRfidTag == 0 && (!autoSendToBambu || autoSetToBambuSpoolId == 0)) if (pauseMainTask == 0 && weight != lastWeight && hasReadRfidTag == 0)
{ {
(weight < 2) ? ((weight < -2) ? oledShowMessage("!! -0") : oledShowWeight(0)) : oledShowWeight(weight); (weight < 0) ? oledShowMessage("!! -1") : oledShowWeight(weight);
} }
// 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 && hasReadRfidTag < 3) if (currentMillis - lastWeightReadTime >= weightReadInterval && hasReadRfidTag < 3)
{ {
@ -196,12 +145,6 @@ void loop() {
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
{ {

View File

@ -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;
} }
@ -244,12 +238,10 @@ void writeJsonToTag(void *parameter) {
hasReadRfidTag = 3; 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
@ -339,7 +331,7 @@ void startWriteJsonToTag(const char* payload) {
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)
@ -375,19 +367,21 @@ void scanRfidTask(void * parameter) {
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
} }
@ -413,13 +407,6 @@ void scanRfidTask(void * parameter) {
hasReadRfidTag = 1; hasReadRfidTag = 1;
} }
free(data);
}
else
{
oledShowMessage("NFC-Tag read error");
hasReadRfidTag = 2;
}
} }
else else
{ {
@ -433,7 +420,7 @@ void scanRfidTask(void * parameter) {
//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

@ -1,243 +1,169 @@
#include <Arduino.h> #include <Arduino.h>
#include <website.h> #include "ota.h"
#include <commonFS.h> #include <Update.h>
#include <SPIFFS.h>
#include "commonFS.h"
#include "bambu.h"
#include "scale.h"
#include "nfc.h"
// Globale Variablen für Config Backups hinzufügen static bool tasksAreStopped = false;
String bambuCredentialsBackup;
String spoolmanUrlBackup;
// Globale Variable für den Update-Typ void stopAllTasks() {
static int currentUpdateCommand = 0; Serial.println("Stopping RFID Reader");
if (RfidReaderTask) vTaskSuspend(RfidReaderTask);
// Globale Update-Variablen Serial.println("Stopping Bambu");
static size_t updateTotalSize = 0; if (BambuMqttTask) vTaskSuspend(BambuMqttTask);
static size_t updateWritten = 0; Serial.println("Stopping Scale");
static bool isSpiffsUpdate = false; if (ScaleTask) vTaskSuspend(ScaleTask);
vTaskDelay(100 / portTICK_PERIOD_MS);
/** Serial.println("All tasks stopped");
* 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 performStageTwo() {
// Bambu Credentials backup if (!SPIFFS.begin(true)) {
if (LittleFS.exists("/bambu_credentials.json")) { Serial.println("Error: Could not mount SPIFFS for stage 2");
File file = LittleFS.open("/bambu_credentials.json", "r"); return;
if (file) {
bambuCredentialsBackup = file.readString();
file.close();
Serial.println("Bambu credentials backed up");
}
} }
// Spoolman URL backup File firmwareFile = SPIFFS.open("/firmware.bin", "r");
if (LittleFS.exists("/spoolman_url.json")) { if (!firmwareFile) {
File file = LittleFS.open("/spoolman_url.json", "r"); Serial.println("Error: Could not open firmware.bin from SPIFFS");
if (file) { return;
spoolmanUrlBackup = file.readString();
file.close();
Serial.println("Spoolman URL backed up");
}
}
} }
void restoreJsonConfigs() { size_t firmwareSize = firmwareFile.size();
// Restore Bambu credentials size_t maxAppSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
if (bambuCredentialsBackup.length() > 0) {
File file = LittleFS.open("/bambu_credentials.json", "w"); Serial.printf("Stage 2 - Firmware size: %u bytes\n", firmwareSize);
if (file) { Serial.printf("Available space: %u bytes\n", maxAppSpace);
file.print(bambuCredentialsBackup);
file.close(); if (firmwareSize > maxAppSpace) {
Serial.println("Bambu credentials restored"); Serial.printf("Error: Not enough space for firmware. Need %u bytes but only have %u bytes\n",
} firmwareSize, maxAppSpace);
bambuCredentialsBackup = ""; // Clear backup return;
} }
// Restore Spoolman URL if (!Update.begin(firmwareSize)) {
if (spoolmanUrlBackup.length() > 0) { Update.printError(Serial);
File file = LittleFS.open("/spoolman_url.json", "w"); return;
if (file) {
file.print(spoolmanUrlBackup);
file.close();
Serial.println("Spoolman URL restored");
}
spoolmanUrlBackup = ""; // Clear backup
}
} }
void espRestart() { size_t written = Update.writeStream(firmwareFile);
yield(); if (written != firmwareSize) {
vTaskDelay(5000 / portTICK_PERIOD_MS); Update.printError(Serial);
return;
}
if (!Update.end(true)) {
Update.printError(Serial);
return;
}
firmwareFile.close();
SPIFFS.remove("/firmware.bin"); // Cleanup
Serial.println("Stage 2 update successful, restarting...");
delay(500);
ESP.restart(); ESP.restart();
} }
void checkForStagedUpdate() {
void sendUpdateProgress(int progress, const char* status = nullptr, const char* message = nullptr) { if (!SPIFFS.begin(true)) {
static int lastSentProgress = -1;
// Verhindere zu häufige Updates
if (progress == lastSentProgress && !status && !message) {
return; return;
} }
String progressMsg = "{\"type\":\"updateProgress\",\"progress\":" + String(progress); if (SPIFFS.exists("/firmware.bin")) {
if (status) { Serial.println("Found staged firmware update, initiating stage 2...");
progressMsg += ",\"status\":\"" + String(status) + "\""; performStageTwo();
} }
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 void handleOTAUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
if (status || abs(progress - lastSentProgress) >= 10 || progress == 100) { static File stagingFile;
for (int i = 0; i < 2; i++) {
ws.textAll(progressMsg);
delay(100); // Längerer Delay zwischen Nachrichten
}
} else {
ws.textAll(progressMsg);
delay(50);
}
lastSentProgress = progress;
}
void handleUpdate(AsyncWebServer &server) {
AsyncCallbackWebHandler* updateHandler = new AsyncCallbackWebHandler();
updateHandler->setUri("/update");
updateHandler->setMethod(HTTP_POST);
// Check if current version is less than defined TOOLVERSION before proceeding with update
if (isVersionLessThan(VERSION, TOOLDVERSION)) {
updateHandler->onRequest([](AsyncWebServerRequest *request) {
request->send(400, "application/json",
"{\"success\":false,\"message\":\"Your current version is too old. Please perform a full upgrade.\"}");
});
server.addHandler(updateHandler);
return;
}
updateHandler->onUpload([](AsyncWebServerRequest *request, String filename,
size_t index, uint8_t *data, size_t len, bool final) {
if (!index) { if (!index) {
updateTotalSize = request->contentLength(); bool isSpiffsUpdate = filename.endsWith("_spiffs.bin");
updateWritten = 0; Serial.printf("Update Start: %s (type: %s)\n", filename.c_str(), isSpiffsUpdate ? "SPIFFS" : "OTA");
isSpiffsUpdate = (filename.indexOf("website") > -1);
if (request->contentLength() == 0) {
request->send(400, "application/json", "{\"status\":\"error\",\"message\":\"Invalid file size\"}");
return;
}
// Stop tasks before update
if (!tasksAreStopped && (RfidReaderTask || BambuMqttTask || ScaleTask)) {
stopAllTasks();
tasksAreStopped = true;
}
size_t updateSize = request->contentLength();
if (isSpiffsUpdate) { if (isSpiffsUpdate) {
// Backup vor dem Update if (!SPIFFS.begin(true)) {
sendUpdateProgress(0, "backup", "Backing up configurations..."); request->send(400, "application/json",
delay(200); "{\"status\":\"error\",\"message\":\"Could not mount SPIFFS\"}");
backupJsonConfigs(); return;
delay(200); }
const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, NULL); // Start SPIFFS update
if (!partition || !Update.begin(partition->size, U_SPIFFS)) { if (!Update.begin(updateSize, U_SPIFFS)) {
request->send(400, "application/json", "{\"success\":false,\"message\":\"Update initialization failed\"}"); Update.printError(Serial);
request->send(400, "application/json",
"{\"status\":\"error\",\"message\":\"SPIFFS update initialization failed\"}");
return; return;
} }
sendUpdateProgress(5, "starting", "Starting SPIFFS update...");
delay(200);
} else { } else {
if (!Update.begin(updateTotalSize)) { // Regular OTA update
request->send(400, "application/json", "{\"success\":false,\"message\":\"Update initialization failed\"}"); stagingFile = SPIFFS.open("/firmware.bin", "w");
if (!stagingFile) {
request->send(400, "application/json",
"{\"status\":\"error\",\"message\":\"Could not create staging file\"}");
return; return;
} }
sendUpdateProgress(0, "starting", "Starting firmware update...");
delay(200);
} }
} }
if (len) { if (stagingFile) {
// Stage 1: Write to SPIFFS
if (stagingFile.write(data, len) != len) {
stagingFile.close();
SPIFFS.remove("/firmware.bin");
request->send(400, "application/json",
"{\"status\":\"error\",\"message\":\"Write to SPIFFS failed\"}");
return;
}
} else {
// Direct SPIFFS update
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\":\"Write failed\"}");
return; return;
} }
updateWritten += len;
int currentProgress;
// Berechne den Fortschritt basierend auf dem Update-Typ
if (isSpiffsUpdate) {
// SPIFFS: 5-75% für Upload
currentProgress = 6 + (updateWritten * 100) / updateTotalSize;
} else {
// Firmware: 0-100% für Upload
currentProgress = 1 + (updateWritten * 100) / updateTotalSize;
}
static int lastProgress = -1;
if (currentProgress != lastProgress && (currentProgress % 10 == 0 || final)) {
sendUpdateProgress(currentProgress, "uploading");
oledShowMessage("Update: " + String(currentProgress) + "%");
delay(50);
lastProgress = currentProgress;
}
} }
if (final) { if (final) {
if (Update.end(true)) { if (stagingFile) {
if (isSpiffsUpdate) { // Finish Stage 1
restoreJsonConfigs(); stagingFile.close();
} Serial.println("Stage 1 complete - firmware staged in SPIFFS");
request->send(200, "application/json",
"{\"status\":\"success\",\"message\":\"Update staged successfully! Starting stage 2...\"}");
performStageTwo();
} else { } else {
request->send(400, "application/json", "{\"success\":false,\"message\":\"Update finalization failed\"}"); // Finish direct SPIFFS update
} if (!Update.end(true)) {
} Update.printError(Serial);
}); request->send(400, "application/json",
"{\"status\":\"error\",\"message\":\"Update failed\"}");
updateHandler->onRequest([](AsyncWebServerRequest *request) {
if (Update.hasError()) {
request->send(400, "application/json", "{\"success\":false,\"message\":\"Update failed\"}");
return; return;
} }
Serial.println("SPIFFS update successful, restarting...");
// Erste 100% Nachricht request->send(200, "application/json",
ws.textAll("{\"type\":\"updateProgress\",\"progress\":100,\"status\":\"success\",\"message\":\"Update successful! Restarting device...\"}"); "{\"status\":\"success\",\"message\":\"SPIFFS update successful! Device will restart...\",\"restart\":true}");
vTaskDelay(2000 / portTICK_PERIOD_MS); delay(500);
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;
@ -16,11 +16,6 @@ int16_t weight = 0;
uint8_t weigthCouterToApi = 0; uint8_t weigthCouterToApi = 0;
uint8_t scale_tare_counter = 0; uint8_t scale_tare_counter = 0;
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";
// ##### Funktionen für Waage ##### // ##### Funktionen für Waage #####
uint8_t tareScale() { uint8_t tareScale() {
@ -47,28 +42,26 @@ void scale_loop(void * parameter) {
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
} }
} }
uint8_t start_scale() { void start_scale() {
Serial.println("Prüfe Calibration Value"); Serial.println("Prüfe Calibration Value");
long 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.getLong(NVS_KEY_CALIBRATION, defaultScaleCalibrationValue);
preferences.end(); //calibrationValue = EEPROM.read(calVal_eepromAdress);
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 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++) {
@ -90,7 +83,7 @@ uint8_t start_scale() {
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. */
@ -101,8 +94,6 @@ uint8_t start_scale() {
} else { } else {
Serial.println("ScaleLoop-Task erfolgreich erstellt"); Serial.println("ScaleLoop-Task erfolgreich erstellt");
} }
return (scaleCalibrated == 1) ? 1 : 3;
} }
uint8_t calibrate_scale() { uint8_t calibrate_scale() {
@ -110,7 +101,6 @@ uint8_t calibrate_scale() {
//vTaskSuspend(RfidReaderTask); //vTaskSuspend(RfidReaderTask);
vTaskDelete(RfidReaderTask); vTaskDelete(RfidReaderTask);
vTaskDelete(ScaleTask);
pauseBambuMqttTask = true; pauseBambuMqttTask = true;
pauseMainTask = 1; pauseMainTask = 1;
@ -147,19 +137,18 @@ 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.putLong(NVS_KEY_CALIBRATION, newCalibrationValue); EEPROM.commit();
preferences.end();
// Verifizieren EEPROM.get(calVal_eepromAdress, newCalibrationValue);
preferences.begin(NVS_NAMESPACE, true); //newCalibrationValue = EEPROM.read(calVal_eepromAdress);
long verifyValue = preferences.getLong(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, revome weight"); Serial.println("End calibration, revome weight");
@ -178,6 +167,8 @@ uint8_t calibrate_scale() {
vTaskDelay(pdMS_TO_TICKS(1)); vTaskDelay(pdMS_TO_TICKS(1));
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
//ESP.restart();
} }
else else
{ {
@ -211,7 +202,8 @@ uint8_t calibrate_scale() {
oledShowMessage("Scale Ready"); oledShowMessage("Scale Ready");
Serial.println("restart Scale Task");
Serial.println("starte Scale Task");
start_scale(); start_scale();
pauseBambuMqttTask = false; pauseBambuMqttTask = false;

View File

@ -5,7 +5,7 @@
#include "HX711.h" #include "HX711.h"
uint8_t start_scale(); void start_scale();
uint8_t calibrate_scale(); uint8_t calibrate_scale();
uint8_t tareScale(); uint8_t tareScale();
@ -14,7 +14,6 @@ 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 pauseMainTask; extern uint8_t pauseMainTask;
extern uint8_t scaleCalibrated;
extern TaskHandle_t ScaleTask; extern TaskHandle_t ScaleTask;

View File

@ -7,16 +7,10 @@
#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");
@ -24,7 +18,6 @@ AsyncWebSocket ws("/ws");
uint8_t lastSuccess = 0; uint8_t lastSuccess = 0;
uint8_t lastHasReadRfidTag = 0; 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!");
@ -35,10 +28,6 @@ void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventTyp
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;
@ -55,7 +44,7 @@ void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventTyp
} }
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);
@ -95,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>());
} }
@ -113,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();
@ -176,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: ");
@ -195,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);
@ -204,7 +173,7 @@ 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); request->send(response);
@ -213,13 +182,24 @@ void setupWebserver(AsyncWebServer &server) {
// 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(LittleFS, "/rfid.html.gz", "text/html"); AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/rfid.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);
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) + "\"}";
@ -229,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);
@ -239,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();
@ -258,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);
@ -280,21 +245,10 @@ 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();
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);
@ -310,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") + "}");
}); });
@ -336,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);
@ -345,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);
@ -354,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");
@ -362,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);
@ -390,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(5);
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