Showing preview only (1,369K chars total). Download the full file or copy to clipboard to get everything.
Repository: cotestatnt/esp-fs-webserver
Branch: master
Commit: 9f5e7e7febbb
Files: 184
Total size: 1.3 MB
Directory structure:
gitextract_673knega/
├── .github/
│ └── workflows/
│ ├── clean_workflow.yml
│ ├── cli-build-esp32-dev.yml
│ ├── cli-build-esp8266.yml
│ ├── pio-build-esp32-dev.yml
│ └── pio-build-esp8266.yml
├── .gitignore
├── LICENSE
├── README.md
├── built-in-webpages/
│ └── readme.md
├── docs/
│ ├── API.md
│ ├── FileEditorAndFS.md
│ ├── SetupAndWiFi.md
│ ├── WebSocket.md
│ ├── pwd_encrypt.md
│ └── readme.md
├── examples/
│ ├── csvLogger/
│ │ ├── .gitignore
│ │ ├── csvLogger.ino
│ │ ├── data/
│ │ │ ├── assets/
│ │ │ │ ├── css/
│ │ │ │ │ ├── index.css
│ │ │ │ │ └── style.css
│ │ │ │ └── js/
│ │ │ │ ├── csv.js
│ │ │ │ └── index.js
│ │ │ ├── csv/
│ │ │ │ └── 2024_01_10.csv
│ │ │ └── index.htm
│ │ ├── partitions.csv
│ │ ├── platformio.ini
│ │ └── readme.md
│ ├── csvLoggerSD/
│ │ ├── csvLoggerSD.ino
│ │ ├── data/
│ │ │ ├── assets/
│ │ │ │ ├── css/
│ │ │ │ │ ├── index.css
│ │ │ │ │ └── style.css
│ │ │ │ └── js/
│ │ │ │ ├── csv.js
│ │ │ │ └── index.js
│ │ │ └── index.htm
│ │ ├── partitions.csv
│ │ ├── platformio.ini
│ │ └── readme.md
│ ├── customHTML/
│ │ ├── customElements.h
│ │ ├── customHTML.ino
│ │ └── thingsboard.h
│ ├── customOptions/
│ │ ├── .gitignore
│ │ ├── customOptions.ino
│ │ ├── partitions.csv
│ │ └── platformio.ini
│ ├── esp32-cam/
│ │ ├── .gitignore
│ │ ├── camera_pins.h
│ │ ├── data/
│ │ │ ├── index.htm
│ │ │ └── www/
│ │ │ ├── app.js
│ │ │ ├── index.htm
│ │ │ └── styles.css
│ │ ├── esp32-cam.ino
│ │ ├── partitions.csv
│ │ └── platformio.ini
│ ├── gpio_list/
│ │ ├── .gitignore
│ │ ├── data/
│ │ │ ├── index.htm
│ │ │ └── script.js
│ │ ├── gpio_list.ino
│ │ ├── partitions.csv
│ │ ├── platformio.ini
│ │ └── readme.md
│ ├── handleFormData/
│ │ ├── .gitignore
│ │ ├── data/
│ │ │ ├── index.htm
│ │ │ ├── myScript.js
│ │ │ └── myStyle.css
│ │ ├── handleFormData.ino
│ │ ├── partitions.csv
│ │ └── platformio.ini
│ ├── leanWebserver/
│ │ ├── .gitignore
│ │ ├── data/
│ │ │ └── index.htm
│ │ ├── leanWebserver.ino
│ │ ├── partitions.csv
│ │ └── platformio.ini
│ ├── localRFID/
│ │ ├── .gitignore
│ │ ├── JsonDB.hpp
│ │ ├── data/
│ │ │ ├── html_login.h
│ │ │ ├── html_rfid.h
│ │ │ ├── login
│ │ │ └── rfid
│ │ ├── localRFID.ino
│ │ ├── partitions.csv
│ │ ├── platformio.ini
│ │ ├── readme.md
│ │ └── webserver.hpp
│ ├── mqtt_webserver/
│ │ ├── .gitignore
│ │ ├── mqtt_webserver.ino
│ │ ├── partitions.csv
│ │ ├── platformio.ini
│ │ └── readme.md
│ ├── mysqlRFID/
│ │ ├── .gitignore
│ │ ├── html_flash_files.h
│ │ ├── mysqlRFID.ino
│ │ ├── mysql_impl.h
│ │ ├── partitions.csv
│ │ ├── platformio.ini
│ │ └── webserver_impl.h
│ ├── remoteOTA/
│ │ ├── data/
│ │ │ └── index.htm
│ │ ├── fw-esp32/
│ │ │ └── readme.md
│ │ ├── fw-esp8266/
│ │ │ └── readme.md
│ │ ├── partitions.csv
│ │ ├── platformio.ini
│ │ ├── remoteOTA.ino
│ │ ├── version-esp32.json
│ │ └── version-esp8266.json
│ ├── simpleServer/
│ │ ├── data/
│ │ │ └── index.htm
│ │ └── simpleServer.ino
│ ├── websocketEcharts/
│ │ ├── README.md
│ │ ├── data/
│ │ │ └── index.htm
│ │ └── websocketEcharts.ino
│ └── withWebSocket/
│ ├── .gitignore
│ ├── index_htm.h
│ ├── partitions.csv
│ ├── platformio.ini
│ ├── readme.md
│ └── withWebSocket.ino
├── keywords.txt
├── library.properties
├── partitions.csv
├── pio_examples/
│ ├── customOptions/
│ │ ├── .gitignore
│ │ ├── customOptions.code-workspace
│ │ ├── platformio.ini
│ │ └── src/
│ │ └── customOptions.ino
│ ├── esp32-p4/
│ │ ├── .gitignore
│ │ ├── app3M_spiffs9M_16MB.csv
│ │ ├── platformio.ini
│ │ └── src/
│ │ ├── index_htm.h
│ │ └── withWebSocket.cpp
│ ├── leanWebserver/
│ │ ├── .gitignore
│ │ ├── data/
│ │ │ └── index.htm
│ │ ├── partitions.csv
│ │ ├── platformio.ini
│ │ └── src/
│ │ └── leanWebserver.ino
│ ├── simpleServer/
│ │ ├── .gitignore
│ │ ├── compile_commands.json
│ │ ├── partitions.csv
│ │ ├── platformio.ini
│ │ └── src/
│ │ └── simpleServer.ino
│ ├── websocketEcharts/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── data/
│ │ │ ├── index.htm
│ │ │ └── index.html
│ │ ├── platformio.ini
│ │ └── src/
│ │ └── websocketEcharts.ino
│ └── withWebSocket/
│ ├── .gitignore
│ ├── partitions.csv
│ ├── platformio.ini
│ ├── readme.md
│ └── src/
│ ├── index_htm.h
│ └── withWebSocket.ino
├── platformio.ini
└── src/
├── ConfigUpgrader.hpp
├── CredentialManager.cpp
├── CredentialManager.h
├── FSWebServer.cpp
├── FSWebServer.h
├── Json.cpp
├── Json.h
├── SerialLog.h
├── SetupConfig.hpp
├── Version.h
├── WiFiService.cpp
├── WiFiService.h
├── assets/
│ ├── edit_htm.h
│ ├── logo_svg.h
│ └── setup_htm.h
├── compat/
│ └── mbedtls_aes.h
├── esp-fs-webserver.h
├── json/
│ ├── cJSON.c
│ └── cJSON.h
├── mimetable/
│ ├── mimetable.cpp
│ └── mimetable.h
└── websocket/
├── SocketIOclient.cpp
├── SocketIOclient.h
├── WebSockets.cpp
├── WebSockets.h
├── WebSocketsClient.cpp
├── WebSocketsClient.h
├── WebSocketsServer.cpp
├── WebSocketsServer.h
├── libb64/
│ ├── AUTHORS
│ ├── LICENSE
│ ├── cdecode.c
│ ├── cdecode_inc.h
│ ├── cencode.c
│ └── cencode_inc.h
└── libsha1/
├── libsha1.c
└── libsha1.h
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/clean_workflow.yml
================================================
name: Clean Workflow Logs
on:
workflow_dispatch:
inputs:
keep_minimum_runs:
description: "Numero di workflow recenti da mantenere"
default: "5"
required: false
jobs:
clean-logs:
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Delete workflow runs
uses: Mattraks/delete-workflow-runs@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
repository: ${{ github.repository }}
retain_days: 0 # Imposta a 0 per ignorare la data e considerare solo keep_minimum_runs
keep_minimum_runs: ${{ github.event.inputs.keep_minimum_runs }}
================================================
FILE: .github/workflows/cli-build-esp32-dev.yml
================================================
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: (ESP32) Build dev with Arduino CLI
on:
workflow_dispatch:
push:
branches:
- dev
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
arduino-esp32:
name: ESP32 (arduino-cli) (shard=${{ matrix.shard }})
if: github.event_name == 'workflow_dispatch' || vars.ENABLE_BUILDS == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install arduino-cli
run: curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=/usr/local/bin sh
- name: Cache Arduino CLI data
uses: actions/cache@v4
with:
key: ${{ runner.os }}-arduino-esp32-${{ hashFiles('.github/workflows/cli-build-esp32-dev.yml') }}-${{ github.run_id }}-${{ matrix.shard }}
restore-keys: |
${{ runner.os }}-arduino-esp32-${{ hashFiles('.github/workflows/cli-build-esp32-dev.yml') }}-
${{ runner.os }}-arduino-esp32-
path: |
~/.arduino15
~/Arduino
~/.cache/arduino-cli
- name: Update core index
run: arduino-cli core update-index --additional-urls https://espressif.github.io/arduino-esp32/package_esp32_index.json
- name: Install core
run: arduino-cli core install --additional-urls https://espressif.github.io/arduino-esp32/package_esp32_index.json esp32:esp32
- name: Install ArduinoJson
run: arduino-cli lib install ArduinoJson
- name: Install Arduino-MySQL
run: ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL=true arduino-cli lib install --git-url https://github.com/cotestatnt/Arduino-MySQL
- name: Install Arduino_MFRC522v2
run: ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL=true arduino-cli lib install --git-url https://github.com/OSSLibraries/Arduino_MFRC522v2
- name: Install PubSubClient
run: ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL=true arduino-cli lib install --git-url https://github.com/knolleary/pubsubclient
- name: Build Examples
run: |
set -uo pipefail
total_shards=4
shard="${{ matrix.shard }}"
report_file="build-report-esp32-arduino-cli-shard${{ matrix.shard }}.txt"
: > "$report_file"
failures=0
total=0
index=0
for dir in examples/*; do
if [[ ! -d "$dir" ]]; then
continue
fi
example="$(basename "$dir")"
# Split examples across shards for faster CI.
index=$((index + 1))
example_shard=$(( (index - 1) % total_shards + 1 ))
if [[ "$example_shard" != "$shard" ]]; then
continue
fi
sketch="examples/${example}/${example}.ino"
total=$((total + 1))
echo "============================================================="
echo "Building ${sketch}..."
echo "============================================================="
if [[ ! -f "$sketch" ]]; then
failures=$((failures + 1))
printf '%-25s : FAIL (missing %s)\n' "${example}" "${sketch}" >> "$report_file"
continue
fi
echo "::group::arduino-cli compile - ${example}"
set +e
arduino-cli compile \
--library . \
--warnings none \
--build-cache-path "$HOME/.cache/arduino-cli" \
-b esp32:esp32:esp32 \
"$sketch"
rc=$?
set -e
echo "::endgroup::"
if [[ $rc -eq 0 ]]; then
printf '%-25s : OK\n' "${example}" >> "$report_file"
else
failures=$((failures + 1))
printf '%-25s : FAIL (exit=%s)\n' "${example}" "$rc" >> "$report_file"
fi
done
echo ""
echo "==================== Build report (arduino-cli) ===================="
cat "$report_file"
echo "==================================================================="
echo "Total attempted: ${total} | Failures: ${failures}"
# Do not fail the job here: the final report job will decide.
- name: Upload build report
if: always()
uses: actions/upload-artifact@v4
with:
name: arduino-cli-esp32-report-shard${{ matrix.shard }}
path: build-report-esp32-arduino-cli-shard${{ matrix.shard }}.txt
report:
name: ESP32 (arduino-cli) - Final report
runs-on: ubuntu-latest
needs: arduino-esp32
if: always() && needs.arduino-esp32.result != 'skipped'
steps:
- name: Download all reports
uses: actions/download-artifact@v4
with:
pattern: arduino-cli-esp32-report-*
merge-multiple: true
path: reports
- name: Print final report and set status
run: |
set -uo pipefail
echo "==================== Final build report (ESP32/arduino-cli) ===================="
failures=0
files=0
shopt -s nullglob
for f in reports/*.txt; do
files=$((files + 1))
echo "--- ${f} ---"
cat "$f"
if grep -q " : FAIL" "$f"; then
failures=$((failures + 1))
fi
done
if [[ $files -eq 0 ]]; then
echo "No report files found (artifact download failed?)"
exit 1
fi
echo "==========================================================================="
if [[ $failures -gt 0 ]]; then
echo "One or more shards reported FAIL."
exit 1
fi
================================================
FILE: .github/workflows/cli-build-esp8266.yml
================================================
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: (ESP8266) Build with Arduino CLI
on:
workflow_dispatch:
push:
branches:
- dev
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
arduino-esp8266:
name: ESP8266 (arduino-cli) (shard=${{ matrix.shard }})
if: github.event_name == 'workflow_dispatch' || vars.ENABLE_BUILDS == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install arduino-cli
run: curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=/usr/local/bin sh
- name: Cache Arduino CLI data
uses: actions/cache@v4
with:
key: ${{ runner.os }}-arduino-esp8266-${{ hashFiles('.github/workflows/cli-build-esp8266.yml') }}-${{ github.run_id }}-${{ matrix.shard }}
restore-keys: |
${{ runner.os }}-arduino-esp8266-${{ hashFiles('.github/workflows/cli-build-esp8266.yml') }}-
${{ runner.os }}-arduino-esp8266-
path: |
~/.arduino15
~/Arduino
~/.cache/arduino-cli
- name: Update core index
run: arduino-cli core update-index --additional-urls https://arduino.esp8266.com/stable/package_esp8266com_index.json
- name: Install core
run: arduino-cli core install --additional-urls https://arduino.esp8266.com/stable/package_esp8266com_index.json esp8266:esp8266
- name: Install ArduinoJson
run: arduino-cli lib install ArduinoJson
- name: Build Examples
run: |
set -uo pipefail
total_shards=4
shard="${{ matrix.shard }}"
# Examples not intended for ESP8266 / this job
EXCLUDE_EXAMPLES=(
esp32-cam
binaryWebSocket
csvLoggerSD
localRFID
mysqlRFID
mqtt_webserver
)
report_file="build-report-esp8266-arduino-cli-shard${{ matrix.shard }}.txt"
: > "$report_file"
failures=0
total=0
index=0
for dir in examples/*; do
if [[ ! -d "$dir" ]]; then
continue
fi
example="$(basename "$dir")"
if [[ " ${EXCLUDE_EXAMPLES[*]} " == *" ${example} "* ]]; then
echo "Skipping: ${example}"
printf '%-25s : SKIP\n' "${example}" >> "$report_file"
continue
fi
# Split examples across shards for faster CI.
index=$((index + 1))
example_shard=$(( (index - 1) % total_shards + 1 ))
if [[ "$example_shard" != "$shard" ]]; then
continue
fi
sketch="examples/${example}/${example}.ino"
total=$((total + 1))
echo "============================================================="
echo "Building ${sketch}..."
echo "============================================================="
if [[ ! -f "$sketch" ]]; then
failures=$((failures + 1))
printf '%-25s : FAIL (missing %s)\n' "${example}" "${sketch}" >> "$report_file"
continue
fi
echo "::group::arduino-cli compile - ${example}"
set +e
arduino-cli compile \
--library . \
--warnings none \
--build-cache-path "$HOME/.cache/arduino-cli" \
-b esp8266:esp8266:huzzah \
"$sketch"
rc=$?
set -e
echo "::endgroup::"
if [[ $rc -eq 0 ]]; then
printf '%-25s : OK\n' "${example}" >> "$report_file"
else
failures=$((failures + 1))
printf '%-25s : FAIL (exit=%s)\n' "${example}" "$rc" >> "$report_file"
fi
done
echo ""
echo "==================== Build report (esp8266/arduino-cli) ===================="
cat "$report_file"
echo "============================================================================="
echo "Total attempted: ${total} | Failures: ${failures}"
# Do not fail the job here: the final report job will decide.
- name: Upload build report
if: always()
uses: actions/upload-artifact@v4
with:
name: arduino-cli-esp8266-report-shard${{ matrix.shard }}
path: build-report-esp8266-arduino-cli-shard${{ matrix.shard }}.txt
report:
name: ESP8266 (arduino-cli) - Final report
runs-on: ubuntu-latest
needs: arduino-esp8266
if: always() && needs.arduino-esp8266.result != 'skipped'
steps:
- name: Download all reports
uses: actions/download-artifact@v4
with:
pattern: arduino-cli-esp8266-report-*
merge-multiple: true
path: reports
- name: Print final report and set status
run: |
set -uo pipefail
echo "==================== Final build report (ESP8266/arduino-cli) ===================="
failures=0
files=0
shopt -s nullglob
for f in reports/*.txt; do
files=$((files + 1))
echo "--- ${f} ---"
cat "$f"
if grep -q " : FAIL" "$f"; then
failures=$((failures + 1))
fi
done
if [[ $files -eq 0 ]]; then
echo "No report files found (artifact download failed?)"
exit 1
fi
echo "==========================================================================="
if [[ $failures -gt 0 ]]; then
echo "One or more shards reported FAIL."
exit 1
fi
================================================
FILE: .github/workflows/pio-build-esp32-dev.yml
================================================
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: (ESP32) Build dev with PlatformIO
on:
workflow_dispatch:
push:
branches:
- dev
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
# Build with PlatformIO - ESP32 Arduino Latest
platformio-esp32-arduino-latest:
name: ESP32 (pio) - Arduino Latest (board=${{ matrix.board }}, shard=${{ matrix.shard }})
if: github.event_name == 'workflow_dispatch' || vars.ENABLE_BUILDS == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
board:
- esp32dev
- esp32-s3-devkitc-1
shard: [1, 2, 3, 4]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Cache PlatformIO
uses: actions/cache@v4
with:
key: ${{ runner.os }}-pio
path: |
~/.cache/pip
~/.platformio
- name: Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install PIO
run: |
python -m pip install --upgrade pip
pip install --upgrade platformio
- name: Build Examples
run: |
set -uo pipefail
total_shards=4
shard="${{ matrix.shard }}"
report_file="build-report-${{ matrix.board }}-shard${{ matrix.shard }}.txt"
: > "$report_file"
failures=0
total=0
index=0
for dir in examples/*; do
if [[ ! -d "$dir" ]]; then
continue
fi
example="$(basename "$dir")"
board_to_use="${{ matrix.board }}"
# Split examples across shards for faster CI.
index=$((index + 1))
example_shard=$(( (index - 1) % total_shards + 1 ))
if [[ "$example_shard" != "$shard" ]]; then
continue
fi
# Avoid duplicating esp32-cam across the board matrix.
if [[ "$example" == "esp32-cam" && "${{ matrix.board }}" != "esp32dev" ]]; then
printf '%-25s : SKIP (board-specific)\n' "${example}" >> "$report_file"
continue
fi
# Some examples are board-specific
if [[ "$example" == "esp32-cam" ]]; then
board_to_use="esp32cam"
fi
total=$((total + 1))
echo "============================================================="
echo "Building examples/${example} (board=${board_to_use})..."
echo "============================================================="
echo "::group::pio run - examples/${example}"
set +e
# Use a per-example build directory to avoid cross-example artefacts.
PLATFORMIO_BUILD_DIR=".pio/build-${board_to_use}-${example}" \
PLATFORMIO_SRC_DIR="examples/${example}" PIO_BOARD="${board_to_use}" \
pio run -e ci-arduino-3-latest
rc=$?
set -e
echo "::endgroup::"
if [[ $rc -eq 0 ]]; then
printf '%-25s : OK\n' "${example}" >> "$report_file"
else
failures=$((failures + 1))
printf '%-25s : FAIL (exit=%s)\n' "${example}" "$rc" >> "$report_file"
fi
done
echo ""
echo "==================== Build report (board=${{ matrix.board }}) ===================="
cat "$report_file"
echo "============================================================================="
echo "Total attempted: ${total} | Failures: ${failures}"
# Do not fail the job here: the final report job will decide.
- name: Upload build report
if: always()
uses: actions/upload-artifact@v4
with:
name: pio-esp32-report-${{ matrix.board }}-shard${{ matrix.shard }}
path: build-report-${{ matrix.board }}-shard${{ matrix.shard }}.txt
report:
name: ESP32 (pio) - Final report
runs-on: ubuntu-latest
needs: platformio-esp32-arduino-latest
if: always() && needs.platformio-esp32-arduino-latest.result != 'skipped'
steps:
- name: Download all reports
uses: actions/download-artifact@v4
with:
pattern: pio-esp32-report-*
merge-multiple: true
path: reports
- name: Print final report and set status
run: |
set -uo pipefail
echo "==================== Final build report (ESP32/pio) ===================="
failures=0
files=0
shopt -s nullglob
for f in reports/*.txt; do
files=$((files + 1))
echo "--- ${f} ---"
cat "$f"
if grep -q " : FAIL" "$f"; then
failures=$((failures + 1))
fi
done
if [[ $files -eq 0 ]]; then
echo "No report files found (artifact download failed?)"
exit 1
fi
echo "======================================================================="
if [[ $failures -gt 0 ]]; then
echo "One or more shards reported FAIL."
exit 1
fi
================================================
FILE: .github/workflows/pio-build-esp8266.yml
================================================
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: (ESP8266) Build with PlatformIO
on:
workflow_dispatch:
push:
branches:
- dev
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
platformio-esp8266:
name: ESP8266 (pio) (board=${{ matrix.board }}, shard=${{ matrix.shard }})
if: github.event_name == 'workflow_dispatch' || vars.ENABLE_BUILDS == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
board:
- d1_mini
shard: [1, 2, 3, 4]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Cache PlatformIO
uses: actions/cache@v4
with:
key: ${{ runner.os }}-pio
path: |
~/.cache/pip
~/.platformio
- name: Python
uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Install PIO
run: |
python -m pip install --upgrade pip
pip install --upgrade platformio
- name: Build Examples
run: |
set -uo pipefail
total_shards=4
shard="${{ matrix.shard }}"
# Examples not intended for ESP8266 / this job
EXCLUDE_EXAMPLES=(
esp32-cam
binaryWebSocket
csvLoggerSD
localRFID
mysqlRFID
mqtt_webserver
)
report_file="build-report-esp8266-pio-${{ matrix.board }}-shard${{ matrix.shard }}.txt"
: > "$report_file"
failures=0
total=0
index=0
for dir in examples/*; do
if [[ ! -d "$dir" ]]; then
continue
fi
example="$(basename "$dir")"
if [[ " ${EXCLUDE_EXAMPLES[*]} " == *" ${example} "* ]]; then
echo "Skipping: ${example}"
printf '%-25s : SKIP\n' "${example}" >> "$report_file"
continue
fi
# Split examples across shards for faster CI.
index=$((index + 1))
example_shard=$(( (index - 1) % total_shards + 1 ))
if [[ "$example_shard" != "$shard" ]]; then
continue
fi
total=$((total + 1))
echo "============================================================="
echo "Building examples/${example} (board=${{ matrix.board }}, shard=${shard})..."
echo "============================================================="
echo "::group::pio run - examples/${example}"
set +e
# Use a per-example build directory to avoid cross-example artefacts.
PLATFORMIO_BUILD_DIR=".pio/build-${{ matrix.board }}-${example}" \
PLATFORMIO_SRC_DIR="examples/${example}" PIO_BOARD="${{ matrix.board }}" \
pio run -e ci-esp8266
rc=$?
set -e
echo "::endgroup::"
if [[ $rc -eq 0 ]]; then
printf '%-25s : OK\n' "${example}" >> "$report_file"
else
failures=$((failures + 1))
printf '%-25s : FAIL (exit=%s)\n' "${example}" "$rc" >> "$report_file"
fi
done
echo ""
echo "==================== Build report (esp8266/pio, board=${{ matrix.board }}, shard=${{ matrix.shard }}) ===================="
cat "$report_file"
echo "==============================================================================================="
echo "Total attempted: ${total} | Failures: ${failures}"
# Do not fail the job here: the final report job will decide.
- name: Upload build report
if: always()
uses: actions/upload-artifact@v4
with:
name: pio-esp8266-report-${{ matrix.board }}-shard${{ matrix.shard }}
path: build-report-esp8266-pio-${{ matrix.board }}-shard${{ matrix.shard }}.txt
report:
name: ESP8266 (pio) - Final report
runs-on: ubuntu-latest
needs: platformio-esp8266
if: always() && needs.platformio-esp8266.result != 'skipped'
steps:
- name: Download all reports
uses: actions/download-artifact@v4
with:
pattern: pio-esp8266-report-*
merge-multiple: true
path: reports
- name: Print final report and set status
run: |
set -uo pipefail
echo "==================== Final build report (ESP8266/pio) ===================="
failures=0
files=0
shopt -s nullglob
for f in reports/*.txt; do
files=$((files + 1))
echo "--- ${f} ---"
cat "$f"
if grep -q " : FAIL" "$f"; then
failures=$((failures + 1))
fi
done
if [[ $files -eq 0 ]]; then
echo "No report files found (artifact download failed?)"
exit 1
fi
echo "==========================================================================="
if [[ $failures -gt 0 ]]; then
echo "One or more shards reported FAIL."
exit 1
fi
================================================
FILE: .gitignore
================================================
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# PlatformIO in VScode
.pio/
.vscode/
*.lnk
*.bak
/build
/built-in-webpages/setup/build_setup/node_modules
/examples/customHTML/build
/pio_examples/temp
/pio_examples/simpleServer/setup/build_setup/node_modules
/built-in-webpages/edit/build_edit/node_modules
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
# ESP-FS-WebServer
A library for ESP8266/ESP32 that provides a web server with an integrated file system browser, WiFi configuration manager, and support for WebSockets. This library is based on the synchronous `WebServer` class.

## Features
- **Dynamic WiFi Configuration**: An integrated setup page (`/setup`) allows you to scan for WiFi networks and connect the ESP to your local network. Passwords are stored using AES-256-CBC encryption (hardware on ESP32 platform) with multiple SSIDs manager (up to 5 different WiFi credentials).
- **Powerful & Customizable UI**:
- Easily add your own configuration parameters (text boxes, checkboxes, sliders, dropdown lists) to the setup page.
- Inject custom **HTML, CSS, and JavaScript** snippets into the setup page to create rich, dynamic user interfaces for your specific project needs.
- **Over-the-Air (OTA) Updates**: Update your device's firmware securely and conveniently through the web interface. You can easily upload also your entire web project's `data` folder to the ESP's filesystem.
- **WebSocket Support**: Built-in support for real-time, two-way communication between the web client and the ESP.
- **Advanced File Management**: An embedded file manager (`/edit`) allows you to browse, view, upload, and delete files and folders.
Built-in `/setup` and `/edit` page sources are shared with the async library and are maintained in `C:\Cloud\fs-webserver-shared-pages`. Regenerate the embedded headers from that repository instead of editing generated files in `src/assets` directly.
## Documentation
For more detailed information, please refer to the documentation in the `docs` folder:
- **[API Reference](docs/API.md)** – Detailed overview of the public methods.
- **[Setup and WiFi](docs/SetupAndWiFi.md)** – Guide to `startWiFi()`, captive portal, and the `/setup` page.
- **[Filesystem and Editor](docs/FileEditorAndFS.md)** – How to serve static files and use the `/edit` page.
- **[WebSocket](docs/WebSocket.md)** – Information on using the WebSocket server.
- **[Password Encryption](docs/pwd_encrypt.md)** – Information on how passwords are managed.
## Dependencies
- ESP8266/ESP32 Core for Arduino
## Basic Usage
```cpp
#include <FS.h>
#include <LittleFS.h>
#if defined(ESP8266)
#include <ESP8266WiFi.h>
#else
#include <WiFi.h>
#endif
#include "FSWebServer.h"
// Use LittleFS
fs::FS& filesystem = LittleFS;
FSWebServer server(80, filesystem, "esphost");
void setup() {
Serial.begin(115200);
// Initialize Filesystem
if (!filesystem.begin()) {
Serial.println("Error mounting file system.");
return;
}
// Start WiFi (or captive portal if no credentials)
server.startWiFi(10000);
// Add a handler for the root page
server.on("/", []() {
server.send(200, "text/html", "<h1>Hello from FSWebServer!</h1>");
});
// Start the server
server.begin();
Serial.println("HTTP server started.");
}
void loop() {
server.run();
}
```
## WiFi Management Enhancements
### Dedicated WiFiService class
- `WiFiService` centralizes scanning, connections, captive portal setup, MDNS, and watchdog handling so the sketch can stay focused on application logic.
- When `startWiFi()` runs it loads credentials from `CredentialManager`, picks the SSID with the strongest RSSI (optionally honoring static IP data saved per credential), and reports a `WiFiStartResult` that tells `FSWebServer` whether to stay in STA or fall back to the captive portal.
- `doWifiConnection()` now persists updated credentials before calling `WiFiService::connectWithParams()`, which feeds the long-timeout watchdog, reuses stored passwords when the form leaves the password blank, and advertises `http://<hostname>.local` through `startMDNSOnly()` as soon as the STA link is up. `FSWebServer` logs when mDNS becomes reachable so you can verify the hostname was published.
### Setup page reconnection experience
- The `/getStatus` handler exposes firmware version, active mode, hostname, IP, and the configuration file path so the UI knows where to direct the user after a network change.
- After `/connect` begins switching SSIDs the browser starts polling `http://<hostname>.local/getStatus` (falling back to the captured AP when necessary) until the ESP reappears on the new network, automatically updates the credential list, and surfaces a timeout message only after several failed polls.
- This polling loop keeps the loader visible, shows a modal once the device is reachable again, and points users to either the new `http://<hostname>.local` URL or the IP reported by the ESP without forcing them to refresh manually.
### Custom application "options manager"

### OTA update and `data` folder upload

### Custom 'code snippets'

### ACE file editor

================================================
FILE: built-in-webpages/readme.md
================================================
The canonical built-in page sources are no longer maintained in this folder.
Use the shared repository at `C:\Cloud\fs-webserver-shared-pages` as the single source of truth for both **AsyncFsWebServer** and **FSWebServer** built-in pages.
Current workflow:
* edit `/setup` sources in `C:\Cloud\fs-webserver-shared-pages\setup`
* edit `/edit` sources in `C:\Cloud\fs-webserver-shared-pages\edit`
* open a terminal in `C:\Cloud\fs-webserver-shared-pages`
* run `npm install` once
* run `npm run build` to regenerate `setup_htm.h`, `logo_svg.h`, and `edit_htm.h` in both library `src/assets` folders
Target resolution order in the shared repo:
* `--target <path>` CLI arguments
* `targets.json`
* autodiscovery of the Arduino sketchbook `libraries` folder
This folder is kept only as historical reference and should not be used as the primary build entrypoint anymore.
================================================
FILE: docs/API.md
================================================
# FSWebServer – API (overview)
This page summarizes the main methods exposed by `FSWebServer` and when to use them.
> Note: some APIs are available only when the related features are enabled via macros (e.g. `ESP_FS_WS_SETUP`, `ESP_FS_WS_EDIT`).
## Constructor
```cpp
FSWebServer(uint16_t port, fs::FS &fs, const char* hostname = "");
```
- `port`: HTTP port (typically `80`)
- `fs`: filesystem (`LittleFS`, `SPIFFS`, `FFat`, …)
- `hostname`: optional (also used for mDNS)
## Server start
```cpp
void begin(WebSocketsServer::WebSocketServerEvent wsEventHandler = nullptr);
```
- Registers built-in handlers (setup/edit if enabled), static file serving, and notFound.
- If `wsEventHandler != nullptr`, creates and starts a websocket server.
- Starts the webserver.
## Runtime info
```cpp
IPAddress getServerIP();
bool isAccessPointMode() const;
```
## Authentication (/setup page)
```cpp
void setAuthentication(const char* user, const char* pswd);
```
When set, the `/setup` page requires basic-auth.
## Filesystem listing
```cpp
void printFileList(fs::FS &fs, const char * dirname, uint8_t levels);
void printFileList(fs::FS &fs, const char * dirname, uint8_t levels, Print& out);
```
- The `Print& out` overload lets you send output to streams other than `Serial`.
Example:
```cpp
server.printFileList(FILESYSTEM, "/", 1); // default -> Serial
server.printFileList(FILESYSTEM, "/", 1, Serial); // explicit
```
## WiFi + captive portal
```cpp
bool startWiFi(uint32_t timeout, CallbackF fn = nullptr);
bool startCaptivePortal(const char* ssid, const char* pass, const char* redirectTargetURL);
```
- `startWiFi()` tries to connect using already-saved credentials.
- If it fails, you typically start `startCaptivePortal()` and use `/setup` to configure.
## WebSocket (runtime)
```cpp
WebSocketsServer* getWebSocketServer();
bool broadcastWebSocket(const String &payload);
bool broadcastWebSocket(const uint8_t *payload, size_t length);
bool sendWebSocket(uint8_t num, const String &payload);
```
## Setup page (only if `ESP_FS_WS_SETUP`)
Config file and callback:
```cpp
File getConfigFile(const char* mode);
const char* getConfiFileName();
bool clearConfigFile();
void setConfigSavedCallback(ConfigSavedCallbackF callback);
```
Options and setup UI:
```cpp
void setSetupPageTitle(const char* title);
void addOptionBox(const char* title);
// attach a comment string to an existing option element
// For most controls the comment is rendered in a separate line under the input.
// Boolean (checkbox) fields use a <span> so the text stays on the same line.
void addComment(const char *lbl, const char *comment);
// boolean option: grouped by default, pass fourth argument false to keep declaration order
// the third parameter specifies `hidden` exactly like the generic template
void addOption(const char *lbl, bool val, bool hidden = false, bool grouped = true);
// boolean overload that accepts a comment
void addOption(const char *lbl, bool val, const char *comment,
bool hidden = false, bool grouped = true);
// generic templated version (bool excluded via SFINAE below)
template <typename T>
void addOption(const char *lbl, T val, bool hidden=false, double min=MIN_F, double max=MAX_F, double st=1.0);
// convenience comment overload for non-bool types
// use the bool-specific variant to add a comment to a checkbox
template <typename T, typename std::enable_if<!std::is_same<T,bool>::value, int>::type = 0>
void addOption(const char *lbl, T val, const char *comment);
template <typename T>
void addOption(const char *lbl, T val, bool hidden=false, double min=MIN_F, double max=MAX_F, double st=1.0);
template <typename T>
bool getOptionValue(const char *lbl, T &var);
template <typename T>
bool saveOptionValue(const char *lbl, T val);
```
Boolean options are now controlled per-option; the third parameter (or the `grouped` argument) determines whether the switch/check is collected with other booleans or left inline. Hidden behaviour still works via `hidden` argument.
Dropdown/Slider:
```cpp
using DropdownList = FSWebServer::DropdownList;
using Slider = FSWebServer::Slider;
void addDropdownList(DropdownList &def);
void addSlider(Slider &def);
bool getDropdownSelection(DropdownList &def);
bool getSliderValue(Slider &def);
```
## Web file editor (only if `ESP_FS_WS_EDIT`)
```cpp
void enableFsCodeEditor(FsInfoCallbackF fsCallback = nullptr);
void setFsInfoCallback(FsInfoCallbackF fsCallback);
```
See also: `SetupAndWiFi.md`, `FileEditorAndFS.md`, `WebSocket.md`.
================================================
FILE: docs/FileEditorAndFS.md
================================================
# Filesystem + Web File Editor (/edit)
The library serves static files from the filesystem and, optionally, includes a web editor (ACE) to manage files directly from the browser.
## Static file serving
By default, files are served from the filesystem with:
- root URL `/` -> filesystem path `/`
- default file: `index.htm`
In code (internally):
- `serveStatic("/", fs, "/").setDefaultFile("index.htm")`
## Stampa contenuto filesystem (debug)
## Print filesystem contents (debug)
```cpp
server.printFileList(FILESYSTEM, "/", 1); // default -> Serial
server.printFileList(FILESYSTEM, "/", 1, Serial); // or to a chosen stream
```
The `Print&` overload is useful to log to streams other than `Serial`.
## Enable /edit
```cpp
server.enableFsCodeEditor();
```
Main endpoints:
- `GET /edit` editor page
- `GET /list?dir=/` directory listing
- `POST /edit` upload
- `PUT /edit` create/rename
- `DELETE /edit` delete
## Provide filesystem info (recommended on ESP32)
On ESP32, to show correct “total/used bytes” in the UI:
```cpp
server.setFsInfoCallback([](fsInfo_t* fsInfo) {
fsInfo->fsName = "LittleFS";
fsInfo->totalBytes = LittleFS.totalBytes();
fsInfo->usedBytes = LittleFS.usedBytes();
});
```
================================================
FILE: docs/SetupAndWiFi.md
================================================
# Setup + WiFi (startWiFi / captive portal / config)
This library can:
- try to connect to a previously saved WiFi network (STA)
- if it fails, start an Access Point + captive portal and serve `/setup`
- persist **application options** into `config.json` on the filesystem
(WiFi credentials are now handled separately by `CredentialManager` and **never** stored in `config.json`)
## Typical flow
```cpp
if (!server.startWiFi(10000)) {
server.startCaptivePortal("ESP_AP", "123456789", "/setup");
}
server.init(onWsEvent);
```
1. `startWiFi(timeout)` attempts to connect using saved credentials.
2. If it fails, `startCaptivePortal(ssid, pass, "/setup")` switches the ESP to AP mode and redirects requests to `/setup` (or a custom endpoint)
3. On `/setup` the user selects the WiFi network (managed by `CredentialManager`) and any extra application options;
the library saves **only the application options** to `config.json` and stores WiFi credentials in encrypted form via `CredentialManager`.
It's possible also change the setting of IP address and mask for the captive portal passing a `WiFiConnectParams` structs to `startCaptivePortal()` method.
```cpp
if (!server.startWiFi(10000)) {
Serial.println("\nWiFi not connected! Starting AP mode...");
WiFiConnectParams params ("ESP_AP", "123456789");
params.config.local_ip = IPAddress(192, 168, 1, 1);
params.config.gateway = IPAddress(192, 168, 1, 1);
params.config.subnet = IPAddress(255, 255, 255, 0);
server.startCaptivePortal(params, "/setup");
}
```
## Application options on /setup
Example (see also `withWebSocket.ino`):
```cpp
server.addOptionBox("My Options");
server.addOption("LED Pin", ledPin);
server.addOption("Option 1", option1.c_str());
server.addOption("Option 2", option2);
// you can also add a comment for a control that will appear beneath it
server.addComment("Option 2", "Explanation or hint text goes here");
```
Read your application options at boot (from `config.json`, WiFi excluded):
```cpp
uint32_t option2;
server.getOptionValue("Option 2", option2);
```
## Config file: read/write
- Full path: `server.getConfiFileName()`
- File access: `server.getConfigFile("r")` / `server.getConfigFile("w")`
- Reset config: `server.clearConfigFile()`
“Config saved” callback (useful when saving via `/edit` or upload):
```cpp
server.setConfigSavedCallback([](const char* filename){
Serial.printf("Config saved: %s\n", filename);
});
```
## WiFi credentials storage (CredentialManager)
- WiFi SSID, password, DHCP/static IP and related data are **not** stored in `config.json`.
- They are managed and stored (encrypted) by the internal `CredentialManager`:
- ESP32: NVS
- ESP8266: filesystem (e.g. LittleFS)
- The `/setup` page talks directly with the WiFi APIs (`/wifi/credentials`, `/connect`, etc.),
so your sketch usually does **not** need to read or write WiFi data manually.
See also: `pwd_encrypt.md` for a deeper overview of `CredentialManager` and encrypted WiFi storage.
## Protect /setup with basic-auth
```cpp
server.setAuthentication("admin", "admin");
```
When set, the `/setup` page requires authentication.
================================================
FILE: docs/WebSocket.md
================================================
# WebSocket Support
The library includes and uses [WebSockets](https://github.com/Links2004/arduino-WebSockets) by Markus Sattler for bidirectional communication with web clients.
Support is enabled by default via the `ESP_FS_WS_WEBSOCKET` macro.
## 1. Create the Event Handler
First, you need to define a function that will handle WebSocket events. This function must match the `WebSocketServerEvent` signature.
```cpp
// WebSocket event handler
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
switch (type) {
case WStype_DISCONNECTED:
Serial.printf("[%u] Disconnected!\n", num);
break;
case WStype_CONNECTED:
{
IPAddress ip = server.getWebSocketServer()->remoteIP(num);
Serial.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
// Send a welcome message to the newly connected client
server.getWebSocketServer()->sendTXT(num, "Hello from FSWebServer!");
}
break;
case WStype_TEXT:
Serial.printf("[%u] Received text: %s\n", num, payload);
// Echo the message back to the client
server.getWebSocketServer()->sendTXT(num, payload);
break;
case WStype_BIN:
Serial.printf("[%u] Received binary data of length %u\n", num, length);
// hexdump(payload, length); // Example for printing binary data
break;
default:
Serial.printf("Unhandled event type: %d\n", type);
break;
}
}
```
- `num`: The client ID (an integer from 0 up to the maximum number of clients).
- `type`: The type of event (e.g., `WStype_CONNECTED`, `WStype_DISCONNECTED`, `WStype_TEXT`).
- `payload`: A pointer to the received data.
- `length`: The length of the received data.
## 2. Start the Server with the Handler
Pass your event handler function to the `server.begin()` method. This will automatically start the WebSocket server.
```cpp
void setup() {
// ... other setup code ...
// Start the web server and enable WebSockets
server.begin(webSocketEvent);
}
```
## 3. Sending Messages from the ESP
You have two ways to send messages to clients.
### Simple Broadcast (Recommended)
Use the helper methods built into `FSWebServer` to easily broadcast messages to all connected clients.
```cpp
// Broadcast a text message
server.broadcastWebSocket("This is a message for everyone.");
// Broadcast binary data
uint8_t binaryPayload[] = {0xDE, 0xAD, 0xBE, 0xEF};
server.broadcastWebSocket(binaryPayload, sizeof(binaryPayload));
```
### Advanced Control
For more advanced scenarios, like sending a message to a specific client, you can get a pointer to the underlying `WebSocketsServer` object.
```cpp
// Get the WebSocket server instance
WebSocketsServer* ws = server.getWebSocketServer();
if (ws) {
// Send a text message to client number 2
ws->sendTXT(2, "This is a private message for you.");
// Disconnect client number 3
ws->disconnect(3);
}
```
This gives you full access to the underlying WebSocket library's API.
================================================
FILE: docs/pwd_encrypt.md
================================================
# FSWebServer CredentialManager Integration
✅ **Encrypted Password Storage** - AES-256-CBC encryption
✅ **Automatic Persistence** - NVS (ESP32) or Filesystem (ESP8266)
✅ **Multi-SSID Support** - Store up to 5 WiFi networks (configurable)
✅ **FIFO Management** - Automatically remove oldest when full
✅ **RSSI-Based Selection** - Connect to strongest signal
✅ **Zero Configuration** - Works transparently with FSWebServer
✅ **Cross-Platform** - ESP32 and ESP8266 compatible
## Quick Start
### Set Encryption Key
```cpp
// In CredentialManager.h, change this:
#define CREDENTIAL_MANAGER_ENCRYPTION_KEY "YOUR_SECRET_KEY_16_CHARS!"
// To something like:
#define CREDENTIAL_MANAGER_ENCRYPTION_KEY "MyCompany2024Key!"
```
Then:
1. Open `/setup` to add WiFi credentials
2. Reboot device
3. Check serial logs for RSSI-based connection
---
## Architecture Overview
```
┌─────────────────────────────────────────────────────┐
│ User's Arduino Sketch │
│ │
│ - Calls: server.begin() │
│ - Calls: server.run() │
│ - No credential management code needed │
└─────────────────────────┬───────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Modified FSWebServer Library │
│ │
│ • Constructor: Load credentials from storage │
│ • startWiFi(): RSSI-based WiFi selection │
│ • doWifiConnection(): FIFO credential mgmt │
└─────────────────────────┬───────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ CredentialManager (New Class) │
│ │
│ • AES-256-CBC encryption/decryption │
│ • NVS persistence (ESP32) │
│ • Filesystem persistence (ESP8266) │
│ • Credential list management │
│ • FIFO removal when full │
└─────────────────────────┬───────────────────────────┘
│
┌─────────────┴─────────────┐
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ NVS Flash │ │ Filesystem │
│ (ESP32) │ │ (ESP8266/ESP32) │
│ Encrypted │ │ Encrypted │
│ Passwords │ │ Passwords │
└──────────────┘ └──────────────────┘
```
---
## Key Features Explained
### 1. Transparent Credential Management
- User adds credentials via `/setup` web page
- Credentials automatically saved to encrypted storage
- No code changes needed in user sketch
- Credentials persist across power cycles
### 2. FIFO (First In, First Out) Management
```
When list is full (5 credentials):
Add new credential → Automatically remove oldest → Keep 5 total
```
### 3. RSSI-Based WiFi Selection
```
On startup:
1. Scan available networks
2. Find stored credentials that match
3. Select the one with best (highest) signal strength
4. Connect to that network
```
**Example**:
- HomeWiFi: -65 dBm (good)
- OfficeWiFi: -42 dBm (excellent)
→ Connect to OfficeWiFi (higher value = better signal)
### 4. Encryption
```
User input: "MyPassword123"
↓ (AES-256-CBC encrypt)
Stored: "aX7k3jQ9pL2nM8vX..."
↓ (AES-256-CBC decrypt)
Retrieved: "MyPassword123"
```
### 5. Cross-Platform Support
```
ESP32: Uses NVS (secure, encrypted by hardware)
ESP8266: Uses LittleFS/SPIFFS (plaintext storage file)
Both: Passwords encrypted by CredentialManager
```
## Security Considerations
### Password Encryption
- **Algorithm**: AES-256-CBC (256-bit encryption)
- **Padding**: PKCS7 (standard padding)
- **Mode**: Cipher Block Chaining (secure against pattern attacks)
### Encryption Key Storage
- **ESP32**: Ideally in eFuse BLOCK_KEY0 (hardware protected)
```bash
# Generate and set key:
espefuse.py key_set BLOCK_KEY0 <32-byte-hex-key>
```
- **ESP8266**: Compile-time constant (less secure)
```cpp
#define CREDENTIAL_MANAGER_ENCRYPTION_KEY "MySecretKey12345"
```
### Additional Security Measures
- Use **Secure Boot** to prevent firmware tampering
- Use **eFuse** to lock down critical settings
- Keep encryption key confidential and consistent
- Change key only if you can re-encrypt stored credentials
### Threat Model
- **Protected Against**: Flash dump reading (passwords encrypted)
- **Protected Against**: Casual inspection of storage
- **Not Protected Against**: Brute-force attacks on weak keys
- **Not Protected Against**: Side-channel attacks (e.g., timing)
---
## Customization Options
### Change Maximum Credentials
In FSWebServer.h:
```cpp
static constexpr uint8_t MAX_CREDENTIALS = 5; // Change to 10 for more
```
### Change Encryption Key
In CredentialManager.h:
```cpp
#define CREDENTIAL_MANAGER_ENCRYPTION_KEY "YourCustomKey12!"
```
---
## Performance
### Startup Time Impact
- WiFi scan: 500-2000ms (depends on RF environment)
- Credential matching: 10-50ms
- Connection: 2-5 seconds
- **Total**: ~3-7 seconds (typical)
### Memory Usage
- CredentialManager class: ~500 bytes
- Per credential: ~100 bytes
- Vector overhead: ~20 bytes
- **Total for 5 credentials**: ~1.2 KB
### Storage Usage
- Per credential in NVS/FS: ~60 bytes encrypted
- For 5 credentials: ~300 bytes
- **Impact**: Negligible on ESP32 (512KB NVS)
---
================================================
FILE: docs/readme.md
================================================
# Documentation
- [API](API.md) – available methods and what they do
- [Setup + WiFi](SetupAndWiFi.md) – `startWiFi()`, captive portal, `/setup`, config
- [Filesystem + Editor](FileEditorAndFS.md) – static file serving, `/edit`, FS info
- [WebSocket](WebSocket.md) – enablement, handler, broadcast
================================================
FILE: examples/csvLogger/.gitignore
================================================
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
================================================
FILE: examples/csvLogger/csvLogger.ino
================================================
#include <Arduino.h>
#include <FS.h>
#include <FSWebServer.h>
#include <LittleFS.h>
FSWebServer server(LittleFS, 80, "esphost");
// Timezone definition to get properly time from NTP server
#define MYTZ "CET-1CEST,M3.5.0,M10.5.0/3"
#include <time.h>
struct tm ntpTime;
const char* basePath = "/csv";
//////////////////////////////// Filesystem
////////////////////////////////////////////
bool startFilesystem() {
if (LittleFS.begin()) {
server.printFileList(LittleFS, "/", 2, Serial);
return true;
} else {
Serial.println("ERROR on mounting filesystem. It will be formmatted!");
LittleFS.format();
ESP.restart();
}
return false;
}
//////////////////////////// Append a row to csv file
//////////////////////////////////////
bool appenRow() {
getLocalTime(&ntpTime, 10);
char filename[32];
snprintf(filename, sizeof(filename), "%s/%04d_%02d_%02d.csv", basePath,
ntpTime.tm_year + 1900, ntpTime.tm_mon + 1, ntpTime.tm_mday);
File file;
if (LittleFS.exists(filename)) {
file = LittleFS.open(filename, "a"); // Append to existing file
} else {
file = LittleFS.open(filename, "w"); // Create a new file
file.println("timestamp, free heap, largest free block, connected, wifi strength");
}
if (file) {
char timestamp[25];
strftime(timestamp, sizeof(timestamp), "%c", &ntpTime);
char row[64];
#ifdef ESP32
snprintf(row, sizeof(row), "%s, %d, %d, %s, %d", timestamp,
heap_caps_get_free_size(0), heap_caps_get_largest_free_block(0),
(WiFi.status() == WL_CONNECTED) ? "true" : "false", WiFi.RSSI());
#elif defined(ESP8266)
uint32_t free;
uint32_t max;
ESP.getHeapStats(&free, &max, nullptr);
snprintf(row, sizeof(row), "%s, %d, %d, %s, %d", timestamp, free, max,
(WiFi.status() == WL_CONNECTED) ? "true" : "false", WiFi.RSSI());
#endif
Serial.println(row);
file.println(row);
file.close();
return true;
}
return false;
}
void setup() {
Serial.begin(115200);
delay(1000);
startFilesystem();
// Try to connect to WiFi (will start AP if not connected after timeout)
if (!server.startWiFi(10000)) {
Serial.println("\nWiFi not connected! Starting AP mode...");
server.startCaptivePortal("ESP32_LOGGER", "123456789", "/setup");
}
// Enable ACE FS file web editor and add FS info callback fucntion
server.enableFsCodeEditor();
// Start server
server.begin();
Serial.print(F("Async ESP Web Server started on IP Address: "));
Serial.println(server.getServerIP());
Serial.println(
F("This is \"scvLogger.ino\" example.\n"
"Open /setup page to configure optional parameters.\n"
"Open /edit page to view, edit or upload example or your custom "
"webserver source files."));
// Set NTP servers
#ifdef ESP8266
configTime(MYTZ, "time.google.com", "time.windows.com", "pool.ntp.org");
#elif defined(ESP32)
configTzTime(MYTZ, "time.google.com", "time.windows.com", "pool.ntp.org");
#endif
// Wait for NTP sync (with timeout)
getLocalTime(&ntpTime, 5000);
// Create csv logs folder if not exists
if (!LittleFS.exists(basePath)) {
LittleFS.mkdir(basePath);
}
Serial.println("Setup completed.");
}
void loop() {
server.handleClient();
if (server.isAccessPointMode()) server.updateDNS();
static uint32_t updateTime;
if (millis() - updateTime > 30000) {
updateTime = millis();
appenRow();
}
}
================================================
FILE: examples/csvLogger/data/assets/css/index.css
================================================
/* Misc */
html, body {
font-family: "Helvetica","Arial",sans-serif;
font-size: 14px;
font-weight: 400;
line-height: 20px;
}
body {
margin: 0;
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
text-align: left;
background-color: #fff;
}
h1 {
margin: 5px;
text-align: center;
}
p {
font-size: 0.9rem;
margin: 0.5rem 0 1.5rem 0;
}
a,
a:visited {
color: #08C;
text-decoration: none;
}
a:hover,
a:focus {
color: #69c773;
cursor: pointer;
}
a.delete-file,
a.delete-file:visited {
color: #CC0000;
margin-left: 0.5rem;
vertical-align: middle;
}
button {
display: inline-block;
border-radius: 3px;
border: none;
font-size: 0.9rem;
padding: 0.5rem 1em;
background: #86b32d;
border-bottom: 1px solid #5d7d1f;
color: white;
margin: 5px 0;
text-align: center;
}
button:hover {
opacity: 0.75;
cursor: pointer;
}
#page-wrapper {
width: 95%;
background: #FFF;
padding: 1.25rem;
margin: 1rem auto;
min-height: 800px;
border-top: 5px solid #69c773;
box-shadow: 0 2px 10px rgba(0,0,0,0.8);
}
#content {
width: 85%;
overflow: auto;
height: 86vh;
}
#files {
width: 15%;
}
#files ul {
margin: 20px 0;
padding: 0.5rem 1rem;
overflow-y: auto;
list-style: square;
background: #F7F7F7;
border: 1px solid #D9D9D9;
border-radius: 5px;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1);
max-height: 75vh;
}
#files li {
margin-left: 8px;
font-size: 14px;
display: flex;
justify-content: space-between;
align-items: center;
}
.container {
display: flex;
flex-direction: row;
align-content: flex-start;
justify-content: space-between;
align-items: flex-start;
column-gap: 10px;
height: 86vh;
}
.delete {
font-size: 24px;
transition: 0.3s;
margin-top:5px;
}
.delete-all{
color: #f44336;
font-size: 12px;
background-color: transparent;
background-repeat: no-repeat;
border: none;
cursor: pointer;
overflow: hidden;
}
/* Tables */
.table-holder {
margin-top: 20px;
border: 1px solid lightgray;
border-radius: 5px;
border-bottom: 0px;
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
}
.tables {
margin-bottom: 50px;
}
table {
width: 100%;
border-bottom: 0px;
}
table, th, td {
border: 1px solid lightgrey;
border-collapse: collapse;
padding-left: 5px;
}
table tr:nth-child(even) {
background-color: white;
}
table tr:nth-child(odd) {
background-color: #f2f2f2;
}
table th {
background-color: #e1e1e1;
color: black;
}
/* Sections */
.section {
box-shadow: 10px 10px 10px 10px;
background-color: #e5e5e5;
padding: 10px;
padding-top: 20px;
font-size: 18px;
}
.section-lightgrey {
background-color: #f9f9f9;
}
/* 100% Image Width on Smaller Screens */
@media only screen and (max-width: 700px){
.modal-content {
width: 100%;
}
}
================================================
FILE: examples/csvLogger/data/assets/css/style.css
================================================
/* Misc */
html, body {
font-family: "Helvetica","Arial",sans-serif;
font-size: 14px;
font-weight: 400;
line-height: 20px;
}
body {
margin: 0;
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
text-align: left;
background-color: #fff;
}
h1 {
margin: 5px;
text-align: center;
}
p {
font-size: 0.9rem;
margin: 0.5rem 0 1.5rem 0;
}
a,
a:visited {
color: #08C;
text-decoration: none;
}
a:hover,
a:focus {
color: #69c773;
cursor: pointer;
}
a.delete-file,
a.delete-file:visited {
color: #CC0000;
margin-left: 0.5rem;
vertical-align: middle;
}
button {
display: inline-block;
border-radius: 3px;
border: none;
font-size: 0.9rem;
padding: 0.5rem 1em;
background: #86b32d;
border-bottom: 1px solid #5d7d1f;
color: white;
margin: 5px 0;
text-align: center;
}
button:hover {
opacity: 0.75;
cursor: pointer;
}
#page-wrapper {
width: 95%;
background: #FFF;
padding: 1.25rem;
margin: 1rem auto;
min-height: 800px;
border-top: 5px solid #69c773;
box-shadow: 0 2px 10px rgba(0,0,0,0.8);
}
#content {
width: 85%;
overflow: auto;
height: 86vh;
}
#files {
width: 15%;
line-height: 1;
}
#files ul {
margin: 20px 0;
padding: 0.5rem 1rem;
overflow-y: auto;
list-style: square;
background: #F7F7F7;
border: 1px solid #D9D9D9;
border-radius: 5px;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1);
max-height: 75vh;
}
#files li {
font-size: 14px;
display: flex;
justify-content: space-between;
align-items: center;
}
.container {
display: flex;
flex-direction: row;
align-content: flex-start;
justify-content: space-between;
align-items: flex-start;
column-gap: 10px;
height: 86vh;
}
.delete {
font-size: 24px;
transition: 0.3s;
margin-top:5px;
}
.delete-all{
color: #f44336;
font-size: 12px;
background-color: transparent;
background-repeat: no-repeat;
border: none;
cursor: pointer;
overflow: hidden;
}
/* Tables */
.table-holder {
margin-top: 20px;
border: 1px solid lightgray;
border-radius: 5px;
border-bottom: 0px;
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
}
.tables {
margin-bottom: 50px;
}
table {
width: 100%;
border-bottom: 0px;
}
table, th, td {
border: 1px solid lightgrey;
border-collapse: collapse;
padding-left: 5px;
}
table tr:nth-child(even) {
background-color: white;
}
table tr:nth-child(odd) {
background-color: #f2f2f2;
}
table th {
background-color: #e1e1e1;
color: black;
}
/* Sections */
.section {
box-shadow: 10px 10px 10px 10px;
background-color: #e5e5e5;
padding: 10px;
padding-top: 20px;
font-size: 18px;
}
.section-lightgrey {
background-color: #f9f9f9;
}
/* 100% Image Width on Smaller Screens */
@media only screen and (max-width: 700px){
.modal-content {
width: 100%;
}
}
================================================
FILE: examples/csvLogger/data/assets/js/csv.js
================================================
// Default file to be loaded with no parameter in url
var filename = '';
// JQuery-like selector
var $ = function(el) {
return document.getElementById(el);
};
/**
* @returns {getUserInput.userInput} an object
* containing all the user input at the time
* of the method call.
*/
function getUserInput() {
var userInput = {};
userInput.fileName = filename;
userInput.maxRows = "0";
userInput.encoding = 'UTF-8';
userInput.columnSeparator = ',';
userInput.useQuotes = true;
userInput.firstRowHeaders = true;
userInput.firstRowInlcude = false;
return userInput;
}
/* Tables */
var tableCount = 1;
/**
* Creates a table holder with the
* given table in it.
*
* @param {type} title
* @param {type} tableHtml
* @returns {String|getTableUnit.tableHolder}
*/
function getTableUnit(title, tableHtml){
var id = "table-" + tableCount;
var tableHolder = "<div class='table-holder' id='" + id + "'><b contenteditable>{@name}</b>{@table}</div>";
tableHolder = tableHolder.replace("{@name}", title);
tableHolder = tableHolder.replace("{@table}", tableHtml);
tableCount++;
return tableHolder;
}
/**
* Clears all tables from the page.
*/
function clearTables(){
tableCount = 1;
$('csv-table').innerHTML = '';
}
/**
* Adds (appends) a table holder to the page.
*
* @param {type} unit
* @returns {undefined}
*/
function addTableUnit(unit){
$('csv-table').innerHTML = unit ;
}
function saveTable(filename, text) {
var myblob = new Blob([text]);
var formData = new FormData();
formData.append("data", myblob, filename);
// POST data using the Fetch API
fetch('/edit', {
method: 'POST',
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Max-Age': '600',
'Access-Control-Allow-Methods': 'PUT,POST,GET,OPTIONS',
'Access-Control-Allow-Headers': '*',
'filename': filename
},
body: formData
})
// Handle the server response
.then(response => response.text())
.then(text => {
console.log(text);
});
}
/* Table downloading */
/**
* Download the specified table to
* the users computer.
*
* @param {type} table table number
* @param {save} save file to host memory
* @returns {undefined}
*/
function downloadTable(table, save = false) {
var tableId = "table-" + table;
var csvArray = [];
var rows = document.querySelectorAll("#" + tableId + " > table tr");
for (var i = 0; i < rows.length; i++) {
var row = [], cols = rows[i].querySelectorAll("td, th");
for (var j = 0; j < cols.length; j++) {
var value = cols[j].innerText;
if(customParseFloat(value) ) {
row.push(value);
}
else {
row.push('"' + value +'"');
}
}
csvArray.push(row.join(","));
csvArray.push("\n");
}
var csvString = csvArray.join("");
if (save === false)
download(filename, csvString);
else {
saveTable(filename, csvString);
}
}
/**
* Creates and downloads a file to the users computer.
*
* @param {type} filename
* @param {type} text
* @returns {undefined}
*/
function download(filename, text) {
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
/**
* Gets the users input and creates
* a set of tables from it.
*/
function populate(csv){
var ui = getUserInput();
addTableUnit(
getTableUnit(
ui.fileName,
getTable(
csvTo2DArray(
csv,
ui.columnSeparator,
ui.useQuotes,
ui.maxRows
),
ui.firstRowHeaders,
ui.firstRowInlcude
)
)
);
// Prevents to ad a new line inside cell
var cells = document.querySelectorAll('td');
cells.forEach(item => {
item.addEventListener('keypress', event => {
if ( event.keyCode === 13 ){
if (window.event) {
window.event.returnValue = false;
}
}
});
});
}
/**
* Load the csv from webserver location and fit table with data
*/
function loadCsv(path) {
clearTables();
filename = path;
fetch(path)
.then(response => response.text())
.then(textString => {
populate(textString);
});
}
/**
* Standard parseFloat don't handle properly "0", "0.0", "0.00" etc strings
* @param {strNumber} the string representing a number to be parsed
* @returns {float number} or NaN
*/
function customParseFloat(strNumber){
if (isNaN(parseFloat(strNumber)) === false){
let toFixedLength = 0;
let arr = strNumber.split('.');
if (arr.length === 2 ){
toFixedLength = arr[1].length;
}
return parseFloat(strNumber).toFixed(toFixedLength);
}
return NaN; // Not a number
}
/**
* Creates an unstyled, bare-bones html table
* from the provided 2D(Multidimentional Array).
*
* @param {type} tableArray the 2D array.
* @param {type} useHeaders will make the first row
* in the table bold if true.
* @param {type} dupeHeaders will duplicate the first row
* in the table if true and useHeaders is true.
* @param {type} tableId HTML ID for the table.
* @returns {String} the constructed html table as text.
*/
function getTable(tableArray, useHeaders, dupeHeaders, tableId){
var tableOpen = "<table contenteditable id=\"" + tableId + "\">";
var tableClose = "</table>";
var headerCell = "<th>{@val}</th>";
var cell = "<td>{@val}</td>";
var rowOpen = "<tr>";
var rowClose = "</tr>";
var table = tableOpen;
for(i = 0; i < tableArray.length; i++){
//Row
if(i === 1 && useHeaders && dupeHeaders){
i = 0;
useHeaders = false;
dupeHeaders = false;
}
table += rowOpen;
for(j = 0; j < tableArray[i].length; j++){
//Cell
if(i === 0 && useHeaders){
table += headerCell.replace("{@val}", tableArray[i][j]);
} else {
table += cell.replace("{@val}", tableArray[i][j]);
}
}
table += rowClose;
}
return table + tableClose;
}
/**
* Creates a 2D (Multidimentional) array from
* CSV data in string form.
*
* @param {type} csv the CSV data.
* @param {type} separator the character used
* to separate the columns/cells.
* @param {type} quotes ignores the separator
* in quoted text.
* @param {type} maxRows the maximum rows
* to scan.
* @returns {Array|csvTo2DArray.table} the CSV data
* as a 2D (Multidimentional) array.
*/
function csvTo2DArray(csv, separator, quotes, maxRows){
var table = [];
var rows = 0;
csv.split("\n").map(function(row){
if(maxRows !== "0")
if(rows >= maxRows)
return;
var tableRow = getRow(row, separator, quotes);
if(tableRow === null)
return table;
table.push(tableRow);
rows++;
});
return table;
}
/**
* Creates an array from a CSV row (line)
*
* @param {type} row the CSV row.
* @param {type} separator character used to separate
* cells/columns
* @param {type} quotes ignores the separator
* in quoted text.
* @returns {Array|getRow.trow} the CSV row as an array.
*/
function getRow(row, separator, quotes){
if(row.length === 0)
return null;
isQuoted = false;
var trow = [];
var cell = "";
for(var i = 0; i < row.length; i++){
var char = row.charAt(i);
if(quotes){
if(char === '\"' || char === '\''){
isQuoted = !isQuoted;
continue;
}
}
if(char === separator && !isQuoted){
trow.push(cell);
cell = "";
continue;
}
cell += char;
}
trow.push(cell);
return trow;
}
================================================
FILE: examples/csvLogger/data/assets/js/index.js
================================================
var dataFolder = document.getElementById("csv-path").value;
var fileList = document.getElementById('file-list');
var currentFile = "";
// Fetch the list of files and fill the filelist
function listFiles() {
var url = '/list?dir=' + dataFolder;
if (url.charAt(url.length - 1) === '/')
url = url.slice(0, -1); // Remove the last character
fetch(url) // Do the request
.then(response => response.json()) // Parse the response
.then(obj => { // DO something with response
fileList.innerHTML = '';
obj.forEach(function(entry, i) {
addEntry(entry.name);
});
// Load last file
loadCsv(dataFolder + obj[obj.length -1].name);
});
}
// Load selected image inside the preview content
function loadFile(filename) {
loadCsv(filename);
}
// Delete selected file in SD
async function deleteFile(filename) {
var isExecuted = confirm("Are you sure to delete "+ filename + "?");
if(isExecuted){
const data = new URLSearchParams();
data.append('path', filename);
fetch('/edit', {
method: 'DELETE',
body: data
});
// Update the file browser.
listFiles();
}
}
async function deleteAll() {
var isExecuted = confirm("Are you sure to delete all files in "+ dataFolder + " folder?");
if(isExecuted){
var ul = document.getElementById("file-list");
var items = ul.getElementsByClassName("edit-file");
for (var i=0; i<items.length; i++) {
console.log("Delete " + items[i].innerHTML);
await deleteFile(imgFolder + items[i].innerHTML);
}
}
}
// Add a single entry to the filelist
function addEntry(entryName) {
var li = document.createElement('li');
var link = document.createElement('a');
link.innerHTML = entryName;
link.className = 'edit-file';
li.appendChild(link);
var delLink = document.createElement('a');
delLink.innerHTML = '<span class="delete">×</span>';
delLink.className = 'delete-file';
li.appendChild(delLink);
fileList.insertBefore(li, fileList.firstChild);
// Setup an event listener that will load the file when the link is clicked.
link.addEventListener('click', function(e) {
e.preventDefault();
loadFile(dataFolder + entryName);
currentFile = dataFolder + entryName;
});
// Setup an event listener that will delete the file when the delete link is clicked.
delLink.addEventListener('click', function(e) {
e.preventDefault();
deleteFile(dataFolder + entryName);
});
}
// Add the event listeners
document.getElementById('download-csv').addEventListener('click', function(e) {
downloadTable(1);
});
document.getElementById('save-csv').addEventListener('click', function(e) {
downloadTable(1, true);
});
document.getElementById('load-list').addEventListener('click', function(e) {
listFiles();
});
// Start the web page
listFiles();
================================================
FILE: examples/csvLogger/data/csv/2024_01_10.csv
================================================
timestamp, free heap, largest free block, connected, wifi strength
Wed Jan 10 13:19:43 2024, 270472, 262132, true, -43
Wed Jan 10 13:19:53 2024, 270472, 262132, true, -43
Wed Jan 10 13:20:03 2024, 270472, 262132, true, -40
Wed Jan 10 13:20:13 2024, 268924, 258036, true, -46
Wed Jan 10 13:20:23 2024, 266644, 253940, true, -41
Wed Jan 10 13:20:33 2024, 266644, 253940, true, -42
Wed Jan 10 13:20:43 2024, 266644, 253940, true, -42
Wed Jan 10 13:20:53 2024, 266644, 253940, true, -43
Wed Jan 10 13:21:03 2024, 270504, 262132, true, -45
Wed Jan 10 13:21:13 2024, 270500, 262132, true, -44
Wed Jan 10 13:21:23 2024, 270500, 262132, true, -42
Wed Jan 10 13:21:33 2024, 270500, 262132, true, -44
Wed Jan 10 13:21:43 2024, 270500, 262132, true, -45
Wed Jan 10 13:21:53 2024, 270500, 262132, true, -43
Wed Jan 10 13:22:33 2024, 263376, 253940, true, -42
================================================
FILE: examples/csvLogger/data/index.htm
================================================
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>ESP32 CSV List</title>
<!-- Cyustom page styling -->
<link rel="stylesheet" href="assets/css/style.css" />
</head>
<body>
<main>
<div id="page-wrapper" class="clearfix">
<h1>CSV list web interface</h1>
<div class=container>
<div id="files">
<ul id="file-list"></ul>
<div class="field">
<input type="text" id="csv-path" name="path" value='/csv/'>
<button id="load-list">Load list</button><br><br>
<button id="download-csv">Download</button>
<button id="save-csv">Save CSV</button>
</div>
<hr>
<input type="button" onclick="deleteAll()" value="Delete all files" class="delete-all">
<hr>
</div>
<div id="content">
<div class="tables text-center" id="csv-table"></div>
</div>
</div>
</div>
</main>
<script src="assets/js/csv.js"></script>
<script src="assets/js/index.js"></script>
</body>
</html>
================================================
FILE: examples/csvLogger/partitions.csv
================================================
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x140000,
app1, app, ota_1, 0x150000,0x140000,
spiffs, data, spiffs, 0x290000,0x160000,
coredump, data, coredump,0x3F0000,0x10000,
================================================
FILE: examples/csvLogger/platformio.ini
================================================
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[platformio]
src_dir = .
[env:esp32-s3-devkitc1-n4r2]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip
board = esp32-s3-devkitc1-n4r2
framework = arduino
upload_speed = 921600
monitor_speed = 115200
board_build.partitions = partitions.csv
lib_extra_dirs = ../../
lib_ignore = pio_examples
[env:esp32dev]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip
board = esp32dev
framework = arduino
upload_speed = 921600
monitor_speed = 115200
board_build.partitions = partitions.csv
lib_extra_dirs = ../../
lib_ignore = pio_examples
[env:esp8266-nodemcuv2]
platform = espressif8266
board = nodemcuv2
framework = arduino
upload_speed = 921600
monitor_speed = 115200
board_build.partitions = partitions.csv
lib_extra_dirs = ../../
lib_ignore = pio_examples
================================================
FILE: examples/csvLogger/readme.md
================================================
An example for logging to a CSV file and viewing the content with the browser.
It is also possible to modify or download the file.

================================================
FILE: examples/csvLoggerSD/csvLoggerSD.ino
================================================
#include <SD.h>
#include <FSWebServer.h>
// Timezone definition to get properly time from NTP server
#define MYTZ "CET-1CEST,M3.5.0,M10.5.0/3"
#include <time.h>
#define PIN_CS 14
#define PIN_SCK 13
#define PIN_MOSI 12
#define PIN_MISO 11
FSWebServer server(SD, 80, "myServer");
bool captiveRun = false;
struct tm ntpTime;
const char* basePath = "/csv";
//////////////////////////////// NTP Time /////////////////////////////////////////
void getUpdatedtime(const uint32_t timeout) {
uint32_t start = millis();
do {
time_t now = time(nullptr);
ntpTime = *localtime(&now);
delay(1);
} while (millis() - start < timeout && ntpTime.tm_year <= (1970 - 1900));
}
//////////////////////////////// Filesystem /////////////////////////////////////////
bool startFilesystem(){
if (SD.begin(PIN_CS)){
server.printFileList(SD, "/", 2);
return true;
}
else {
Serial.println("ERROR on mounting filesystem. It will be formmatted!");
ESP.restart();
}
return false;
}
//////////////////////////// Append a row to csv file ///////////////////////////////////
bool appenRow() {
getUpdatedtime(10);
char filename[24];
snprintf(filename, sizeof(filename),
"%s/%04d_%02d_%02d.csv",
basePath,
ntpTime.tm_year + 1900,
ntpTime.tm_mon + 1,
ntpTime.tm_mday
);
File file;
if (SD.exists(filename)) {
file = SD.open(filename, "a"); // Append to existing file
}
else {
file = SD.open(filename, "w"); // Create a new file
file.println("timestamp, free heap, largest free block, connected, wifi strength");
}
if (file) {
char timestamp[25];
strftime(timestamp, sizeof(timestamp), "%c", &ntpTime);
char row[64];
#ifdef ESP32
snprintf(row, sizeof(row), "%s, %d, %d, %s, %d",
timestamp,
heap_caps_get_free_size(0),
heap_caps_get_largest_free_block(0),
(WiFi.status() == WL_CONNECTED) ? "true" : "false",
WiFi.RSSI()
);
#elif defined(ESP8266)
uint32_t free;
uint16_t max;
ESP.getHeapStats(&free, &max, nullptr);
snprintf(row, sizeof(row),
"%s, %d, %d, %s, %d",
timestamp, free, max,
(WiFi.status() == WL_CONNECTED) ? "true" : "false",
WiFi.RSSI()
);
#endif
Serial.println(row);
file.println(row);
file.close();
return true;
}
return false;
}
void setup() {
SPI.begin(PIN_SCK, PIN_MISO, PIN_MOSI, PIN_CS);
Serial.begin(115200);
delay(1000);
startFilesystem();
// Create csv logs folder if not exists
if (!SD.exists(basePath)) {
SD.mkdir(basePath);
}
// Try to connect to WiFi (will start AP if not connected after timeout)
if (!server.startWiFi(10000)) {
Serial.println("\nWiFi not connected! Starting AP mode...");
server.startCaptivePortal("ESP_AP", "123456789", "/setup");
captiveRun = true;
}
// Enable ACE FS file web editor and add FS info callback fucntion
server.enableFsCodeEditor();
#ifdef ESP32
server.setFsInfoCallback([](fsInfo_t* fsInfo) {
fsInfo->totalBytes = SD.totalBytes();
fsInfo->usedBytes = SD.usedBytes();
fsInfo->fsName = "SD";
});
#endif
// Start server
server.begin();
Serial.print(F("\nAsync ESP Web Server started on IP Address: "));
Serial.println(server.getServerIP());
Serial.println(F(
"This is \"scvLoggerSdFat.ino\" example.\n"
"Open /setup page to configure optional parameters.\n"
"Open /edit page to view, edit or upload example or your custom webserver source files."
));
// Set NTP servers
#ifdef ESP8266
configTime(MYTZ, "time.google.com", "time.windows.com", "pool.ntp.org");
#elif defined(ESP32)
configTzTime(MYTZ, "time.google.com", "time.windows.com", "pool.ntp.org");
#endif
// Wait for NTP sync (with timeout)
getUpdatedtime(5000);
appenRow();
}
void loop() {
server.handleClient();
if (captiveRun)
server.updateDNS();
static uint32_t updateTime;
if (millis()- updateTime > 30000) {
updateTime = millis();
appenRow();
}
}
================================================
FILE: examples/csvLoggerSD/data/assets/css/index.css
================================================
/* Misc */
html, body {
font-family: "Helvetica","Arial",sans-serif;
font-size: 14px;
font-weight: 400;
line-height: 20px;
}
body {
margin: 0;
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
text-align: left;
background-color: #fff;
}
h1 {
margin: 5px;
text-align: center;
}
p {
font-size: 0.9rem;
margin: 0.5rem 0 1.5rem 0;
}
a,
a:visited {
color: #08C;
text-decoration: none;
}
a:hover,
a:focus {
color: #69c773;
cursor: pointer;
}
a.delete-file,
a.delete-file:visited {
color: #CC0000;
margin-left: 0.5rem;
vertical-align: middle;
}
button {
display: inline-block;
border-radius: 3px;
border: none;
font-size: 0.9rem;
padding: 0.5rem 1em;
background: #86b32d;
border-bottom: 1px solid #5d7d1f;
color: white;
margin: 5px 0;
text-align: center;
}
button:hover {
opacity: 0.75;
cursor: pointer;
}
#page-wrapper {
width: 95%;
background: #FFF;
padding: 1.25rem;
margin: 1rem auto;
min-height: 800px;
border-top: 5px solid #69c773;
box-shadow: 0 2px 10px rgba(0,0,0,0.8);
}
#content {
width: 85%;
overflow: auto;
height: 86vh;
}
#files {
width: 15%;
}
#files ul {
margin: 20px 0;
padding: 0.5rem 1rem;
overflow-y: auto;
list-style: square;
background: #F7F7F7;
border: 1px solid #D9D9D9;
border-radius: 5px;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1);
max-height: 75vh;
}
#files li {
margin-left: 8px;
font-size: 14px;
display: flex;
justify-content: space-between;
align-items: center;
}
.container {
display: flex;
flex-direction: row;
align-content: flex-start;
justify-content: space-between;
align-items: flex-start;
column-gap: 10px;
height: 86vh;
}
.delete {
font-size: 24px;
transition: 0.3s;
margin-top:5px;
}
.delete-all{
color: #f44336;
font-size: 12px;
background-color: transparent;
background-repeat: no-repeat;
border: none;
cursor: pointer;
overflow: hidden;
}
/* Tables */
.table-holder {
margin-top: 20px;
border: 1px solid lightgray;
border-radius: 5px;
border-bottom: 0px;
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
}
.tables {
margin-bottom: 50px;
}
table {
width: 100%;
border-bottom: 0px;
}
table, th, td {
border: 1px solid lightgrey;
border-collapse: collapse;
padding-left: 5px;
}
table tr:nth-child(even) {
background-color: white;
}
table tr:nth-child(odd) {
background-color: #f2f2f2;
}
table th {
background-color: #e1e1e1;
color: black;
}
/* Sections */
.section {
box-shadow: 10px 10px 10px 10px;
background-color: #e5e5e5;
padding: 10px;
padding-top: 20px;
font-size: 18px;
}
.section-lightgrey {
background-color: #f9f9f9;
}
/* 100% Image Width on Smaller Screens */
@media only screen and (max-width: 700px){
.modal-content {
width: 100%;
}
}
================================================
FILE: examples/csvLoggerSD/data/assets/css/style.css
================================================
/* Misc */
html, body {
font-family: "Helvetica","Arial",sans-serif;
font-size: 14px;
font-weight: 400;
line-height: 20px;
}
body {
margin: 0;
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
text-align: left;
background-color: #fff;
}
h1 {
margin: 5px;
text-align: center;
}
p {
font-size: 0.9rem;
margin: 0.5rem 0 1.5rem 0;
}
a,
a:visited {
color: #08C;
text-decoration: none;
}
a:hover,
a:focus {
color: #69c773;
cursor: pointer;
}
a.delete-file,
a.delete-file:visited {
color: #CC0000;
margin-left: 0.5rem;
vertical-align: middle;
}
button {
display: inline-block;
border-radius: 3px;
border: none;
font-size: 0.9rem;
padding: 0.5rem 1em;
background: #86b32d;
border-bottom: 1px solid #5d7d1f;
color: white;
margin: 5px 0;
text-align: center;
}
button:hover {
opacity: 0.75;
cursor: pointer;
}
#page-wrapper {
width: 95%;
background: #FFF;
padding: 1.25rem;
margin: 1rem auto;
min-height: 800px;
border-top: 5px solid #69c773;
box-shadow: 0 2px 10px rgba(0,0,0,0.8);
}
#content {
width: 85%;
overflow: auto;
height: 86vh;
}
#files {
width: 15%;
line-height: 1;
}
#files ul {
margin: 20px 0;
padding: 0.5rem 1rem;
overflow-y: auto;
list-style: square;
background: #F7F7F7;
border: 1px solid #D9D9D9;
border-radius: 5px;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1);
max-height: 75vh;
}
#files li {
font-size: 14px;
display: flex;
justify-content: space-between;
align-items: center;
}
.container {
display: flex;
flex-direction: row;
align-content: flex-start;
justify-content: space-between;
align-items: flex-start;
column-gap: 10px;
height: 86vh;
}
.delete {
font-size: 24px;
transition: 0.3s;
margin-top:5px;
}
.delete-all{
color: #f44336;
font-size: 12px;
background-color: transparent;
background-repeat: no-repeat;
border: none;
cursor: pointer;
overflow: hidden;
}
/* Tables */
.table-holder {
margin-top: 20px;
border: 1px solid lightgray;
border-radius: 5px;
border-bottom: 0px;
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
}
.tables {
margin-bottom: 50px;
}
table {
width: 100%;
border-bottom: 0px;
}
table, th, td {
border: 1px solid lightgrey;
border-collapse: collapse;
padding-left: 5px;
}
table tr:nth-child(even) {
background-color: white;
}
table tr:nth-child(odd) {
background-color: #f2f2f2;
}
table th {
background-color: #e1e1e1;
color: black;
}
/* Sections */
.section {
box-shadow: 10px 10px 10px 10px;
background-color: #e5e5e5;
padding: 10px;
padding-top: 20px;
font-size: 18px;
}
.section-lightgrey {
background-color: #f9f9f9;
}
/* 100% Image Width on Smaller Screens */
@media only screen and (max-width: 700px){
.modal-content {
width: 100%;
}
}
================================================
FILE: examples/csvLoggerSD/data/assets/js/csv.js
================================================
// Default file to be loaded with no parameter in url
var filename = '';
// JQuery-like selector
var $ = function(el) {
return document.getElementById(el);
};
/**
* @returns {getUserInput.userInput} an object
* containing all the user input at the time
* of the method call.
*/
function getUserInput() {
var userInput = {};
userInput.fileName = filename;
userInput.maxRows = "0";
userInput.encoding = 'UTF-8';
userInput.columnSeparator = ',';
userInput.useQuotes = true;
userInput.firstRowHeaders = true;
userInput.firstRowInlcude = false;
return userInput;
}
/* Tables */
var tableCount = 1;
/**
* Creates a table holder with the
* given table in it.
*
* @param {type} title
* @param {type} tableHtml
* @returns {String|getTableUnit.tableHolder}
*/
function getTableUnit(title, tableHtml){
var id = "table-" + tableCount;
var tableHolder = "<div class='table-holder' id='" + id + "'><b contenteditable>{@name}</b>{@table}</div>";
tableHolder = tableHolder.replace("{@name}", title);
tableHolder = tableHolder.replace("{@table}", tableHtml);
tableCount++;
return tableHolder;
}
/**
* Clears all tables from the page.
*/
function clearTables(){
tableCount = 1;
$('csv-table').innerHTML = '';
}
/**
* Adds (appends) a table holder to the page.
*
* @param {type} unit
* @returns {undefined}
*/
function addTableUnit(unit){
$('csv-table').innerHTML = unit ;
}
function saveTable(filename, text) {
var myblob = new Blob([text]);
var formData = new FormData();
formData.append("data", myblob, filename);
// POST data using the Fetch API
fetch('/edit', {
method: 'POST',
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Max-Age': '600',
'Access-Control-Allow-Methods': 'PUT,POST,GET,OPTIONS',
'Access-Control-Allow-Headers': '*',
'filename': filename
},
body: formData
})
// Handle the server response
.then(response => response.text())
.then(text => {
console.log(text);
});
}
/* Table downloading */
/**
* Download the specified table to
* the users computer.
*
* @param {type} table table number
* @param {save} save file to host memory
* @returns {undefined}
*/
function downloadTable(table, save = false) {
var tableId = "table-" + table;
var csvArray = [];
var rows = document.querySelectorAll("#" + tableId + " > table tr");
for (var i = 0; i < rows.length; i++) {
var row = [], cols = rows[i].querySelectorAll("td, th");
for (var j = 0; j < cols.length; j++) {
var value = cols[j].innerText;
if(customParseFloat(value) ) {
row.push(value);
}
else {
row.push('"' + value +'"');
}
}
csvArray.push(row.join(","));
csvArray.push("\n");
}
var csvString = csvArray.join("");
if (save === false)
download(filename, csvString);
else {
saveTable(filename, csvString);
}
}
/**
* Creates and downloads a file to the users computer.
*
* @param {type} filename
* @param {type} text
* @returns {undefined}
*/
function download(filename, text) {
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
/**
* Gets the users input and creates
* a set of tables from it.
*/
function populate(csv){
var ui = getUserInput();
addTableUnit(
getTableUnit(
ui.fileName,
getTable(
csvTo2DArray(
csv,
ui.columnSeparator,
ui.useQuotes,
ui.maxRows
),
ui.firstRowHeaders,
ui.firstRowInlcude
)
)
);
// Prevents to ad a new line inside cell
var cells = document.querySelectorAll('td');
cells.forEach(item => {
item.addEventListener('keypress', event => {
if ( event.keyCode === 13 ){
if (window.event) {
window.event.returnValue = false;
}
}
});
});
}
/**
* Load the csv from webserver location and fit table with data
*/
function loadCsv(path) {
clearTables();
filename = path;
fetch(path)
.then(response => response.text())
.then(textString => {
populate(textString);
});
}
/**
* Standard parseFloat don't handle properly "0", "0.0", "0.00" etc strings
* @param {strNumber} the string representing a number to be parsed
* @returns {float number} or NaN
*/
function customParseFloat(strNumber){
if (isNaN(parseFloat(strNumber)) === false){
let toFixedLength = 0;
let arr = strNumber.split('.');
if (arr.length === 2 ){
toFixedLength = arr[1].length;
}
return parseFloat(strNumber).toFixed(toFixedLength);
}
return NaN; // Not a number
}
/**
* Creates an unstyled, bare-bones html table
* from the provided 2D(Multidimentional Array).
*
* @param {type} tableArray the 2D array.
* @param {type} useHeaders will make the first row
* in the table bold if true.
* @param {type} dupeHeaders will duplicate the first row
* in the table if true and useHeaders is true.
* @param {type} tableId HTML ID for the table.
* @returns {String} the constructed html table as text.
*/
function getTable(tableArray, useHeaders, dupeHeaders, tableId){
var tableOpen = "<table contenteditable id=\"" + tableId + "\">";
var tableClose = "</table>";
var headerCell = "<th>{@val}</th>";
var cell = "<td>{@val}</td>";
var rowOpen = "<tr>";
var rowClose = "</tr>";
var table = tableOpen;
for(i = 0; i < tableArray.length; i++){
//Row
if(i === 1 && useHeaders && dupeHeaders){
i = 0;
useHeaders = false;
dupeHeaders = false;
}
table += rowOpen;
for(j = 0; j < tableArray[i].length; j++){
//Cell
if(i === 0 && useHeaders){
table += headerCell.replace("{@val}", tableArray[i][j]);
} else {
table += cell.replace("{@val}", tableArray[i][j]);
}
}
table += rowClose;
}
return table + tableClose;
}
/**
* Creates a 2D (Multidimentional) array from
* CSV data in string form.
*
* @param {type} csv the CSV data.
* @param {type} separator the character used
* to separate the columns/cells.
* @param {type} quotes ignores the separator
* in quoted text.
* @param {type} maxRows the maximum rows
* to scan.
* @returns {Array|csvTo2DArray.table} the CSV data
* as a 2D (Multidimentional) array.
*/
function csvTo2DArray(csv, separator, quotes, maxRows){
var table = [];
var rows = 0;
csv.split("\n").map(function(row){
if(maxRows !== "0")
if(rows >= maxRows)
return;
var tableRow = getRow(row, separator, quotes);
if(tableRow === null)
return table;
table.push(tableRow);
rows++;
});
return table;
}
/**
* Creates an array from a CSV row (line)
*
* @param {type} row the CSV row.
* @param {type} separator character used to separate
* cells/columns
* @param {type} quotes ignores the separator
* in quoted text.
* @returns {Array|getRow.trow} the CSV row as an array.
*/
function getRow(row, separator, quotes){
if(row.length === 0)
return null;
isQuoted = false;
var trow = [];
var cell = "";
for(var i = 0; i < row.length; i++){
var char = row.charAt(i);
if(quotes){
if(char === '\"' || char === '\''){
isQuoted = !isQuoted;
continue;
}
}
if(char === separator && !isQuoted){
trow.push(cell);
cell = "";
continue;
}
cell += char;
}
trow.push(cell);
return trow;
}
================================================
FILE: examples/csvLoggerSD/data/assets/js/index.js
================================================
var dataFolder = document.getElementById("csv-path").value;
var fileList = document.getElementById('file-list');
var currentFile = "";
// Fetch the list of files and fill the filelist
function listFiles() {
var url = '/list?dir=' + dataFolder;
if (url.charAt(url.length - 1) === '/')
url = url.slice(0, -1); // Remove the last character
fetch(url) // Do the request
.then(response => response.json()) // Parse the response
.then(obj => { // DO something with response
fileList.innerHTML = '';
obj.forEach(function(entry, i) {
addEntry(entry.name);
});
// Load last file
loadCsv(dataFolder + obj[obj.length -1].name);
});
}
// Load selected image inside the preview content
function loadFile(filename) {
loadCsv(filename);
}
// Delete selected file in SD
async function deleteFile(filename) {
var isExecuted = confirm("Are you sure to delete "+ filename + "?");
if(isExecuted){
const data = new URLSearchParams();
data.append('path', filename);
fetch('/edit', {
method: 'DELETE',
body: data
});
// Update the file browser.
listFiles();
}
}
async function deleteAll() {
var isExecuted = confirm("Are you sure to delete all files in "+ dataFolder + " folder?");
if(isExecuted){
var ul = document.getElementById("file-list");
var items = ul.getElementsByClassName("edit-file");
for (var i=0; i<items.length; i++) {
console.log("Delete " + items[i].innerHTML);
await deleteFile(imgFolder + items[i].innerHTML);
}
}
}
// Add a single entry to the filelist
function addEntry(entryName) {
var li = document.createElement('li');
var link = document.createElement('a');
link.innerHTML = entryName;
link.className = 'edit-file';
li.appendChild(link);
var delLink = document.createElement('a');
delLink.innerHTML = '<span class="delete">×</span>';
delLink.className = 'delete-file';
li.appendChild(delLink);
fileList.insertBefore(li, fileList.firstChild);
// Setup an event listener that will load the file when the link is clicked.
link.addEventListener('click', function(e) {
e.preventDefault();
loadFile(dataFolder + entryName);
currentFile = dataFolder + entryName;
});
// Setup an event listener that will delete the file when the delete link is clicked.
delLink.addEventListener('click', function(e) {
e.preventDefault();
deleteFile(dataFolder + entryName);
});
}
// Add the event listeners
document.getElementById('download-csv').addEventListener('click', function(e) {
downloadTable(1);
});
document.getElementById('save-csv').addEventListener('click', function(e) {
downloadTable(1, true);
});
document.getElementById('load-list').addEventListener('click', function(e) {
listFiles();
});
// Start the web page
listFiles();
================================================
FILE: examples/csvLoggerSD/data/index.htm
================================================
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>ESP32 CSV List</title>
<!-- Cyustom page styling -->
<link rel="stylesheet" href="assets/css/style.css" />
</head>
<body>
<main>
<div id="page-wrapper" class="clearfix">
<h1>CSV list web interface</h1>
<div class=container>
<div id="files">
<ul id="file-list"></ul>
<div class="field">
<input type="text" id="csv-path" name="path" value='/csv/'>
<button id="load-list">Load list</button><br><br>
<button id="download-csv">Download</button>
<button id="save-csv">Save CSV</button>
</div>
<hr>
<input type="button" onclick="deleteAll()" value="Delete all files" class="delete-all">
<hr>
</div>
<div id="content">
<div class="tables text-center" id="csv-table"></div>
</div>
</div>
</div>
</main>
<script src="assets/js/csv.js"></script>
<script src="assets/js/index.js"></script>
</body>
</html>
================================================
FILE: examples/csvLoggerSD/partitions.csv
================================================
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x140000,
app1, app, ota_1, 0x150000,0x140000,
spiffs, data, spiffs, 0x290000,0x160000,
coredump, data, coredump,0x3F0000,0x10000,
================================================
FILE: examples/csvLoggerSD/platformio.ini
================================================
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[platformio]
src_dir = .
[env:esp32-s3-devkitc1-n4r2]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip
board = esp32-s3-devkitc1-n4r2
framework = arduino
upload_speed = 921600
board_build.partitions = partitions.csv
lib_extra_dirs = ../../
lib_ignore = pio_examples
[env:esp32dev]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip
board = esp32dev
framework = arduino
upload_speed = 921600
board_build.partitions = partitions.csv
lib_extra_dirs = ../../
lib_ignore = pio_examples
[env:esp8266-nodemcuv2]
platform = espressif8266
board = nodemcuv2
framework = arduino
upload_speed = 921600
board_build.partitions = partitions.csv
lib_extra_dirs = ../../
lib_ignore = pio_examples
================================================
FILE: examples/csvLoggerSD/readme.md
================================================
An example for logging to a CSV file and viewing the content with the browser.
It is also possible to modify or download the file.

================================================
FILE: examples/customHTML/customElements.h
================================================
#pragma once
#include <Arduino.h>
/*
* This HTML code will be injected in /setup webpage using a <div></div> element as parent
* The parent element will have the HTML id properties equal to 'raw-html-<id>'
* where the id value will be equal to the id parameter passed to the function addHTML(html_code, id).
*/
inline const char custom_html[] PROGMEM = R"EOF(
<label for=url class=input-label>Endpoint</label>
<input type=text placeholder='https://httpbin.org/' id=url value='https://httpbin.org/' />
<br>
<div class=row-wrapper>
<input type="radio" id="get" name="httpmethod" value="GET" checked>
<label for="html">GET</label><br>
<input type="radio" id="post" name="httpmethod" value="POST">
<label for="css">POST</label>
<a id=fetch class='btn'>
<span>Fecth url</span>
</a>
</div>
<pre id=payload></pre>
)EOF";
/*
* In this example, a style sections is added in order to render properly the new
* <select> and <pre> elements introduced. Since this section will be added at the end of the body,
* it is also possible to override the style of the elements already present:
* for example the background color of body will be overridden with a different color
*/
inline const char custom_css[] PROGMEM = R"EOF(
pre{
font-family: Monaco,Menlo,Consolas,'Courier New',monospace;
color: #333;
line-height: 20px;
background-color: #f5f5f5;
border: 1px solid rgba(0,0,0,0.15);
border-radius: 6px;
overflow-y: scroll;
min-height: 350px;
font-size: 85%;
width: 95%;
}
)EOF";
/*
* Also the JavaScript will be added at the bottom of body
* In this example a simple 'click' event listener will be added for the button
* with id='fetch' (added as HTML). The listener will execute the function 'fetchEndpoint'
* in order to fetch a remote resource and show the response in a text box.
*
* The instruction $('<id-name>') is a "Jquery like" selector already defined
* so you can use for your purposes:
* var $ = function(el) {
* return document.getElementById(el);
* };
*/
inline const char custom_script[] PROGMEM = R"EOF(
function fetchEndpoint() {
var mt;
document.getElementsByName('httpmethod').forEach(el => {
if (el.checked)
mt = el.value;
})
var url = $('url').value + mt.toLowerCase();
var bd = (mt != 'GET') ? 'body: ""' : '';
var options = {
method: mt,
bd
};
fetch(url, options)
.then(response => response.text())
.then(txt => {
$('payload').innerHTML = txt;
});
}
$('fetch').addEventListener('click', fetchEndpoint);
)EOF";
inline const char base64_logo[] PROGMEM = R"EOF(
iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAABGdBTUEAALGPC/xhBQAAAAFzUkdC
AK7OHOkAAAIKUExURQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAIhNjGoAAACtdFJOUwBl/fwyM2b+Bfn7mRAHBgEa+veYfx4C
PNsD6Lbr3T49T6zLhOYOzN7uWmx671yOjR0PU1EJTtTzaj8x9hP1wFDwfrg5gC1fIXeTYrIEVQry
+I+crg3XFoE1lFnR07HShyVdbW7f1cdo5aPxq8n0cO1xxJsRW2kM52QYwRkLYa0v4LNYb2BEUpd5
1siCFIaVpbAm7FbkiJKmF2ODxmfqmldJNn0gG0rCRtpNxUVMXLiLfgAABc5JREFUaN7tWflXE0kQ
bifAQAgJBAiBhAQMEATCfYsKoigIKIKioogHgnjggfe967Guq3vf933O/7gz1T0zfUyGwA4/7Hup
N/BSNT319VfTXd1TjVBa0pKWtPx/xHtkYTh0vGyrJG0tOx4aXvjR66T34jM7uxRFZq5LO88Ur91T
fZFoyzu13cV7Vy/1n2v7qTyxfVF9cv/dsryLM1W/UybbSFl9Ne9flruT+e9wKRxC9tthse8GA7jC
l7NZ/4ri7rD2v3u/9hiNUNKpKKsBKPJ8lOm/atm/28q/J4hZN+TrwW9yySmJ1Ki/ivwGbAl6LAAq
cb+2lRB9Tw10kO24VFAg8QzUq2aPznkbtlwQ/Y+AL9nVRvRIWGYA3Ocahwabb3q9N5sHh5q+LmAD
Fo6Qx9rIiBsRhvoSJreX6K1+Ogg5J461sO1b3vogh27hbyU39mK9n58kT3BnK4m6xa33Tv2rjeVb
vbT8r2opEu4txPwQW/axjStk8HWWvJwdZgQU/0lPsnHtOek3o1SwgxjP4ndUwWQaPIJcPU4klh48
+IJ0tjqNO3vUmdR1FHOqMy3tVWC6Ve0MQMstAKhqNyx1+M0/cCr7PsD+DAql5fBaZkudAiidBYfl
usMKPAxanVtAWvHQ0gdSCAjNB5wDCMyDyxCZMC5gkEUytCg+/blR2qry91k3UyULGLjxBL0CSv99
7P9SjiC9cKd5IuinrW8Q6mWahff9agDc74dOXwHlL6BTSAjkiOk4Q7sx1sVZ1Qcy+ZafG0toIehX
tZ+HcdqJGgBshlb/NIDf3fyCk6sCCOvQ+zpABCeow1peg3tVAVuA7LiSEoD8gz5Sq8CiZcCDQOYg
sg3Rn6LVKkSybCRR7Paa+usQQH2MbBm8llNjoHykA3wP6iGErrtgKbzDApzPouQ97Z1p1se0NWoA
xEA/D79zRomje6C6rqMeoBJHbIg2cTMnF6ybOWsmNcpQFl77jE1MHPQI6oZeVCKWgQigWQUAzEAH
AAYGwEMI2g00AUDTzjOYBr0RTUIvEs4zSACDSXQRGkWdB4gAwLvkXcxxIYplUDKuz/yXWH/kYUO0
AtYYF6IPQS9HeOvRh+znQRO7v/vtGc2AvpaMnNoHlmV0AG7krQKQ4KzSqyQAd83vCrAcQBIw8SH7
VDFVyxuzkGWq+NIA8OGNMcJ7Wd8qDLRXyN6onbJicHHUBMBUUwwRQq8kLhetWADM3ENCiJaBSd8q
IVLl2V2JsTYJIfrkp3ZqDPeBbRnFAXyOYxDLpGRc34o+wvpLYFBoMFgBa8bPPmaSzAGD8hQnGiM1
XLrOsGylT7RJYJJYJRcxsplbcKwBEnBvEk1AL3Y5z2AaGDSiGyml63UAVJJ03UNShuMhKicLDlky
v+CWzE2UqEtmlNYfc6PIEuCOBEumR1/0x+wnWq646L+2BxjTF32yv/jOfqLlitZ/7EN0zdi24I3X
YmCNDOLZtgwCi8bGi2wdS9YG4P4b2QKUmFtHdBXIvFhTiLrGhEWflRfm5pds3xvG7bbvb2jd/8dE
MzTupbf3jIw3AIPL+AMEb5xv232AlNK6kfN94pcHkdv0BwgKAUCnk59QnQAQMusImrQ5B/Ace6xg
P2NnHKMQmGE/Y9UPcWD01CmAp3j41lGlBGBU5VApoRq7W2wXiiHDG1UMQd4gALgizpRzAIAp5+gD
KW4WpMxi2ad2BSmzHVWQAumwLakdY0tqv1iW1GKWJbVKy5IaKl7CVUe9KPjcr1BVR6ui4AkmLZpF
QWwRioJoBBMzyprRMJvecFlzyuudKh4cajxXwN79NmqUNbFhRGR8YYMLs8gTxI8xpeVUateK1GSW
lrHFsrSsFcc1KaKL46mUrjtLzCcGwGJdHEeoQ83bctF/K+8P2JT34YCiSDygsAMQDygGlOQHFEmO
WBLbk5T5rY9YBurXcUh0Wjwk6lrXIZHdMdc3n+nHXLOh4YUjjh5zpSUtaUnLBsu/XvNl5HUuawwA
AAAASUVORK5CYII=
)EOF";
================================================
FILE: examples/customHTML/customHTML.ino
================================================
#include <FS.h>
#include <LittleFS.h>
#include <FSWebServer.h> // https://github.com/cotestatnt/esp-fs-webserver
const char* hostname = "myserver";
#define FILESYSTEM LittleFS
FSWebServer server(FILESYSTEM, 80, hostname);
#ifndef LED_BUILTIN
#define LED_BUILTIN 2
#endif
// Test "options" values
uint8_t ledPin = LED_BUILTIN;
bool boolVar = true;
bool boolVar2 = false;
uint32_t longVar = 1234567890;
float floatVar = 15.5F;
String stringVar = "Test option String";
String dropdownSelected = "Item1";
// ThingsBoard variables
String tb_deviceName = "ESP Sensor";
double tb_deviceLatitude = 41.88505;
double tb_deviceLongitude = 12.50050;
String tb_deviceToken = "xxxxxxxxxxxxxxxxxxx";
String tb_device_key = "xxxxxxxxxxxxxxxxxxx";
String tb_secret_key = "xxxxxxxxxxxxxxxxxxx";
String tb_serverIP = "thingsboard.cloud";
uint16_t tb_port = 80;
// Var labels (in /setup webpage)
#define LED_LABEL "The LED pin number"
#define BOOL_LABEL "A bool variable"
#define LONG_LABEL "A long variable"
#define FLOAT_LABEL "A float variable"
#define STRING_LABEL "A String variable"
#define DROPDOWN_TEST "A dropdown listbox"
#define TB_DEVICE_NAME "Device Name"
#define TB_DEVICE_LAT "Device Latitude"
#define TB_DEVICE_LON "Device Longitude"
#define TB_SERVER "ThingsBoard server address"
#define TB_PORT "ThingsBoard server port"
#define TB_DEVICE_TOKEN "ThingsBoard device token"
#define TB_DEVICE_KEY "Provisioning device key"
#define TB_SECRET_KEY "Provisioning secret key"
// Timezone definition to get properly time from NTP server
//n.u. #define MYTZ "CET-1CEST,M3.5.0,M10.5.0/3"
//n.u. struct tm Time;
/*
* Include the custom HTML, CSS and Javascript to be injected in /setup webpage.
* HTML code will be injected according to the order of options declaration.
* CSS and JavaScript will be appended to the end of body in order to work properly.
* In this manner, is also possible override the default element styles
* like for example background color, margins, padding etc etc
*/
#include "customElements.h"
#include "thingsboard.h"
// Callback: notify user when the configuration file is saved
void onConfigSaved(const char* path) {
Serial.printf("\n[Config] File salvato: %s\n", path);
}
//////////////////////////////// Filesystem /////////////////////////////////////////
bool startFilesystem() {
if (FILESYSTEM.begin()){
server.printFileList(FILESYSTEM, "/", 1);
return true;
}
else {
Serial.println("ERROR on mounting filesystem. It will be reformatted!");
FILESYSTEM.format();
ESP.restart();
}
return false;
}
//////////////////// Load application options from filesystem ////////////////////
bool loadOptions() {
if (FILESYSTEM.exists(server.getConfiFileName())) {
// Test "options" values
server.getOptionValue(LED_LABEL, ledPin);
server.getOptionValue(BOOL_LABEL, boolVar);
server.getOptionValue(BOOL_LABEL "2", boolVar2);
server.getOptionValue(LONG_LABEL, longVar);
server.getOptionValue(FLOAT_LABEL, floatVar);
server.getOptionValue(STRING_LABEL, stringVar);
server.getOptionValue(DROPDOWN_TEST, dropdownSelected);
// ThingsBoard variables
server.getOptionValue(TB_DEVICE_NAME, tb_deviceName);
server.getOptionValue(TB_DEVICE_LAT, tb_deviceLatitude);
server.getOptionValue(TB_DEVICE_LON, tb_deviceLongitude);
server.getOptionValue(TB_DEVICE_TOKEN, tb_deviceToken);
server.getOptionValue(TB_DEVICE_KEY, tb_device_key);
server.getOptionValue(TB_SECRET_KEY, tb_secret_key);
server.getOptionValue(TB_SERVER, tb_serverIP);
server.getOptionValue(TB_PORT, tb_port);
server.closeSetupConfiguration(); // Close configuration to free resources
Serial.println("\nThis are the current values stored: \n");
Serial.printf("LED pin value: %d\n", ledPin);
Serial.printf("Bool value 1: %s\n", boolVar ? "true" : "false");
Serial.printf("Bool value 2: %s\n", boolVar2 ? "true" : "false");
Serial.printf("Long value: %u\n", longVar);
Serial.printf("Float value: %d.%d\n", (int)floatVar, (int)(floatVar*1000)%1000);
Serial.printf("String value: %s\n", stringVar.c_str());
Serial.printf("Dropdown selected: %s\n", dropdownSelected.c_str());
return true;
}
else {
Serial.println("Failed to parse configuration file");
return false;
}
return true;
}
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(115200);
// FILESYSTEM INIT
if (startFilesystem()){
// Load configuration (if not present, default will be created when webserver will start)
loadOptions();
}
// Try to connect to WiFi (will start AP if not connected after timeout)
if (!server.startWiFi(10000)) {
Serial.println("\nWiFi not connected! Starting AP mode...");
server.startCaptivePortal("ESP_AP", "123456789");
}
// Add custom HTTP request handlers to webserver
server.on("/reload", HTTP_GET, [](){
server.send(200, "text/plain", "Options loaded");
loadOptions();
Serial.println("Application option loaded after web request");
});
// Add a new options box
server.addOptionBox("My Options");
server.addOption(LED_LABEL, ledPin);
server.addOption(LONG_LABEL, longVar);
// Float fields can be configured with min, max and step properties
server.addOption(FLOAT_LABEL, floatVar, 0.0, 100.0, 0.01);
server.addOption(STRING_LABEL, stringVar);
server.addOption(BOOL_LABEL, boolVar);
server.addOption(BOOL_LABEL "2", boolVar2);
static const char* dropItem[] = {"Item1", "Item2", "Item3"};
FSWebServer::DropdownList dropdownDef{ DROPDOWN_TEST, dropItem, 3, 0 };
server.addDropdownList(dropdownDef);
// Add a new options box with custom code injected
server.addOptionBox("Custom HTML");
// How many times you need (for example one in different option box)
server.addHTML(custom_html, "fetch-test", /*overwrite*/ false);
// Add a new options box
server.addOptionBox("ThingsBoard");
server.addOption(TB_DEVICE_NAME, tb_deviceName);
server.addOption(TB_DEVICE_LAT, tb_deviceLatitude, -180.0, 180.0, 0.00001);
server.addOption(TB_DEVICE_LON, tb_deviceLongitude, -180.0, 180.0, 0.00001);
server.addOption(TB_SERVER, tb_serverIP);
server.addOption(TB_PORT, tb_port);
server.addOption(TB_DEVICE_KEY, tb_device_key);
server.addOption(TB_SECRET_KEY, tb_secret_key);
server.addOption(TB_DEVICE_TOKEN, tb_deviceToken);
server.addHTML(thingsboard_htm, "ts", /*overwrite file*/ false);
// CSS will be appended to HTML head
server.addCSS(custom_css, "fetch", /*overwrite file*/ false);
// Javascript will be appended to HTML body
server.addJavascript(custom_script, "fetch", /*overwrite file*/ false);
server.addJavascript(thingsboard_script, "ts", /*overwrite file*/ false);
// Add custom page title to /setup
server.setSetupPageTitle("Custom HTML Web Server");
// Add custom logo to /setup page with custom size
//server.setLogoBase64(base64_logo, "128", "128", /*overwrite file*/ false);
// Enable ACE FS file web editor and add FS info callback function
server.enableFsCodeEditor();
// Inform user when config.json is saved via /edit or /upload
server.setConfigSavedCallback(onConfigSaved);
// Start web server
server.begin();
Serial.print(F("\n\nWeb Server started on IP Address: "));
Serial.println(server.getServerIP());
Serial.println(F(
"\nThis is \"customHTML.ino\" example.\n"
"Open /setup page to configure optional parameters.\n"
"Open /edit page to view, edit or upload example or your custom webserver source files."
));
Serial.printf("Ready! Open http://%s.local in your browser\n", hostname);
if (server.isAccessPointMode())
Serial.print(F("Captive portal is running"));
}
void loop() {
server.run(); // Handle client requests
// Nothing to do here, just a small delay for task yield
delay(10);
}
================================================
FILE: examples/customHTML/thingsboard.h
================================================
#pragma once
#include <Arduino.h>
inline const char thingsboard_htm[] PROGMEM = R"EOF(
<div>
<br>If you don't have a valid <b>device token</b> press button "Device Provisioning" to start procedure in order to get a new token from ThingsBoard server.<br>
<br>To perform <a href="https://thingsboard.io/docs/user-guide/device-provisioning/">device provisioning</a>, this functionality must be enabled in the ThingsBoard profile of your devices.
<div class='btn-bar'>
<a id=device-provisioning class='btn'>
<div class=svg>
<svg class='icon' viewBox='0 0 24 24'>
<path fill='currentColor' d='M21.41 11.58L12.41 2.58C12.04 2.21 11.53 2 11 2H4C2.9 2 2 2.9 2 4V11C2 11.53 2.21 12.04 2.59 12.41L3 12.81C3.9 12.27 4.94 12 6 12C9.31 12 12 14.69 12 18C12 19.06 11.72 20.09 11.18 21L11.58 21.4C11.95 21.78 12.47 22 13 22S14.04 21.79 14.41 21.41L21.41 14.41C21.79 14.04 22 13.53 22 13S21.79 11.96 21.41 11.58M5.5 7C4.67 7 4 6.33 4 5.5S4.67 4 5.5 4 7 4.67 7 5.5 6.33 7 5.5 7M8.63 14.27L4.76 18.17L3.41 16.8L2 18.22L4.75 21L10.03 15.68L8.63 14.27' />
</svg>
</div>
<span> Device provisioning</span>
</a>
</div>
</div>
)EOF";
inline const char thingsboard_script[] PROGMEM = R"EOF(
const TB_DEVICE_NAME = 'Device Name';
const TB_DEVICE_LAT = 'Device Latitude';
const TB_DEVICE_LON = 'Device Longitude';
const TB_SERVER = 'ThingsBoard server address';
const TB_PORT = 'ThingsBoard server port';
const TB_DEVICE_TOKEN = 'ThingsBoard device token';
const TB_DEVICE_KEY = 'Provisioning device key';
const TB_SECRET_KEY = 'Provisioning secret key';
function getUrl(host, port) {
var url;
if (port === '80')
url = 'http://' + host + '/api/v1/';
else if (port === '443')
url = 'https://' + host + '/api/v1/'
else
url = 'http://' + host + ':'+ port + '/api/v1/'
return url;
}
function deviceProvisioning() {
console.log('Device provisioning');
var server = $(TB_SERVER).value;
var port = $(TB_PORT).value;
var token = $(TB_DEVICE_TOKEN).value;
if (token === '')
token = 'xxxxx';
const url = 'https://corsproxy.io/?' + encodeURIComponent(getUrl(server, port) + token + '/attributes');
fetch(url, {
method: "GET"
})
.then((response) => {
if (response.ok) {
openModalMessage('Device provisioning', 'Device already provisioned. Do you want update device client attributes?', setDeviceClientAttribute);
return;
}
throw new Error('Device token not present. Provisioning new device');
})
.catch((error) => {
openModalMessage('New device', 'A new device will be provisioned on ThingsBoard server', createNewDevice);
});
}
function createNewDevice() {
var server = $(TB_SERVER).value;
var port = $(TB_PORT).value;
var key = $(TB_DEVICE_KEY).value;
var secret = $(TB_SECRET_KEY).value;
var name = $(TB_DEVICE_NAME).value;
const url = 'https://corsproxy.io/?' + encodeURIComponent(getUrl(server, port) + 'provision');
var payload = {
'deviceName': name,
'provisionDeviceKey': key,
'provisionDeviceSecret': secret
};
fetch(url, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
method: 'POST',
body: JSON.stringify(payload)
})
.then(response => response.json())
.then(obj => {
var token = $(TB_DEVICE_TOKEN);
token.focus();
token.value = obj.credentialsValue;
options[token.id] = token.value; // Manual update, because, it doesn't fire "change" event...
openModal('Write device attributes', 'Device provisioned correctly.<br>Do you want to set client attributes on ThingsBoard server?', setDeviceClientAttribute);
});
}
function setDeviceClientAttribute(){
var server = $(TB_SERVER).value;
var port = $(TB_PORT).value;
var token = $(TB_DEVICE_TOKEN).value;
var name = $(TB_DEVICE_NAME).value;
var latitude = $(TB_DEVICE_LAT).value;
var longitude = $(TB_DEVICE_LON).value;
const url = 'https://corsproxy.io/?' + encodeURIComponent(getUrl(server, port) + token + '/attributes');
var payload = {
'DeviceName': name,
'latitude': latitude,
'longitude': longitude,
};
fetch(url, {
method: 'POST',
body: JSON.stringify(payload),
})
.then(response => response.text())
.then(() => {
openModalMessage('Device properties', 'Device client properties updated!');
});
}
$('device-provisioning').addEventListener('click', deviceProvisioning);
)EOF";
================================================
FILE: examples/customOptions/.gitignore
================================================
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
================================================
FILE: examples/customOptions/customOptions.ino
================================================
#include <FS.h>
#include <LittleFS.h>
#include <FSWebServer.h> //https://github.com/cotestatnt/esp-fs-webserver
// Timezone definition to get properly time from NTP server
#define MYTZ "CET-1CEST,M3.5.0,M10.5.0/3"
struct tm Time;
#define FILESYSTEM LittleFS
FSWebServer server(FILESYSTEM, 80, "myserver");
// Define built-in LED if not defined by board (eg. generic dev boards)
#ifndef LED_BUILTIN
#define LED_BUILTIN 2
#endif
#ifndef BOOT_PIN
#define BOOT_PIN 0
#endif
// Labels used in /setup webpage for options
#define LED_LABEL "The LED pin number"
#define BOOL_LABEL "A bool variable"
#define BOOL_LABEL2 "A second bool variable"
#define LONG_LABEL "A long variable"
#define FLOAT_LABEL "A float variable"
#define STRING_LABEL "A String variable"
#define DROPDOWN_LABEL "Days of week"
#define BRIGHTNESS_LABEL "Brightness"
// Test "options" values
uint8_t ledPin = LED_BUILTIN;
bool boolVar = true;
bool boolVar2 = false;
uint32_t longVar = 1234567890;
float floatVar = 15.51F;
String stringVar = "Test option String";
// Add a dropdown list box in /setup page
const char* days[] = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"};
uint8_t daySelected = 2;// Default to "Wednesday"
SetupConfig::DropdownList dayOfWeek{ DROPDOWN_LABEL, days, 7, daySelected};
// Add a slider in /setup page
SetupConfig::Slider brightness{ BRIGHTNESS_LABEL, 0.0, 100.0, 1.0, 50.0 };
static const char reload_btn_htm[] PROGMEM = R"EOF(
<div class="btn-bar">
<a class="btn" id="reload-btn">Reload options</a>
</div>
)EOF";
static const char reload_btn_script[] PROGMEM = R"EOF(
/* Add click listener to button */
const reloadCfg = () => {
console.log('Reload configuration options');
fetch('/reload')
.then((response) => {
if (response.ok) {
openModal('Options loaded', 'Options was reloaded from configuration file');
return;
}
throw new Error('Something goes wrong with fetch');
})
.catch((error) => {
openModal('Error', 'Something goes wrong with your request');
});
};
document.getElementById('reload-btn').addEventListener('click', reloadCfg);
)EOF";
/////////// Callback: notify user when the configuration file is saved /////////////
void onConfigSaved(const char* path) {
Serial.printf("\n[Config] File salvato: %s\n", path);
}
//////////////////////////////// Filesystem /////////////////////////////////////////
bool startFilesystem() {
if (FILESYSTEM.begin()){
server.printFileList(FILESYSTEM, "/", 2);
return true;
}
else {
Serial.println("ERROR on mounting filesystem. It will be reformatted!");
FILESYSTEM.format();
ESP.restart();
}
return false;
}
//////////////////// Load application options from filesystem ////////////////////
bool loadOptions() {
if (FILESYSTEM.exists(server.getConfiFileName())) {
server.getOptionValue(LED_LABEL, ledPin);
server.getOptionValue(BOOL_LABEL, boolVar);
server.getOptionValue(BOOL_LABEL2, boolVar2);
server.getOptionValue(LONG_LABEL, longVar);
server.getOptionValue(FLOAT_LABEL, floatVar);
server.getOptionValue(STRING_LABEL, stringVar);
server.getDropdownSelection(dayOfWeek);
server.getSliderValue(brightness);
server.closeSetupConfiguration(); // Close configuration to free resources
Serial.println("\nThis are the current values stored: \n");
Serial.printf("LED pin value: %d\n", ledPin);
Serial.printf("Bool value: %s\n", boolVar ? "true" : "false");
Serial.printf("Bool value2: %s\n", boolVar2 ? "true" : "false");
Serial.printf("Long value: %u\n", longVar);
Serial.printf("Float value: %3.2f\n", floatVar);
Serial.printf("String value: %s\n", stringVar.c_str());
Serial.printf("Dropdown selected value: %s\n", days[dayOfWeek.selectedIndex]);
Serial.printf("Slider value: %3.2f\n\n", brightness.value);
return true;
}
else
Serial.println(F("Config file not exist"));
return false;
}
//////////////////////////// HTTP Request Handlers ////////////////////////////////////
void handleLoadOptions() {
server.send(200, "text/plain", "Options loaded");
loadOptions();
Serial.println("Application option loaded after web request");
}
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
pinMode(BOOT_PIN, INPUT_PULLUP);
Serial.begin(115200);
// FILESYSTEM INIT
if (startFilesystem()){
// Load configuration (if not present, default will be created when webserver will start)
loadOptions();
}
// Default firmware version is set to compile time, but you can edit with a custom string
// Custom firmware version -> Major.Minor.Build (build is set to compile date YYYYMMDDHHMM)
String version = "1.0." + String(BUILD_TIMESTAMP);
server.setFirmwareVersion(version);
// Try to connect to WiFi (will start AP if not connected after timeout)
if (!server.startWiFi(10000)) {
Serial.println("\nWiFi not connected! Starting AP mode...");
server.startCaptivePortal("ESP_AP", "123456789", "/setup");
}
// Add custom page handler
server.on("/reload", HTTP_GET, handleLoadOptions);
// Configure /setup page and start Web Server
server.addOptionBox("My Options");
server.addOption(BOOL_LABEL, boolVar);
server.addOption(BOOL_LABEL2, boolVar2);
// You can also add a comment to the option,
// which will be displayed in the UI as helper text.
server.addOption(LED_LABEL, ledPin, "Select the GPIO for the LED");
server.addOption(LONG_LABEL, longVar, "Enter a long integer value");
server.addOption(FLOAT_LABEL, floatVar, 1.0, 100.0, 0.01);
server.addOption(STRING_LABEL, stringVar, "Enter a custom string");
server.addDropdownList(dayOfWeek);
server.addSlider(brightness);
server.addHTML(reload_btn_htm, "buttons", /*overwrite*/ false);
server.addJavascript(reload_btn_script, "js", /*overwrite*/ false);
// Enable ACE FS file web editor and add FS info callback function
#ifdef ESP32
server.enableFsCodeEditor([](fsInfo_t* fsInfo) {
fsInfo->fsName = "LittleFS";
fsInfo->totalBytes = LittleFS.totalBytes();
fsInfo->usedBytes = LittleFS.usedBytes();
});
#else
// ESP8266 core support LittleFS by default
server.enableFsCodeEditor();
#endif
// set /setup and /edit page authentication
server.setAuthentication("admin", "admin");
// Inform user when config.json is saved via /edit or /upload
server.setConfigSavedCallback(onConfigSaved);
// Start server
server.begin();
Serial.print(F("\nESP Web Server started on IP Address: "));
Serial.println(server.getServerIP());
Serial.println(F(
"\nThis is \"customOptions.ino\" example.\n"
"Open /setup page to configure optional parameters.\n"
"Open /edit page to view, edit or upload example or your custom webserver source files."
));
}
void loop() {
server.handleClient();
if (server.isAccessPointMode())
server.updateDNS();
// Keep BOOT_PIN pressed 5 seconds to clear application options
static uint32_t buttonPressStart = 0;
static bool buttonPressed = false;
if (digitalRead(BOOT_PIN) == LOW) {
if (!buttonPressed) {
buttonPressed = true;
buttonPressStart = millis();
}
else if (millis() - buttonPressStart >= 5000) {
Serial.println("\nClearing application options...");
server.clearConfigFile();
delay(1000);
ESP.restart();
}
} else {
buttonPressed = false;
}
// Nothing to do here, just a small delay for task yield
delay(10);
}
================================================
FILE: examples/customOptions/partitions.csv
================================================
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x140000,
app1, app, ota_1, 0x150000,0x140000,
spiffs, data, spiffs, 0x290000,0x160000,
coredump, data, coredump,0x3F0000,0x10000,
================================================
FILE: examples/customOptions/platformio.ini
================================================
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[platformio]
src_dir = .
[env:esp32-s3-devkitc1-n4r2]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip
board = esp32-s3-devkitc1-n4r2
framework = arduino
upload_speed = 921600
board_build.partitions = partitions.csv
lib_extra_dirs = ../../
lib_ignore = pio_examples
monitor_speed = 115200
[env:esp32dev]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip
board = esp32dev
framework = arduino
upload_speed = 921600
board_build.partitions = partitions.csv
lib_extra_dirs = ../../
lib_ignore = pio_examples
[env:esp8266-nodemcuv2]
platform = espressif8266
board = nodemcuv2
framework = arduino
upload_speed = 921600
board_build.partitions = partitions.csv
lib_extra_dirs = ../../
lib_ignore = pio_examples
================================================
FILE: examples/esp32-cam/.gitignore
================================================
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
================================================
FILE: examples/esp32-cam/camera_pins.h
================================================
#pragma once
#include <Arduino.h>
#include "esp_camera.h"
#include <esp_err.h>
// Select camera model
//#define CAMERA_MODEL_WROVER_KIT
//#define CAMERA_MODEL_ESP_EYE
//#define CAMERA_MODEL_M5STACK_PSRAM
//#define CAMERA_MODEL_M5STACK_V2_PSRAM
//#define CAMERA_MODEL_M5STACK_WIDE
//#define CAMERA_MODEL_M5STACK_ESP32CAM
#define CAMERA_MODEL_AI_THINKER
//#define CAMERA_MODEL_TTGO_T_JOURNAL
#if defined(CAMERA_MODEL_WROVER_KIT)
//
// ESP WROVER
// https://dl.espressif.com/dl/schematics/ESP-WROVER-KIT_SCH-2.pdf
//
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 21
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 19
#define Y4_GPIO_NUM 18
#define Y3_GPIO_NUM 5
#define Y2_GPIO_NUM 4
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
#define LED_PIN 2 // A status led on the RGB; could also use pin 0 or 4
#define LED_ON HIGH //
#define LED_OFF LOW //
// #define LAMP_PIN x // No LED FloodLamp.
#elif defined(CAMERA_MODEL_ESP_EYE)
//
// ESP-EYE
// https://twitter.com/esp32net/status/1085488403460882437
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 4
#define SIOD_GPIO_NUM 18
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 36
#define Y8_GPIO_NUM 37
#define Y7_GPIO_NUM 38
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 35
#define Y4_GPIO_NUM 14
#define Y3_GPIO_NUM 13
#define Y2_GPIO_NUM 34
#define VSYNC_GPIO_NUM 5
#define HREF_GPIO_NUM 27
#define PCLK_GPIO_NUM 25
#define LED_PIN 21 // Status led
#define LED_ON HIGH //
#define LED_OFF LOW //
// #define LAMP_PIN x // No LED FloodLamp.
#elif defined(CAMERA_MODEL_M5STACK_PSRAM)
//
// ESP32 M5STACK
//
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM 15
#define XCLK_GPIO_NUM 27
#define SIOD_GPIO_NUM 25
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 19
#define Y8_GPIO_NUM 36
#define Y7_GPIO_NUM 18
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 5
#define Y4_GPIO_NUM 34
#define Y3_GPIO_NUM 35
#define Y2_GPIO_NUM 32
#define VSYNC_GPIO_NUM 22
#define HREF_GPIO_NUM 26
#define PCLK_GPIO_NUM 21
// M5 Stack status/illumination LED details unknown/unclear
// #define LED_PIN x // Status led
// #define LED_ON HIGH //
// #define LED_OFF LOW //
// #define LAMP_PIN x // LED FloodLamp.
#elif defined(CAMERA_MODEL_M5STACK_V2_PSRAM)
//
// ESP32 M5STACK V2
//
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM 15
#define XCLK_GPIO_NUM 27
#define SIOD_GPIO_NUM 22
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 19
#define Y8_GPIO_NUM 36
#define Y7_GPIO_NUM 18
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 5
#define Y4_GPIO_NUM 34
#define Y3_GPIO_NUM 35
#define Y2_GPIO_NUM 32
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 26
#define PCLK_GPIO_NUM 21
// M5 Stack status/illumination LED details unknown/unclear
// #define LED_PIN x // Status led
// #define LED_ON HIGH //
// #define LED_OFF LOW //
// #define LAMP_PIN x // LED FloodLamp.
#elif defined(CAMERA_MODEL_M5STACK_WIDE)
//
// ESP32 M5STACK WIDE
//
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM 15
#define XCLK_GPIO_NUM 27
#define SIOD_GPIO_NUM 22
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 19
#define Y8_GPIO_NUM 36
#define Y7_GPIO_NUM 18
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 5
#define Y4_GPIO_NUM 34
#define Y3_GPIO_NUM 35
#define Y2_GPIO_NUM 32
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 26
#define PCLK_GPIO_NUM 21
// M5 Stack status/illumination LED details unknown/unclear
// #define LED_PIN x // Status led
// #define LED_ON HIGH //
// #define LED_OFF LOW //
// #define LAMP_PIN x // LED FloodLamp.
#elif defined(CAMERA_MODEL_M5STACK_ESP32CAM)
//
// Common M5 Stack without PSRAM
//
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM 15
#define XCLK_GPIO_NUM 27
#define SIOD_GPIO_NUM 25
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 19
#define Y8_GPIO_NUM 36
#define Y7_GPIO_NUM 18
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 5
#define Y4_GPIO_NUM 34
#define Y3_GPIO_NUM 35
#define Y2_GPIO_NUM 17
#define VSYNC_GPIO_NUM 22
#define HREF_GPIO_NUM 26
#define PCLK_GPIO_NUM 21
// Note NO PSRAM,; so maximum working resolution is XGA 1024×768
// M5 Stack status/illumination LED details unknown/unclear
// #define LED_PIN x // Status led
// #define LED_ON HIGH //
// #define LED_OFF LOW //
// #define LAMP_PIN x // LED FloodLamp.
#elif defined(CAMERA_MODEL_AI_THINKER)
//
// AI Thinker
// https://github.com/SeeedDocument/forum_doc/raw/master/reg/ESP32_CAM_V1.6.pdf
//
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
#define LED_PIN 33 // Status led
#define LED_ON LOW // - Pin is inverted.
#define LED_OFF HIGH //
#define LAMP_PIN 4 // LED FloodLamp.
#elif defined(CAMERA_MODEL_TTGO_T_JOURNAL)
//
// LilyGO TTGO T-Journal ESP32; with OLED! but not used here.. :-(
#define PWDN_GPIO_NUM 0
#define RESET_GPIO_NUM 15
#define XCLK_GPIO_NUM 27
#define SIOD_GPIO_NUM 25
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 19
#define Y8_GPIO_NUM 36
#define Y7_GPIO_NUM 18
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 5
#define Y4_GPIO_NUM 34
#define Y3_GPIO_NUM 35
#define Y2_GPIO_NUM 17
#define VSYNC_GPIO_NUM 22
#define HREF_GPIO_NUM 26
#define PCLK_GPIO_NUM 21
// TTGO T Journal status/illumination LED details unknown/unclear
// #define LED_PIN 33 // Status led
// #define LED_ON LOW // - Pin is inverted.
// #define LED_OFF HIGH //
// #define LAMP_PIN 4 // LED FloodLamp.
#else
// Well.
// that went badly...
#error "Camera model not selected, did you forget to uncomment it in myconfig?"
#endif
camera_config_t camera_config = {
.pin_pwdn = PWDN_GPIO_NUM,
.pin_reset = RESET_GPIO_NUM,
.pin_xclk = XCLK_GPIO_NUM,
.pin_sscb_sda = SIOD_GPIO_NUM,
.pin_sscb_scl = SIOC_GPIO_NUM,
.pin_d7 = Y9_GPIO_NUM,
.pin_d6 = Y8_GPIO_NUM,
.pin_d5 = Y7_GPIO_NUM,
.pin_d4 = Y6_GPIO_NUM,
.pin_d3 = Y5_GPIO_NUM,
.pin_d2 = Y4_GPIO_NUM,
.pin_d1 = Y3_GPIO_NUM,
.pin_d0 = Y2_GPIO_NUM,
.pin_vsync = VSYNC_GPIO_NUM,
.pin_href = HREF_GPIO_NUM,
.pin_pclk = PCLK_GPIO_NUM,
.xclk_freq_hz = 20000000, //XCLK 20MHz or 10MHz
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
.pixel_format = PIXFORMAT_JPEG, //YUV422,GRAYSCALE,RGB565,JPEG
.frame_size = FRAMESIZE_SXGA, //QQVGA-UXGA Do not use sizes above QVGA when not JPEG
.jpeg_quality = 8, //0-63 lower number means higher quality
.fb_count = 2, //if more than one, i2s runs in continuous mode. Use only with JPEG
.fb_location = CAMERA_FB_IN_PSRAM,
.grab_mode = CAMERA_GRAB_LATEST
};
int lampChannel = 7; // a free PWM channel (some channels used by camera)
const int pwmfreq = 50000; // 50K pwm frequency
const int pwmresolution = 9; // duty cycle bit range
const int pwmMax = pow(2,pwmresolution)-1;
inline esp_err_t init_camera() {
//initialize the camera
Serial.print("Camera init... ");
esp_err_t err = esp_camera_init(&camera_config);
if (err != ESP_OK) {
delay(100); // need a delay here or the next serial o/p gets missed
Serial.printf("\n\nCRITICAL FAILURE: Camera sensor failed to initialise.\n\n");
Serial.printf("A full (hard, power off/on) reboot will probably be needed to recover from this.\n");
return err;
} else {
Serial.println("succeeded");
// Get a reference to the sensor
sensor_t* s = esp_camera_sensor_get();
// Dump camera module, warn for unsupported modules.
switch (s->id.PID) {
case OV9650_PID: Serial.println("WARNING: OV9650 camera module is not properly supported, will fallback to OV2640 operation"); break;
case OV7725_PID: Serial.println("WARNING: OV7725 camera module is not properly supported, will fallback to OV2640 operation"); break;
case OV2640_PID: Serial.println("OV2640 camera module detected"); break;
case OV3660_PID: Serial.println("OV3660 camera module detected"); break;
default: Serial.println("WARNING: Camera module is unknown and not properly supported, will fallback to OV2640 operation");
}
s->set_sharpness(s, 1);
s->set_vflip(s, 1);
//s->set_hmirror(s, 1);
}
return ESP_OK;
}
================================================
FILE: examples/esp32-cam/data/index.htm
================================================
<!DOCTYPE HTML>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="0; url=/www/index.htm">
<script type="text/javascript">
window.location.href = "/www/index.htm"
</script>
<title>Page Redirection</title>
</head>
<body>
<!-- Note: don't tell people to `click` the link, just tell them that it is a link. -->
If you are not redirected automatically, follow this <a href='/www/index.htm'>link</a>.
</body>
</html>
================================================
FILE: examples/esp32-cam/data/www/app.js
================================================
const imgFolder = '/img/';
var fileList = document.getElementById('file-list');
var intervalID;
// Load list of files every x milliseconds (used in order to get fresh list)
var myInterval = function(val){
if (val !== 0)
intervalID = setInterval(listFiles, val*1000 + 1000);
else
clearInterval(intervalID);
};
// Fetch the list of files and fill the filelist
function listFiles() {
fetch('/list?dir=/img') // Do the request
.then(response => response.json()) // Parse the response
.then(obj => { // DO something with response
fileList.innerHTML = '';
obj.forEach(function(entry, i) {
addEntry(entry.name);
});
});
}
// Load selected image inside the preview content
function loadFile(filename) {
clearInterval(intervalID);
var name = document.getElementById("image-name");
var content = document.getElementById("image-content");
name.innerHTML = 'Image path: ' + filename;
content.src = filename;
content.alt = 'SD: ' + filename;
// Get the modal
var modal = document.getElementById("modal-container");
// Get the image and insert it inside the modal - use its "alt" text as a caption
var img = document.getElementById("image-content");
var modalImg = document.getElementById("modal-image");
var captionText = document.getElementById("caption");
img.onclick = function(){
modal.style.display = "block";
modalImg.src = this.src;
captionText.innerHTML = this.alt;
};
// Get the <span> element that closes the modal
var span = document.getElementsByClassName("close")[0];
// When the user clicks on <span> (x), close the modal
span.onclick = function() {
modal.style.display = "none";
};
}
// Delete selected file in SD
async function deleteFile(filename) {
console.log(filename);
const data = new URLSearchParams();
data.append('path', filename);
fetch('/edit', {
method: 'DELETE',
body: data
});
// Update the file browser.
listFiles();
}
async function deleteAll() {
let isExecuted = confirm("Are you sure to delete all files in /img/ folder?");
if(isExecuted){
var ul = document.getElementById("file-list");
var items = ul.getElementsByClassName("edit-file");
for (var i=0; i<items.length; i++) {
console.log("Delete " + items[i].innerHTML);
await deleteFile(imgFolder + items[i].innerHTML);
}
}
}
// Add a single entry to the filelist
function addEntry(entryName) {
console.log(entryName);
var li = document.createElement('li');
var link = document.createElement('a');
link.innerHTML = entryName;
link.className = 'edit-file';
li.appendChild(link);
var delLink = document.createElement('a');
delLink.innerHTML = '<span class="delete">×</span>';
delLink.className = 'delete-file';
li.appendChild(delLink);
fileList.insertBefore(li, fileList.firstChild);
// Setup an event listener that will load the file when the link is clicked.
link.addEventListener('click', function(e) {
e.preventDefault();
loadFile(imgFolder + entryName);
});
// Setup an event listener that will delete the file when the delete link is clicked.
delLink.addEventListener('click', function(e) {
e.preventDefault();
deleteFile(imgFolder + entryName);
});
}
// Add the buttons event listeners
var getNew = document.getElementById('get-new');
var getInterval = document.getElementById('set-interval');
getNew.addEventListener('click', function(e) {
e.preventDefault();
fetch('/getPicture') // Do the request
.then(response => response.text()) // Parse the response
.then(txt => { // DO something with response
console.log(txt);
addEntry(txt);
loadFile(imgFolder + txt);
});
});
getInterval.addEventListener('click', function(e) {
e.preventDefault();
let val = document.getElementById('interval').value;
fetch('/setInterval?val='+ val) // Do the request
.then(response => response.text()) // Parse the response
.then(txt => { // DO something with response
console.log('Set interval ' + txt);
myInterval(val);
});
});
// Start the web page
clearInterval(intervalID);
listFiles();
loadFile('espressif.jpg');
================================================
FILE: examples/esp32-cam/data/www/index.htm
================================================
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>ESP32-CAM-WEBPAGE.ino</title>
<!-- Cyustom page styling -->
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<main>
<div id="page-wrapper" class="clearfix">
<h1>ESP32-CAM web interface</h1><br>
<form action="#" method="POST" id="file-form">
<h2 id="image-name">filename</h2>
<div class="field">
<!-- The preview image -->
<img id="image-content" style="width:100%">
<!-- The Modal -->
<div id="modal-container" class="modal">
<span class="close">×</span>
<img class="modal-content" id="modal-image">
<div id="caption"></div>
</div>
</div>
<button id="get-new">Get new image</button>
<div style="float: right">
<label for="interval">Set interval (seconds):</label>
<input type="number" id="interval" name="interval" style="width: 75px" value=300 title="Set 0 to disable">
<button id="set-interval" title="Set 0 to disable">Set interval</button>
</div>
</form>
<div id="files">
<h2>Select image</h2>
<ul id="file-list"></ul>
<input type="button" onclick="deleteAll()" value="Delete All" class="delete-all">
</div>
</div>
</main>
<script src="app.js"></script>
</body>
</html>
================================================
FILE: examples/esp32-cam/data/www/styles.css
================================================
*, *:before, *:after {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
html {
font-family: Helvetica, Arial, sans-serif;
font-size: 100%;
background: #333;
color: #33383D;
-webkit-font-smoothing: antialiased;
}
#page-wrapper {
width: 960px;
background: #FFF;
padding: 1.25rem;
margin: 1rem auto;
min-height: 300px;
border-top: 5px solid #69c773;
box-shadow: 0 2px 10px rgba(0,0,0,0.8);
}
h2 {
margin-top: 0;
font-size: 0.9rem;
letter-spacing: 1px;
color: #999;
}
p {
font-size: 0.9rem;
margin: 0.5rem 0 1.5rem 0;
}
a,
a:visited {
color: #08C;
text-decoration: none;
}
a:hover,
a:focus {
color: #69c773;
cursor: pointer;
}
a.delete-file,
a.delete-file:visited {
color: #CC0000;
margin-left: 0.5rem;
vertical-align: middle;
}
.field {
margin-bottom: 1rem;
}
button {
width: 150px;
display: inline-block;
border-radius: 3px;
border: none;
font-size: 0.9rem;
padding: 0.6rem 1em;
background: #86b32d;
border-bottom: 1px solid #5d7d1f;
color: white;
margin: 0 0.25rem;
text-align: center;
}
button:hover {
opacity: 0.75;
cursor: pointer;
}
#file-form {
width: 75%;
float: left;
}
#files {
width: 23%;
float: right;
}
#files ul {
margin: 0;
padding: 0.5rem 1rem;
max-height: 600px;
overflow-y: auto;
list-style: square;
background: #F7F7F7;
border: 1px solid #D9D9D9;
border-radius: 3px;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1);
}
#files li {
margin-left: 8px;
font-size: 14px;
}
/* Clearfix Utils */
.clearfix {
*zoom: 1;
}
.clearfix:before,
.clearfix:after {
display: table;
line-height: 0;
content: "";
}
.clearfix:after {
clear: both;
}
#image-content {
border-radius: 5px;
cursor: pointer;
transition: 0.3s;
}
#image-content:hover {opacity: 0.7;}
/* The Modal (background) */
.modal {
display: none; /* Hidden by default */
position: fixed; /* Stay in place */
z-index: 1; /* Sit on top */
padding-top: 100px; /* Location of the box */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0,0,0); /* Fallback color */
background-color: rgba(0,0,0,0.9); /* Black w/ opacity */
}
/* Modal Content (image) */
.modal-content {
margin: auto;
display: block;
width: 90%;
}
/* Caption of Modal Image */
#caption {
margin: auto;
display: block;
text-align: center;
color: #ccc;
padding: 10px 0;
}
/* Add Animation */
.modal-content, #caption {
-webkit-animation-name: zoom;
-webkit-animation-duration: 0.6s;
animation-name: zoom;
animation-duration: 0.6s;
}
@-webkit-keyframes zoom {
from {-webkit-transform:scale(0)}
to {-webkit-transform:scale(1)}
}
@keyframes zoom {
from {transform:scale(0)}
to {transform:scale(1)}
}
/* The Close Button */
.close {
position: absolute;
top: 15px;
right: 35px;
color: #f1f1f1;
font-size: 40px;
font-weight: bold;
transition: 0.3s;
}
.close:hover,
.close:focus {
color: #bbb;
text-decoration: none;
cursor: pointer;
}
.delete {
font-size: 30px;
font-weight: bold;
transition: 0.3s;
}
.delete-all{
color: #f44336;
font-size: 12px;
background-color: transparent;
background-repeat: no-repeat;
border: none;
cursor: pointer;
overflow: hidden;
outline: none;
}
/* 100% Image Width on Smaller Screens */
@media only screen and (max-width: 700px){
.modal-content {
width: 100%;
}
}
================================================
FILE: examples/esp32-cam/esp32-cam.ino
================================================
#include <FS.h>
#include <SD_MMC.h>
#include <LittleFS.h>
#include <FSWebServer.h> // https://github.com/cotestatnt/esp-fs-webserver/
#include "esp_camera.h"
#include "soc/soc.h" // Brownout error fix
#include "soc/rtc_cntl_reg.h" // Brownout error fix
#if ESP_ARDUINO_VERSION_MAJOR >= 3
#include "soc/soc_caps.h"
#endif
// Local include files with camera pin definition and configuration
#include "camera_pins.h"
// Timezone definition to get properly time from NTP server
#define MYTZ "CET-1CEST,M3.5.0,M10.5.0/3"
// Set default file system type
#define USE_LITTLEFS 1
#define USE_SDMMC 2
#define FILESYSTEM_TYPE USE_LITTLEFS
#if (FILESYSTEM_TYPE == USE_SDMMC)
#define FILESYSTEM SD_MMC
#elif (FILESYSTEM_TYPE == USE_LITTLEFS)
#define FILESYSTEM LittleFS
#endif
FSWebServer server(FILESYSTEM, 80, "esp32cam");
// Functions prototype
void setInterval();
void getPicture();
// Struct for saving time datas (needed for time-naming the image files)
struct tm tInfo;
uint16_t grabInterval = 0; // Grab a picture every x seconds
uint32_t lastGrabTime = 0;
const char* getFolder = "/img";
/////////////////////////////////// SETUP ///////////////////////////////////////
void setup() {
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); // disable brownout detect
// Flash LED setup
pinMode(LAMP_PIN, OUTPUT); // set the lamp pin as output
#if ESP_ARDUINO_VERSION_MAJOR >= 3
// Nuova API: ledcAttach(pin, freq, resolution)
ledcAttach(LAMP_PIN, pwmfreq, pwmresolution);
#else
ledcSetup(lampChannel, pwmfreq, pwmresolution); // configure LED PWM channel
ledcAttachPin(LAMP_PIN, lampChannel);
#endif
setLamp(0); // set default value
Serial.begin(115200);
Serial.println();
// Try to connect to WiFi (will start AP if not connected after timeout)
if (!server.startWiFi(10000)) {
Serial.println("\nWiFi not connected! Starting AP mode...");
server.startCaptivePortal("ESP32CAM_AP", "123456789", "/setup");
}
// Sync time with NTP
configTzTime(MYTZ, "time.google.com", "time.windows.com", "pool.ntp.org");
#if (FILESYSTEM_TYPE == USE_SDMMC)
/*
Init onboard SD filesystem (format if necessary)
SD_MMC.begin(const char * mountpoint, bool mode1bit, bool format_if_mount_failed, int sdmmc_frequency, uint8_t maxOpenFiles)
To avoid led glowing, set mode1bit = true (SD HS_DATA1 is tied to GPIO4, the same of on-board flash led)
*/
if (!SD_MMC.begin("/sdcard", true, true, SDMMC_FREQ_HIGHSPEED, 5)) {
Serial.println("\nSD Mount Failed.\n");
}
else if (!SD_MMC.exists(getFolder)) {
if(SD_MMC.mkdir(getFolder))
Serial.println("Dir created");
else
Serial.println("mkdir failed");
}
#elif (FILESYSTEM_TYPE == USE_LITTLEFS)
if (!LittleFS.begin()) {
Serial.println("ERROR on mounting LittleFS. It will be formmatted!");
LittleFS.format();
ESP.restart();
}
else if (!LittleFS.exists(getFolder)) {
if(LittleFS.mkdir(getFolder))
Serial.println("Dir created");
else
Serial.println("mkdir failed");
}
Serial.println("LittleFS mounted successfully");
#endif
// List all files stored in filesystem
server.printFileList(FILESYSTEM, "/", 1, Serial);
// Enable ACE FS file web editor and add FS info callback function
server.enableFsCodeEditor();
// Add custom handlers to webserver
server.on("/getPicture", getPicture);
server.on("/setInterval", setInterval);
// Start server with built-in websocket event handler
server.begin();
Serial.print(F("\nESP Web Server started on IP Address: "));
Serial.println(server.getServerIP());
Serial.println(F(
"This is \"remoteOTA.ino\" example.\n"
"Open /setup page to configure optional parameters.\n"
"Open /edit page to view, edit or upload example or your custom webserver source files."
));
// Init the camera module (according the camera_config_t defined)
init_camera();
}
/////////////////////////////////// LOOP ///////////////////////////////////////
void loop() {
server.run();
if (grabInterval) {
if (millis() - lastGrabTime > grabInterval *1000) {
lastGrabTime = millis();
getPicture();
}
}
}
////////////////////////////////// FUNCTIONS//////////////////////////////////////
void setInterval() {
if (server.hasArg("val")) {
grabInterval = server.arg("val").toInt();
Serial.printf("Set grab interval every %d seconds\n", grabInterval);
}
server.send(200, "text/plain", "OK");
}
// Lamp Control
void setLamp(int newVal) {
if (newVal < 0) return;
// Apply exponential scaling to have a better visual effect
int brightness = round((pow(2, (1 + (newVal * 0.02))) - 2) / 6 * pwmMax);
#if ESP_ARDUINO_VERSION_MAJOR >= 3
// ledcWrite(pin, value) with new API
ledcWrite(LAMP_PIN, brightness);
#else
ledcWrite(lampChannel, brightness);
#endif
Serial.print("Lamp: ");
Serial.print(newVal);
Serial.print("%, pwm = ");
Serial.println(brightness);
}
// Send a picture taken from CAM to a Telegram chat
void getPicture() {
// Take Picture with Camera;
Serial.println("Camera capture requested");
// Take Picture with Camera and store in ram buffer fb
setLamp(100);
delay(100);
camera_fb_t *fb = esp_camera_fb_get();
setLamp(0);
if (!fb) {
Serial.println("Camera capture failed");
server.send(500, "text/plain", "ERROR. Image grab failed");
return;
}
// Keep files on SD memory, filename is time based (YYYYMMDD_HHMMSS.jpg)
// Embedded filesystem is too small to keep all images, overwrite the same file
char filename[20];
time_t now = time(nullptr);
tInfo = *localtime(&now);
strftime(filename, sizeof(filename), "%Y%m%d_%H%M%S.jpg", &tInfo);
char filePath[30];
strcpy(filePath, getFolder);
strcat(filePath, "/");
strcat(filePath, filename);
File file = FILESYSTEM.open(filePath, "w");
if (!file) {
Serial.println("Failed to open file in writing mode");
server.send(500, "text/plain", "ERROR. Image grab failed");
return;
}
// size_t _jpg_buf_len = 0;
// uint8_t *_jpg_buf = NULL;
// bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);
file.write(fb->buf, fb->len);
file.close();
Serial.printf("Saved file to path: %s - %zu bytes\n", filePath, fb->len);
// Clear buffer
esp_camera_fb_return(fb);
server.send(200, "text/plain", filename);
}
================================================
FILE: examples/esp32-cam/partitions.csv
================================================
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x140000,
app1, app, ota_1, 0x150000,0x140000,
spiffs, data, spiffs, 0x290000,0x160000,
coredump, data, coredump,0x3F0000,0x10000,
================================================
FILE: examples/esp32-cam/platformio.ini
================================================
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[platformio]
src_dir = .
# Questo esempio è compatibile solo con schede ESP32 dotate di CAM (ad es. AI Thinker, ESP32-CAM, TTGO T-Journal, ecc.)
# Scegli la board corretta e decommenta la sezione corrispondente, oppure aggiungi la tua board compatibile.
[env:esp32cam]
platform = espressif32
board = esp32cam
framework = arduino
upload_speed = 921600
board_build.partitions = partitions.csv
lib_extra_dirs = ../../
lib_ignore = pio_examples
# Esempio per AI Thinker (decommenta se necessario)
# [env:esp32cam-ai-thinker]
# platform = espressif32
# board = esp32cam
# framework = arduino
# upload_speed = 921600
# board_build.partitions = partitions.csv
# lib_extra_dirs = ../../
# lib_ignore = pio_examples
================================================
FILE: examples/gpio_list/.gitignore
================================================
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
================================================
FILE: examples/gpio_list/data/index.htm
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ESP GPIO dinamic list</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel=stylesheet href=/pico.classless.min.css>
<style>
body { margin: 40px; }
#container { max-width: 680px; margin: 0 auto; text-align: center; border-radius: var(--border-radius); }
button { max-width: 70%; }
td, th{ text-align: center; }
th { font-weight: bold; }
</style>
</head>
<body>
<div id="container">
<article class="grid">
<!--<img src="img/logo.svg" alt="Espressif logo" width="100">-->
<svg width=128px height=128px viewBox="0 0 420 420">
<g fill="#e7352c">
<path d="m131.967 298.073a28.186 28.186 0 0 1 -33.688 27.647 28.185 28.185 0 0 1 -22.147-22.147 28.19 28.19 0 0 1 47.579-25.432 28.19 28.19 0 0 1 8.256 19.932zm245.94-29.342a292.167 292.167 0 0 0 -247.602-247.602 185.334 185.334 0 0 0 -40.95 29.522v27.236a231.984 231.984 0 0 1 231.795 231.603h27.237a195.21 195.21 0 0 0 29.521-40.759z"/>
<path d="m399.961 183.228a183.219 183.219 0 0 0 -113.768-169.553 183.225 183.225 0 0 0 -70.41-13.673c-6.475 0-12.761 0-19.046.953l-4.19 12.19a315.224 315.224 0 0 1 193.13 192.939l12.189-4.381c0-6.095 1.143-12.19 1.143-19.046m-181.118 217.307a217.891 217.891 0 0 1 -217.89-216.938 216.367 216.367 0 0 1 63.805-154.085l12.38 11.619a201.127 201.127 0 0 0 0 285.695 201.133 201.133 0 0 0 285.695 0l11.619 11.618a216.184 216.184 0 0 1 -155.609 62.091z"/>
<path d="m215.438 322.074a126.65 126.65 0 0 0 -49.711-112.564 123.998 123.998 0 0 0 -63.805-25.903 11.615 11.615 0 0 1 -10.095-12.189 11.24 11.24 0 0 1 7.945-9.865 11.222 11.222 0 0 1 4.436-.42 149.516 149.516 0 0 1 133.324 163.417 130.44 130.44 0 0 1 -4.952 25.713l33.14 9.332a183.963 183.963 0 0 0 28.57-10.856 202.753 202.753 0 0 0 3.619-39.045 209.512 209.512 0 0 0 -177.702-206.843 92.199 92.199 0 0 0 -33.332 0 72.567 72.567 0 0 0 -35.997 22.284 71.234 71.234 0 0 0 31.807 114.278 164.882 164.882 0 0 0 17.904 3.238 66.856 66.856 0 0 1 55.615 65.9 66.09 66.09 0 0 1 -10.666 35.998l22.856 14.666a169.675 169.675 0 0 0 34.283 5.713 125.335 125.335 0 0 0 12.571-43.806"/>
</g>
</svg>
<h3 class="box">GPIO status list</h3>
<div class="box">
<table>
<thead>
<tr>
<th>Pin Name</th>
<th>Pin number</th>
<th>Type</th>
<th>Level</th>
</tr>
</thead>
<tbody id='gpio-list'>
<!-- The content of table body will be injected here with javascript function updateGpiosList() -->
</tbody>
</table>
</div>
</article>
</div>
<script src="script.js"></script>
</body>
</html>
================================================
FILE: examples/gpio_list/data/script.js
================================================
const svgLightOn = '<svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M20,11H23V13H20V11M1,11H4V13H1V11M13,1V4H11V1H13M4.92,3.5L7.05,5.64L5.63,7.05L3.5,4.93L4.92,3.5M16.95,5.63L19.07,3.5L20.5,4.93L18.37,7.05L16.95,5.63M12,6A6,6 0 0,1 18,12C18,14.22 16.79,16.16 15,17.2V19A1,1 0 0,1 14,20H10A1,1 0 0,1 9,19V17.2C7.21,16.16 6,14.22 6,12A6,6 0 0,1 12,6M14,21V22A1,1 0 0,1 13,23H11A1,1 0 0,1 10,22V21H14M11,18H13V15.87C14.73,15.43 16,13.86 16,12A4,4 0 0,0 12,8A4,4 0 0,0 8,12C8,13.86 9.27,15.43 11,15.87V18Z" /></svg>' ;
const svgLightOff = '<svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M12,2A7,7 0 0,0 5,9C5,11.38 6.19,13.47 8,14.74V17A1,1 0 0,0 9,18H15A1,1 0 0,0 16,17V14.74C17.81,13.47 19,11.38 19,9A7,7 0 0,0 12,2M9,21A1,1 0 0,0 10,22H14A1,1 0 0,0 15,21V20H9V21Z" /></svg>';
/**
* Custom selector "JQuery style", but in plain "Vanilla JS"
*/
var $ = function(el) {
return document.getElementById(el);
};
/**
* Start a websocket client and set event callback functions
*/
function ws_connect() {
var ws = new WebSocket('ws://' + location.hostname + ':81/');
ws.onopen = function() {
ws.send('Connected - ' + new Date());
getGpioList();
};
ws.onmessage = function(e) {
parseMessage(e.data);
};
ws.onclose = function(e) {
setTimeout(function() {
ws_connect();
}, 1000);
};
ws.onerror = function(err) {
ws.close();
};
return ws;
}
/**
* Send data "cmds" to ESP
*/
function sendCommand(cmd, pin, level) {
var data = {
cmd: cmd,
pin: parseInt(pin),
level: level
};
console.log(data);
connection.send(JSON.stringify(data));
}
/**
* Parse messages receveid via websocket
*/
function parseMessage(msg) {
const obj = JSON.parse(msg);
if (typeof obj === 'object' && obj !== null) {
if (obj.esptime !== null) {
var date = new Date(0); // The 0 sets the date to epoch
if( date.setUTCSeconds(obj.esptime))
document.getElementById("esp-time").innerHTML = date;
}
updateGpiosList(obj);
}
}
/**
* Read GPIO list status
*/
function getGpioList() {
fetch('/getGpioList') // Do the request
.then(response => response.json()) // Parse the response
.then(obj => { // DO something with response
updateGpiosList(obj);
});
}
/**
* Iterate to the gpio list passed as parameter anc create DOMs dinamically
*/
function updateGpiosList(elems) {
// Get reference to gpio-list element and clear content
const list = document.querySelector('#gpio-list');
list.innerHTML = "";
// Draw all input rows
const inputs = Object.entries(elems).filter((item) => item[1].type === 'input');
inputs.forEach(el => {
const obj = el[1];
var lbl = obj.level ? `${svgLightOn} HIGH` : `${svgLightOff} LOW`;
// Create a single row with all columns
var row = document.createElement('tr');
row.innerHTML = '<td>' + obj.label + '</td>';
row.innerHTML += '<td>' + obj.pin + '</td>';
row.innerHTML += '<td style="text-align:center;">' + obj.type + '</td>';
row.innerHTML += `<td style="text-align:center;"><label id="pin-${obj}" for="">${lbl}</label></td>` ;
// Append this row to list
list.appendChild(row);
});
// Draw all output rows
const outputs = Object.entries(elems).filter((item) => item[1].type === 'output');
outputs.forEach(el => {
const obj = el[1];
var lbl = obj.level ? ` checked>Turn OFF` : `>Turn ON`;
// Create a single row with all columns
var row = document.createElement('tr');
row.innerHTML = '<td>' + obj.label + '</td>';
row.innerHTML += '<td>' + obj.pin + '</td>';
row.innerHTML += '<td style="text-align:center;">' + obj.type + '</td>';
row.innerHTML += `<td><label id="lbl-${obj.pin}" for=""><input type="checkbox" id="pin-${obj.pin}" role="switch" data-pin=${obj.pin}${lbl}</label></td>`;
// Append this row to list
list.appendChild(row);
});
// Add event listener to each out switch
const outSwitches = document.querySelectorAll('input[type="checkbox"]');
outSwitches.forEach( (outSw) => {
outSw.addEventListener('change', function() {
sendCommand('writeOut', this.dataset.pin, this.checked);
});
});
}
var connection = ws_connect();
window.addEventListener('load', getGpioList);
================================================
FILE: examples/gpio_list/gpio_list.ino
================================================
#include <FS.h>
#include <LittleFS.h>
#include <FSWebServer.h> // https://github.com/cotestatnt/esp-fs-webserver/
#define FILESYSTEM LittleFS
FSWebServer server(FILESYSTEM, 80);
#ifndef LED_BUILTIN
#define LED_BUILTIN 2 // Pin for built-in LED on ESP32
#endif
#ifndef BOOT_PIN
#define BOOT_PIN 0 // Pin for BOOT button on ESP32
#endif
// Define a struct for store all info about each gpio
struct gpio_type {
const char* type;
const char* label;
int pin;
bool level;
};
// Define an array of struct GPIO and initialize with values
/* ESP8266 - Wemos D1-mini */
/*
gpio_type gpios[] = {
{"input", "INPUT 5", D5},
{"input", "INPUT 6", D6},
{"input", "INPUT 7", D7},
{"output", "OUTPUT 2", D2},
{"output", "OUTPUT 3", D3},
{"output", "LED BUILTIN", LED_BUILTIN} // Led ON with signal LOW usually
};
*/
/* ESP32 - NodeMCU-32S */
gpio_type gpios[] = {
{"input", "INPUT 0", BOOT_PIN},
{"input", "INPUT 1", 19},
{"input", "INPUT 2", 21},
{"output", "OUTPUT 1", 4},
{"output", "OUTPUT 2", 5},
{"output", "LED BUILTIN", LED_BUILTIN} // Led ON with signal LOW usually
};
///////////////////////// WebSocket event callback//////////////////////////////// WebSocket Handler /////////////////////////////
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
switch (type) {
case WStype_DISCONNECTED:
Serial.printf("[%u] Disconnected!\n", num);
break;
case WStype_CONNECTED:{
IPAddress ip = server.getWebSocketServer()->remoteIP(num);
server.getWebSocketServer()->sendTXT(num, "{\"Connected\": true}");
Serial.printf("Hello client #%d [%s]\n", (int)num, ip.toString().c_str());
}
break;
case WStype_TEXT:
Serial.printf("[%u] got Text: %s\n", num, payload); // Got text message from a client
if (payload[0] == '{')
parseMessage((const char*)payload);
break;
case WStype_BIN:
Serial.printf("[%u] got binary length: %u\n", num, length); // Got binary message from a client
break;
default:
break;
}
}
void parseMessage(const String json) {
CJSON::Json doc;
if (doc.parse(json)) {
String cmd;
if (doc.getString("cmd", cmd)) {
// If this is a "writeOut" command, set the pin level to value
if (cmd == "writeOut") {
// getNumber returns double, so use double and cast to int
double pin, level;
if (doc.getNumber("pin", pin) && doc.getNumber("level", level)) {
// Find the gpio in the array and set the level
for (gpio_type &gpio : gpios) {
if (gpio.pin == (int)pin) {
Serial.printf("Set pin %d to %d\n", (int)pin, (int)level);
gpio.level = (int)level;
digitalWrite((int)pin, (int)level);
// Update all clients with new gpio list
updateGpioList();
return;
}
}
}
}
}
} else {
Serial.println(F("Failed to parse JSON message"));
}
}
void updateGpioList() {
// Build JSON array using CJSON::Json with nesting support
CJSON::Json doc;
doc.createArray();
// Iterate the array of GPIO struct and add each as JSON object
for (gpio_type &gpio : gpios) {
CJSON::Json item;
item.createObject();
item.setString("type", String(gpio.type));
item.setNumber("pin", gpio.pin);
item.setString("label", String(gpio.label));
item.setBool("level", gpio.level);
doc.add(item);
}
// Serialize JSON document to string
String json = doc.serialize();
// Update client via websocket
server.broadcastWebSocket(json);
server.send(200, "text/plain", json);
}
bool updateGpioState() {
// Iterate the array of GPIO struct and check level of inputs
for (gpio_type &gpio : gpios) {
if (strcmp(gpio.type, "input") == 0) {
// Input value != from last read
if (digitalRead(gpio.pin) != gpio.level) {
gpio.level = digitalRead(gpio.pin);
return true;
}
}
}
return false;
}
void setup() {
Serial.begin(115200);
// FILESYSTEM initialization
if (!FILESYSTEM.begin()) {
Serial.println("ERROR on mounting filesystem.");
//FILESYSTEM.format();
ESP.restart();
}
// Try to connect to WiFi (will start AP if not connected after timeout)
if (!server.startWiFi(10000)) {
Serial.println("\nWiFi not connected! Starting AP mode...");
server.startCaptivePortal("ESP_AP", "123456789", "/setup");
}
// Enable ACE FS file web editor and add FS info callback function
server.enableFsCodeEditor();
/*
* Getting FS info (total and free bytes) is strictly related to
* filesystem library used (LittleFS, FFat, SPIFFS etc etc) and ESP framework
*/
#ifdef ESP32
server.setFsInfoCallback([](fsInfo_t* fsInfo) {
fsInfo->fsName = "LittleFS";
fsInfo->totalBytes = LittleFS.totalBytes();
fsInfo->usedBytes = LittleFS.usedBytes();
});
#endif
// Add custom page handlers
server.on("/getGpioList", HTTP_GET, [](){ updateGpioList(); });
// Start server with custom websocket event handler
server.begin(webSocketEvent);
Serial.print(F("ESP Web Server started on IP Address: "));
Serial.println(server.getServerIP());
Serial.println(F(
"This is \"gpio_list.ino\" example.\n"
"Open /setup page to configure optional parameters.\n"
"Open /edit page to view, edit or upload example or your custom webserver source files."
));
// GPIOs configuration
for (gpio_type &gpio : gpios) {
if (strcmp(gpio.type, "input") == 0)
pinMode(gpio.pin, INPUT_PULLUP);
else
pinMode(gpio.pin, OUTPUT);
}
}
void loop() {
server.run(); // Handle client requests
// True on pin state change
if (updateGpioState()) {
updateGpioList(); // Push new state to web clients via websocket
}
}
================================================
FILE: examples/gpio_list/partitions.csv
================================================
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x140000,
app1, app, ota_1, 0x150000,0x140000,
spiffs, data, spiffs, 0x290000,0x160000,
coredump, data, coredump,0x3F0000,0x10000,
================================================
FILE: examples/gpio_list/platformio.ini
================================================
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[platformio]
src_dir = .
[env:esp32-s3-devkitc1-n4r2]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip
board = esp32-s3-devkitc1-n4r2
framework = arduino
upload_speed = 921600
board_build.partitions = partitions.csv
lib_extra_dirs = ../../
lib_ignore = pio_examples
[env:esp32dev]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip
board = esp32dev
framework = arduino
upload_speed = 921600
board_build.partitions = partitions.csv
lib_extra_dirs = ../../
lib_ignore = pio_examples
[env:esp8266-nodemcuv2]
platform = espressif8266
board = nodemcuv2
framework = arduino
upload_speed = 921600
board_build.partitions = partitions.csv
lib_extra_dirs = ../../
lib_ignore = pio_examples
================================================
FILE: examples/gpio_list/readme.md
================================================
In this example the content of webpage will be created at runtime with Javascript depending from a JSON list of gpios (pin number, pin label, type, actual state) received from ESP device.

================================================
FILE: examples/handleFormData/.gitignore
================================================
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
================================================
FILE: examples/handleFormData/data/index.htm
================================================
<!DOCTYPE html>
<html>
<head>
<style>
.container {
box-shadow:
0 0.125rem 1rem rgba(27, 40, 50, 0.04),
0 0.125rem 2rem rgba(27, 40, 50, 0.08),
0 0 0 0.0625rem rgba(27, 40, 50, 0.024);
border-radius: 5px;
box-sizing: border-box;
width: 100%;
margin: auto;
max-width: 860px;
padding: 40px;
}
h3 {
text-align: center;
}
.collapsible {
background-color: #777;
color: white;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
max-width: 800px;
}
.collapsible:after {
content: '\002B'; /* The '+' character*/
color: white;
font-weight: bold;
float: right;
margin-left: 5px;
}
.content {
padding: 0 15px;
max-height: 0;
overflow: hidden;
transition: max-height 0.4s ease-out;
background-color: #f1f1f1;
border-bottom: solid 1px #f1f1f1;
}
p{
margin-left: 10px;
padding: 5px;
}
/* Form button styling */
input, select {
border-radius: 5px;
border: solid 1px #ccc;
padding: 10px 20px;
cursor: pointer;
margin-top: 5px;
}
input[type=submit] {
background-color: green;
color: white;
}
input[type=submit]:hover {
background-color: #45a049;
border: solid 1px #000;
}
label {
text-align: right;
margin: auto 0 auto 0;
}
form {
margin-top: 10px;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 20px;
}
</style>
</head>
<body>
<div class="container">
<h3>Collapsible form option menu</h3>
<button class="collapsible">Open Section 1</button>
<div class="content" style="max-height: 90px;">
<p>This example show how to handle a form without reload web page on submit.</p>
<p>In addition show a way to build a nice collapsible menu with CSS and JS</p>
</div>
<button class="collapsible">Open Section 2</button>
<div class="content">
<!-- With HTML 5 we can use custom element attributes, so keep track where we want the result of form submit -->
<!-- By default, HTML forms will send to request to the action link with GET method -->
<form action="/setForm1" data-result="form1-result" enctype="text/plain" method="post">
<label for="cars">Choose a car brand:</label>
<select id="cars" name="cars">
<option value="Alfa Romeo">Alfa Romeo</option>
<option value="Ferrari">Ferrari</option>
<option value="Lamborghini">Lamborghini</option>
<option value="Maserati">Maserati</option>
</select>
<input type="submit">
</form>
<br><br>
<p id="form1-result"></p>
</div>
<button class="collapsible">Open Section 3</button>
<div class="content">
<!-- With HTML 5 we can use custom element attributes, so keep track where we want the result of form submit -->
<form action="/setForm2" data-result="form2-result" enctype="text/plain" method="post">
<p><label for="fname">Firstname</label><input type="text" id="fname" name="firstname" placeholder="Your name.."></p>
<p><label for="lname">Lastname</label><input type="text" id="lname" name="lastname" placeholder="Your last name.."></p>
<p><label for="age">Your Age</label><input type="number" id="age" name="age"></p>
<br>
<input type="submit">
</form>
<br><br>
<p id="form2-result"></p>
</div>
</div>
<!-- Load document Javascript -->
<script>
// Expand or collapse the content according to current state
function expandCollapse() {
// Get the HTML element immediately following the button (content)
var content = this.nextElementSibling;
if (content.style.maxHeight)
content.style.maxHeight = null;
else
content.style.maxHeight = content.scrollHeight + "px";
}
// Select all "+" HTML buttons in webpage and add a listener for each one
const btnList = document.querySelectorAll(".collapsible");
btnList.forEach(elem => {
elem.addEventListener("click", expandCollapse ); // Set callback function linked to the button click
});
// This listener will execute an "arrow" function once the page was fully loaded
document.addEventListener("DOMContentLoaded", () => {
console.log('Webpage is fully loaded');
// At first, get the default values for form input elements from ESP
fetch('/getDefault')
.then(response => response.json()) // Parse the server response
.then(jsonObj => { // Do something with parsed response
console.log(jsonObj);
document.getElementById('cars').value = jsonObj.car;
document.getElementById('fname').value = jsonObj.firstname;
document.getElementById('lname').value = jsonObj.lastname;
document.getElementById('age').value = jsonObj.age;
});
});
// This listener will prevent each form to reload page after submitting data
document.addEventListener("submit", (e) => {
const form = e.target; // Store reference to form to make later code easier to read
fetch(form.action, { // Send form data to server using the Fetch API
method: form.method,
body: new FormData(form),
})
.then(response => response.text()) // Parse the server response
.then(text => { // Do something with parsed response
console.log(text);
const resEl = document.getElementById(form.dataset.result).innerHTML= text;
});
e.preventDefault(); // Prevent the default form submit wich reload page
});
</script>
</body>
</html>
================================================
FILE: examples/handleFormData/data/myScript.js
================================================
// Expand or collapse the content according to current state
function expandCollapse() {
// Get the HTML element immediately following the button (content)
var content = this.nextElementSibling;
if (content.style.maxHeight)
content.style.maxHeight = null;
else
content.style.maxHeight = content.scrollHeight + "px";
}
// Select all "+" HTML buttons in webpage and add a listener for each one
const btnList = document.querySelectorAll(".collapsible");
btnList.forEach(elem => {
elem.addEventListener("click", expandCollapse ); // Set callback function linked to the button click
});
// This listener will execute an "arrow" function once the page was fully loaded
document.addEventListener("DOMContentLoaded", () => {
console.log('Webpage is fully loaded');
// At first, get the default values for form input elements from ESP
fetch('/getDefault')
.then(response => response.json()) // Parse the server response
.then(jsonObj => { // Do something with parsed response
console.log(jsonObj);
document.getElementById('cars').value = jsonObj.car;
document.getElementById('fname').value = jsonObj.firstname;
document.getElementById('lname').value = jsonObj.lastname;
document.getElementById('age').value = jsonObj.age;
});
});
// This listener will prevent each form to reload page after submitting data
document.addEventListener("submit", (e) => {
const form = e.target; // Store reference to form to make later code easier to read
fetch(form.action, { // Send form data to server using the Fetch API
method: form.method,
body: new FormData(form),
})
.then(response => response.text()) // Parse the server response
.then(text => { // Do something with parsed response
console.log(text);
const resEl = document.getElementById(form.dataset.result).innerHTML= text;
});
e.preventDefault(); // Prevent the default form submit wich reload page
});
////////////////////////////////////////////////////////////////////////////////////////////
================================================
FILE: examples/handleFormData/data/myStyle.css
================================================
.container {
box-shadow:
0 0.125rem 1rem rgba(27, 40, 50, 0.04),
0 0.125rem 2rem rgba(27, 40, 50, 0.08),
0 0 0 0.0625rem rgba(27, 40, 50, 0.024);
border-radius: 5px;
box-sizing: border-box;
width: 100%;
margin: auto;
max-width: 860px;
padding: 40px;
}
h3 {
text-align: center;
}
.collapsible {
background-color: #777;
color: white;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
max-width: 800px;
}
.collapsible:after {
content: '\002B'; /* The '+' character*/
color: white;
font-weight: bold;
float: right;
margin-left: 5px;
}
.content {
padding: 0 15px;
max-height: 0;
overflow: hidden;
transition: max-height 0.4s ease-out;
background-color: #f1f1f1;
border-bottom: solid 1px #f1f1f1;
}
p{
margin-left: 10px;
padding: 5px;
}
/* Form button styling */
input, select {
border-radius: 5px;
border: solid 1px #ccc;
padding: 10px 20px;
cursor: pointer;
margin-top: 5px;
}
input[type=submit] {
background-color: green;
color: white;
}
input[type=submit]:hover {
background-color: #45a049;
border: solid 1px #000;
}
label {
text-align: right;
margin: auto 0 auto 0;
}
form {
margin-top: 10px;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 20px;
}
================================================
FILE: examples/handleFormData/handleFormData.ino
================================================
#include <FS.h>
#include <LittleFS.h>
#include <FSWebServer.h> // https://github.com/cotestatnt/esp-fs-webserver/
#define FILESYSTEM LittleFS
FSWebServer server(LittleFS, 80, "esphost");
//////////////////////////// HTTP Request Handlers ////////////////////////////////////
void getDefaultValue () {
// Send to client default values as JSON string because it's very easy to parse JSON in Javascript
String defaultVal = "{\"car\":\"Ferrari\", \"firstname\":\"Enzo\", \"lastname\":\"Ferrari\",\"age\":90}";
server.send(200, "text/json", defaultVal);
}
void handleForm1() {
String reply;
if(server.hasArg("cars")) {
reply += "You have submitted with Form1: ";
reply += server.arg("cars");
}
Serial.println(reply);
server.send(200, "text/plain", reply);
}
void handleForm2() {
String reply;
if(server.hasArg("firstname")) {
reply += "You have submitted with Form2: ";
reply += server.arg("firstname");
}
if(server.hasArg("lastname")) {
reply += " ";
reply += server.arg("lastname");
}
if(server.hasArg("age")) {
reply += ", age: ";
reply += server.arg("age");
}
Serial.println(reply);
server.send(200, "text/plain", reply);
}
//////////////////////////////// Filesystem /////////////////////////////////////////
bool startFilesystem() {
if (FILESYSTEM.begin()){
server.printFileList(LittleFS, "/", 1, Serial);
return true;
}
else {
Serial.println("ERROR on mounting filesystem. It will be reformatted!");
FILESYSTEM.format();
ESP.restart();
}
return false;
}
void setup(){
Serial.begin(115200);
// FILESYSTEM INIT
startFilesystem();
// Try to connect to WiFi (will start AP if not connected after timeout)
if (!server.startWiFi(10000)) {
Serial.println("\nWiFi not connected! Starting AP mode...");
server.startCaptivePortal("ESP_AP", "123456789", "/setup");
}
// Add custom page handlers to webserver
server.on("/getDefault", HTTP_GET, getDefaultValue);
server.on("/setForm1", HTTP_POST, handleForm1);
server.on("/setForm2", HTTP_POST, handleForm2);
// Enable ACE FS file web editor and add FS info callback function
server.enableFsCodeEditor();
// Start server
server.begin();
Serial.print(F("ESP Web Server started on IP Address: "));
Serial.println(server.getServerIP());
Serial.println(F(
"This is \"handleFormData.ino\" example.\n"
"Open /setup page to configure optional parameters.\n"
"Open /edit page to view, edit or upload example or your custom webserver source files."
));
}
void loop() {
// Handle client requests
server.run();
// Nothing to do here, just a small delay
delay(10);
}
================================================
FILE: examples/handleFormData/partitions.csv
================================================
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x140000,
app1, app, ota_1, 0x150000,0x140000,
spiffs, data, spiffs, 0x290000,0x160000,
coredump, data, coredump,0x3F0000,0x10000,
================================================
FILE: examples/handleFormData/platformio.ini
================================================
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[platformio]
src_dir = .
[env:esp32-s3-devkitc1-n4r2]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip
board = esp32-s3-devkitc1-n4r2
framework = arduino
upload_speed = 921600
monitor_speed = 115200
board_build.partitions = partitions.csv
lib_extra_dirs = ../../
lib_ignore =
gitextract_673knega/
├── .github/
│ └── workflows/
│ ├── clean_workflow.yml
│ ├── cli-build-esp32-dev.yml
│ ├── cli-build-esp8266.yml
│ ├── pio-build-esp32-dev.yml
│ └── pio-build-esp8266.yml
├── .gitignore
├── LICENSE
├── README.md
├── built-in-webpages/
│ └── readme.md
├── docs/
│ ├── API.md
│ ├── FileEditorAndFS.md
│ ├── SetupAndWiFi.md
│ ├── WebSocket.md
│ ├── pwd_encrypt.md
│ └── readme.md
├── examples/
│ ├── csvLogger/
│ │ ├── .gitignore
│ │ ├── csvLogger.ino
│ │ ├── data/
│ │ │ ├── assets/
│ │ │ │ ├── css/
│ │ │ │ │ ├── index.css
│ │ │ │ │ └── style.css
│ │ │ │ └── js/
│ │ │ │ ├── csv.js
│ │ │ │ └── index.js
│ │ │ ├── csv/
│ │ │ │ └── 2024_01_10.csv
│ │ │ └── index.htm
│ │ ├── partitions.csv
│ │ ├── platformio.ini
│ │ └── readme.md
│ ├── csvLoggerSD/
│ │ ├── csvLoggerSD.ino
│ │ ├── data/
│ │ │ ├── assets/
│ │ │ │ ├── css/
│ │ │ │ │ ├── index.css
│ │ │ │ │ └── style.css
│ │ │ │ └── js/
│ │ │ │ ├── csv.js
│ │ │ │ └── index.js
│ │ │ └── index.htm
│ │ ├── partitions.csv
│ │ ├── platformio.ini
│ │ └── readme.md
│ ├── customHTML/
│ │ ├── customElements.h
│ │ ├── customHTML.ino
│ │ └── thingsboard.h
│ ├── customOptions/
│ │ ├── .gitignore
│ │ ├── customOptions.ino
│ │ ├── partitions.csv
│ │ └── platformio.ini
│ ├── esp32-cam/
│ │ ├── .gitignore
│ │ ├── camera_pins.h
│ │ ├── data/
│ │ │ ├── index.htm
│ │ │ └── www/
│ │ │ ├── app.js
│ │ │ ├── index.htm
│ │ │ └── styles.css
│ │ ├── esp32-cam.ino
│ │ ├── partitions.csv
│ │ └── platformio.ini
│ ├── gpio_list/
│ │ ├── .gitignore
│ │ ├── data/
│ │ │ ├── index.htm
│ │ │ └── script.js
│ │ ├── gpio_list.ino
│ │ ├── partitions.csv
│ │ ├── platformio.ini
│ │ └── readme.md
│ ├── handleFormData/
│ │ ├── .gitignore
│ │ ├── data/
│ │ │ ├── index.htm
│ │ │ ├── myScript.js
│ │ │ └── myStyle.css
│ │ ├── handleFormData.ino
│ │ ├── partitions.csv
│ │ └── platformio.ini
│ ├── leanWebserver/
│ │ ├── .gitignore
│ │ ├── data/
│ │ │ └── index.htm
│ │ ├── leanWebserver.ino
│ │ ├── partitions.csv
│ │ └── platformio.ini
│ ├── localRFID/
│ │ ├── .gitignore
│ │ ├── JsonDB.hpp
│ │ ├── data/
│ │ │ ├── html_login.h
│ │ │ ├── html_rfid.h
│ │ │ ├── login
│ │ │ └── rfid
│ │ ├── localRFID.ino
│ │ ├── partitions.csv
│ │ ├── platformio.ini
│ │ ├── readme.md
│ │ └── webserver.hpp
│ ├── mqtt_webserver/
│ │ ├── .gitignore
│ │ ├── mqtt_webserver.ino
│ │ ├── partitions.csv
│ │ ├── platformio.ini
│ │ └── readme.md
│ ├── mysqlRFID/
│ │ ├── .gitignore
│ │ ├── html_flash_files.h
│ │ ├── mysqlRFID.ino
│ │ ├── mysql_impl.h
│ │ ├── partitions.csv
│ │ ├── platformio.ini
│ │ └── webserver_impl.h
│ ├── remoteOTA/
│ │ ├── data/
│ │ │ └── index.htm
│ │ ├── fw-esp32/
│ │ │ └── readme.md
│ │ ├── fw-esp8266/
│ │ │ └── readme.md
│ │ ├── partitions.csv
│ │ ├── platformio.ini
│ │ ├── remoteOTA.ino
│ │ ├── version-esp32.json
│ │ └── version-esp8266.json
│ ├── simpleServer/
│ │ ├── data/
│ │ │ └── index.htm
│ │ └── simpleServer.ino
│ ├── websocketEcharts/
│ │ ├── README.md
│ │ ├── data/
│ │ │ └── index.htm
│ │ └── websocketEcharts.ino
│ └── withWebSocket/
│ ├── .gitignore
│ ├── index_htm.h
│ ├── partitions.csv
│ ├── platformio.ini
│ ├── readme.md
│ └── withWebSocket.ino
├── keywords.txt
├── library.properties
├── partitions.csv
├── pio_examples/
│ ├── customOptions/
│ │ ├── .gitignore
│ │ ├── customOptions.code-workspace
│ │ ├── platformio.ini
│ │ └── src/
│ │ └── customOptions.ino
│ ├── esp32-p4/
│ │ ├── .gitignore
│ │ ├── app3M_spiffs9M_16MB.csv
│ │ ├── platformio.ini
│ │ └── src/
│ │ ├── index_htm.h
│ │ └── withWebSocket.cpp
│ ├── leanWebserver/
│ │ ├── .gitignore
│ │ ├── data/
│ │ │ └── index.htm
│ │ ├── partitions.csv
│ │ ├── platformio.ini
│ │ └── src/
│ │ └── leanWebserver.ino
│ ├── simpleServer/
│ │ ├── .gitignore
│ │ ├── compile_commands.json
│ │ ├── partitions.csv
│ │ ├── platformio.ini
│ │ └── src/
│ │ └── simpleServer.ino
│ ├── websocketEcharts/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── data/
│ │ │ ├── index.htm
│ │ │ └── index.html
│ │ ├── platformio.ini
│ │ └── src/
│ │ └── websocketEcharts.ino
│ └── withWebSocket/
│ ├── .gitignore
│ ├── partitions.csv
│ ├── platformio.ini
│ ├── readme.md
│ └── src/
│ ├── index_htm.h
│ └── withWebSocket.ino
├── platformio.ini
└── src/
├── ConfigUpgrader.hpp
├── CredentialManager.cpp
├── CredentialManager.h
├── FSWebServer.cpp
├── FSWebServer.h
├── Json.cpp
├── Json.h
├── SerialLog.h
├── SetupConfig.hpp
├── Version.h
├── WiFiService.cpp
├── WiFiService.h
├── assets/
│ ├── edit_htm.h
│ ├── logo_svg.h
│ └── setup_htm.h
├── compat/
│ └── mbedtls_aes.h
├── esp-fs-webserver.h
├── json/
│ ├── cJSON.c
│ └── cJSON.h
├── mimetable/
│ ├── mimetable.cpp
│ └── mimetable.h
└── websocket/
├── SocketIOclient.cpp
├── SocketIOclient.h
├── WebSockets.cpp
├── WebSockets.h
├── WebSocketsClient.cpp
├── WebSocketsClient.h
├── WebSocketsServer.cpp
├── WebSocketsServer.h
├── libb64/
│ ├── AUTHORS
│ ├── LICENSE
│ ├── cdecode.c
│ ├── cdecode_inc.h
│ ├── cencode.c
│ └── cencode_inc.h
└── libsha1/
├── libsha1.c
└── libsha1.h
SYMBOL INDEX (357 symbols across 45 files)
FILE: examples/csvLogger/data/assets/js/csv.js
function getUserInput (line 16) | function getUserInput() {
function getTableUnit (line 41) | function getTableUnit(title, tableHtml){
function clearTables (line 53) | function clearTables(){
function addTableUnit (line 64) | function addTableUnit(unit){
function saveTable (line 69) | function saveTable(filename, text) {
function downloadTable (line 105) | function downloadTable(table, save = false) {
function download (line 144) | function download(filename, text) {
function populate (line 159) | function populate(csv){
function loadCsv (line 194) | function loadCsv(path) {
function customParseFloat (line 211) | function customParseFloat(strNumber){
function getTable (line 236) | function getTable(tableArray, useHeaders, dupeHeaders, tableId){
function csvTo2DArray (line 286) | function csvTo2DArray(csv, separator, quotes, maxRows){
function getRow (line 317) | function getRow(row, separator, quotes){
FILE: examples/csvLogger/data/assets/js/index.js
function listFiles (line 6) | function listFiles() {
function loadFile (line 23) | function loadFile(filename) {
function deleteFile (line 28) | async function deleteFile(filename) {
function deleteAll (line 42) | async function deleteAll() {
function addEntry (line 55) | function addEntry(entryName) {
FILE: examples/csvLoggerSD/data/assets/js/csv.js
function getUserInput (line 16) | function getUserInput() {
function getTableUnit (line 41) | function getTableUnit(title, tableHtml){
function clearTables (line 53) | function clearTables(){
function addTableUnit (line 64) | function addTableUnit(unit){
function saveTable (line 69) | function saveTable(filename, text) {
function downloadTable (line 105) | function downloadTable(table, save = false) {
function download (line 144) | function download(filename, text) {
function populate (line 159) | function populate(csv){
function loadCsv (line 194) | function loadCsv(path) {
function customParseFloat (line 211) | function customParseFloat(strNumber){
function getTable (line 236) | function getTable(tableArray, useHeaders, dupeHeaders, tableId){
function csvTo2DArray (line 286) | function csvTo2DArray(csv, separator, quotes, maxRows){
function getRow (line 317) | function getRow(row, separator, quotes){
FILE: examples/csvLoggerSD/data/assets/js/index.js
function listFiles (line 6) | function listFiles() {
function loadFile (line 23) | function loadFile(filename) {
function deleteFile (line 28) | async function deleteFile(filename) {
function deleteAll (line 42) | async function deleteAll() {
function addEntry (line 55) | function addEntry(entryName) {
FILE: examples/customHTML/thingsboard.h
function function (line 98) | function setDeviceClientAttribute(){
FILE: examples/esp32-cam/camera_pins.h
function esp_err_t (line 263) | inline esp_err_t init_camera() {
FILE: examples/esp32-cam/data/www/app.js
function listFiles (line 14) | function listFiles() {
function loadFile (line 26) | function loadFile(filename) {
function deleteFile (line 58) | async function deleteFile(filename) {
function deleteAll (line 71) | async function deleteAll() {
function addEntry (line 85) | function addEntry(entryName) {
FILE: examples/gpio_list/data/script.js
function ws_connect (line 14) | function ws_connect() {
function sendCommand (line 37) | function sendCommand(cmd, pin, level) {
function parseMessage (line 50) | function parseMessage(msg) {
function getGpioList (line 65) | function getGpioList() {
function updateGpiosList (line 77) | function updateGpiosList(elems) {
FILE: examples/handleFormData/data/myScript.js
function expandCollapse (line 2) | function expandCollapse() {
FILE: examples/localRFID/JsonDB.hpp
class TableManager (line 7) | class TableManager {
method TableManager (line 9) | TableManager(const char* filename) : filename(filename) {
method loadTable (line 15) | bool loadTable() {
method isDuplicate (line 36) | bool isDuplicate(JsonObject& newRecord, const char* uniqueKey) {
method addRecord (line 51) | bool addRecord(JsonObject newRecord, const char* uniqueKey = nullptr) {
method addRecord (line 65) | bool addRecord(JsonObject newRecord, const char* uniqueKeys[], int num...
method deleteRecord (line 80) | bool deleteRecord(const char* key, const char* value) {
method JsonObject (line 95) | JsonObject findRecord(const char* key, const char* value) {
method JsonArray (line 105) | JsonArray getUsers() {
method saveTable (line 111) | bool saveTable() {
method printTable (line 121) | void printTable() {
FILE: examples/localRFID/webserver.hpp
function String (line 16) | String getSHA256(const char* payload) {
function getUserLevel (line 35) | int getUserLevel(const String& username, const String& hash) {
function handleGetUsers (line 45) | void handleGetUsers() {
function handleNewUser (line 52) | void handleNewUser() {
function handleRemoveUser (line 83) | void handleRemoveUser() {
function handleGetCode (line 91) | void handleGetCode() {
function handleCheckHash (line 124) | void handleCheckHash() {
function handleMainPage (line 137) | void handleMainPage() {
function startWebServer (line 162) | bool startWebServer(bool clear = false) {
FILE: examples/mysqlRFID/html_flash_files.h
function keyframes (line 198) | keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rota...
function function (line 371) | function tabClick(el) {
function function (line 383) | function toggleCollapse(id, keep) {
function function (line 401) | function getLogs(filter) {
function function (line 442) | function getTagCode() {
function function (line 460) | function getUsers() {
function function (line 514) | function readTagCode() {
function function (line 522) | function sendUserForm(url) {
function function (line 550) | function deleteUser() {
function function (line 556) | function openModal(title, msg, fn) {
function function (line 570) | function closeModal(do_cb) {
function function (line 577) | function getRowTimestamp(id) {
function function (line 585) | function customFilter() {
function function (line 632) | function getUserLevel() {
FILE: examples/mysqlRFID/mysql_impl.h
function queryExecute (line 68) | bool queryExecute(DataQuery_t& data, const char* queryStr, ...) {
function checkAndCreateTables (line 83) | bool checkAndCreateTables() {
FILE: examples/mysqlRFID/webserver_impl.h
function FSWebServer (line 15) | FSWebServer myWebServer(LittleFS, 80, "esp32rfid");
function handleCheckHash (line 170) | void handleCheckHash() {
function handleMainPage (line 182) | void handleMainPage() {
function loadOptions (line 202) | bool loadOptions() {
FILE: examples/withWebSocket/index_htm.h
function function (line 24) | function ge(s){ return document.getElementById(s);}
function function (line 25) | function ce(s){ return document.createElement(s);}
function function (line 26) | function stb(){ window.scrollTo(0, document.body.scrollHeight || documen...
function function (line 29) | function addMessage(m){
function function (line 37) | function parseMessage(msg) {
FILE: pio_examples/esp32-p4/src/index_htm.h
function function (line 24) | function ge(s){ return document.getElementById(s);}
function function (line 25) | function ce(s){ return document.createElement(s);}
function function (line 26) | function stb(){ window.scrollTo(0, document.body.scrollHeight || documen...
function function (line 29) | function addMessage(m){
function function (line 37) | function parseMessage(msg) {
FILE: pio_examples/esp32-p4/src/withWebSocket.cpp
function wsLogPrintf (line 19) | void wsLogPrintf(bool toSerial, const char* format, ...) {
function webSocketEvent (line 31) | void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_...
type tm (line 61) | struct tm
function getUpdatedtime (line 65) | void getUpdatedtime(const uint32_t timeout) {
function startFilesystem (line 78) | bool startFilesystem() {
function loadApplicationConfig (line 92) | bool loadApplicationConfig() {
function setup (line 103) | void setup() {
function loop (line 163) | void loop() {
FILE: pio_examples/withWebSocket/src/index_htm.h
function function (line 24) | function ge(s){ return document.getElementById(s);}
function function (line 25) | function ce(s){ return document.createElement(s);}
function function (line 26) | function stb(){ window.scrollTo(0, document.body.scrollHeight || documen...
function function (line 29) | function addMessage(m){
function function (line 37) | function parseMessage(msg) {
FILE: src/ConfigUpgrader.hpp
class ConfigUpgrader (line 15) | class ConfigUpgrader
method ConfigUpgrader (line 18) | ConfigUpgrader(fs::FS* filesystem, const char* configFile)
method upgrade (line 28) | bool upgrade(const char* outputFile = nullptr) {
method migrateLegacySetupStorage (line 92) | bool migrateLegacySetupStorage(const char* legacyConfigFile, const cha...
method String (line 134) | String joinPath(const String& base, const String& name) {
method ensureDirectory (line 141) | bool ensureDirectory(const String& path) {
method copyFile (line 145) | bool copyFile(const String& source, const String& target) {
method moveFile (line 173) | bool moveFile(const String& source, const String& target) {
method moveDirectoryContents (line 184) | bool moveDirectoryContents(const String& sourceDir, const String& targ...
method String (line 226) | String remapLegacySetupPath(const String& value, const String& sourceD...
method rewriteLegacySetupPaths (line 239) | void rewriteLegacySetupPaths(cJSON* node, const String& sourceDir, con...
method rewriteSetupPathsInConfigFile (line 255) | bool rewriteSetupPathsInConfigFile(const char* configPath, const Strin...
method String (line 298) | String generateId(const String& label) {
method String (line 317) | String escapeJson(const String& input) {
method String (line 343) | String upgradeFromV1(cJSON* oldRoot) {
method String (line 425) | String upgradeToSections(cJSON* oldRoot) {
method String (line 494) | String buildSection(const String& id, const String& title, const std::...
method String (line 511) | String convertV1Option(const String& key, cJSON* item) {
FILE: src/CredentialManager.cpp
function String (line 63) | String CredentialManager::getStatus() const {
function String (line 79) | String CredentialManager::getHostname() const {
function String (line 205) | String CredentialManager::getPassword(uint8_t index) {
function String (line 812) | String CredentialManager::getDebugInfo() const {
FILE: src/CredentialManager.h
type WiFiCredential (line 49) | struct WiFiCredential {
function class (line 60) | class CredentialManager {
FILE: src/FSWebServer.cpp
function String (line 8) | String serializeJsonDocument(cJSON *root) {
function String (line 410) | String FSWebServer::buildSetupStatusPayload() const {
function String (line 444) | String FSWebServer::buildSetupConfigPayload() const {
function String (line 462) | String FSWebServer::buildSetupCredentialsPayload() const {
FILE: src/FSWebServer.h
type fsInfo_t (line 105) | typedef struct {
function class (line 115) | class FSWebServer : public WebServerClass {
function addComment (line 581) | void addComment(const char *lbl, const char *comment) { getSetupConfigur...
FILE: src/Json.cpp
function jsonEscapeString (line 22) | static void jsonEscapeString(const char* in, String& out) {
function addIndent (line 48) | static void addIndent(String& out, int indent) {
function serializeArray (line 52) | static void serializeArray(const cJSON* array, String& out, bool pretty,...
function serializeObject (line 76) | static void serializeObject(const cJSON* obj, String& out, bool pretty, ...
function serializeNumber (line 105) | static void serializeNumber(const cJSON* item, String& out) {
function serializeNode (line 118) | static void serializeNode(const cJSON* item, String& out, bool pretty, i...
function String (line 136) | String Json::serialize(bool pretty) const
FILE: src/Json.h
function namespace (line 9) | namespace CJSON {
FILE: src/SetupConfig.hpp
type SetupConfig (line 17) | namespace SetupConfig {
type DropdownList (line 18) | struct DropdownList {
type Slider (line 25) | struct Slider {
class SetupConfigurator (line 34) | class SetupConfigurator
method readBinaryByte (line 54) | uint8_t readBinaryByte(const uint8_t* data, size_t offset) const {
method writeBinaryFile (line 62) | bool writeBinaryFile(File& file, const uint8_t* data, size_t len) {
method ensureActiveSection (line 91) | void ensureActiveSection() {
method startNewSection (line 101) | void startNewSection(const char* title) {
method finalizeSectionsToRoot (line 115) | void finalizeSectionsToRoot() {
method cJSON (line 137) | cJSON* findElementByLabel(cJSON* root, const char *label) {
method adoptSavedConfigurationAsSessionDoc (line 158) | bool adoptSavedConfigurationAsSessionDoc() {
method isOpened (line 183) | bool isOpened() {
method openConfiguration (line 187) | bool openConfiguration() {
method upgradeConfigIfNeeded (line 283) | void upgradeConfigIfNeeded() {
method checkConfigFile (line 296) | bool checkConfigFile() {
method SetupConfigurator (line 355) | SetupConfigurator(fs::FS *fs, uint16_t& port, String& host)
method closeConfiguration (line 358) | bool closeConfiguration() {
method setSetupPageLogo (line 424) | void setSetupPageLogo(const uint8_t* imageData, size_t imageSize, cons...
method setSetupPageLogo (line 463) | void setSetupPageLogo(const char* svgText, bool overwrite = false) {
method setSetupPageTitle (line 468) | void setSetupPageTitle(const char* title) {
method optionToFile (line 482) | bool optionToFile(const char* filename, const char* str, bool overWrit...
method optionToFileBinary (line 509) | bool optionToFileBinary(const char* filename, const uint8_t* data, siz...
method optionToFileGzip (line 525) | bool optionToFileGzip(const char* filename, const uint8_t* data, size_...
method addSource (line 540) | void addSource(const String& source, const String& id, const String& e...
method addHTML (line 593) | void addHTML(const char* html, const char* id, bool overWrite) {
method addCSS (line 608) | void addCSS(const char* css, const char* id, bool overWrite) {
method addJavascript (line 613) | void addJavascript(const char* script, const char* id, bool overWrite) {
method addDropdownList (line 621) | void addDropdownList(const char *label, const char** array, size_t siz...
method addDropdownList (line 676) | void addDropdownList(SetupConfig::DropdownList &def) {
method getDropdownSelection (line 741) | bool getDropdownSelection(SetupConfig::DropdownList &def) {
method addSlider (line 794) | void addSlider(SetupConfig::Slider &def) {
method getSliderValue (line 853) | bool getSliderValue(SetupConfig::Slider &def) {
method addOptionBox (line 893) | void addOptionBox(const char* boxTitle) {
method addComment (line 908) | void addComment(const char *tag, const char *comment) {
method addOption (line 960) | void addOption(const char *label, T val, double d_min, double d_max, d...
method addOption (line 968) | void addOption(const char *label, bool val, bool hidden = false, bool ...
method addOption (line 1032) | void addOption(const char *label, T val, bool hidden = false,
method getOptionValue (line 1144) | bool getOptionValue(const char *label, T &var) {
method saveOptionValue (line 1222) | bool saveOptionValue(const char *label, T val) {
FILE: src/WiFiService.cpp
function logCurrentStaNetworkConfig (line 28) | static void logCurrentStaNetworkConfig() {
function resetTaskWdtIfSubscribed (line 44) | static inline void resetTaskWdtIfSubscribed() {
function WiFiScanResult (line 71) | WiFiScanResult WiFiService::scanNetworks() {
function WiFiConnectResult (line 126) | WiFiConnectResult WiFiService::connectWithParams(const WiFiConnectParams...
function WiFiStartResult (line 230) | WiFiStartResult WiFiService::startWiFi(CredentialManager* credentialMana...
FILE: src/WiFiService.h
type WiFiScanResult (line 21) | struct WiFiScanResult {
function WiFiStartAction (line 26) | enum class WiFiStartAction {
type WiFiConnectResult (line 63) | struct WiFiConnectResult {
function class (line 78) | class WiFiService {
FILE: src/compat/mbedtls_aes.h
type mbedtls_aes_context (line 16) | typedef struct {
function mbedtls_aes_init (line 23) | static inline void mbedtls_aes_init(mbedtls_aes_context *ctx) {
function mbedtls_aes_free (line 29) | static inline void mbedtls_aes_free(mbedtls_aes_context *ctx) {
function mbedtls_aes_setkey_enc (line 33) | static inline int mbedtls_aes_setkey_enc(mbedtls_aes_context *ctx, const...
function mbedtls_aes_setkey_dec (line 42) | static inline int mbedtls_aes_setkey_dec(mbedtls_aes_context *ctx, const...
function mbedtls_aes_crypt_cbc (line 51) | static inline int mbedtls_aes_crypt_cbc(mbedtls_aes_context *ctx, int mo...
FILE: src/json/cJSON.c
type error (line 88) | typedef struct {
function cJSON_GetErrorPtr (line 94) | CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void)
function cJSON_GetStringValue (line 99) | CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item)
function cJSON_GetNumberValue (line 109) | CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item)
function cJSON_Version (line 124) | CJSON_PUBLIC(const char*) cJSON_Version(void)
function case_insensitive_strcmp (line 133) | static int case_insensitive_strcmp(const unsigned char *string1, const u...
type internal_hooks (line 156) | typedef struct internal_hooks
function internal_free (line 169) | static void CJSON_CDECL internal_free(void *pointer)
function cJSON_InitHooks (line 209) | CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks)
function cJSON (line 241) | static cJSON *cJSON_New_Item(const internal_hooks * const hooks)
function cJSON_Delete (line 253) | CJSON_PUBLIC(void) cJSON_Delete(cJSON *item)
function get_decimal_point (line 279) | static unsigned char get_decimal_point(void)
type parse_buffer (line 289) | typedef struct
function cJSON_bool (line 307) | static cJSON_bool parse_number(cJSON * const item, parse_buffer * const ...
function cJSON_SetNumberHelper (line 411) | CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number)
function cJSON_SetValuestring (line 430) | CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valu...
type printbuffer (line 473) | typedef struct
function update_offset (line 571) | static void update_offset(printbuffer * const buffer)
function cJSON_bool (line 584) | static cJSON_bool compare_double(double a, double b)
function cJSON_bool (line 591) | static cJSON_bool print_number(const cJSON * const item, printbuffer * c...
function parse_hex4 (line 661) | static unsigned parse_hex4(const unsigned char * const input)
function utf16_literal_to_utf8 (line 698) | static unsigned char utf16_literal_to_utf8(const unsigned char * const i...
function cJSON_bool (line 819) | static cJSON_bool parse_string(cJSON * const item, parse_buffer * const ...
function cJSON_bool (line 949) | static cJSON_bool print_string_ptr(const unsigned char * const input, pr...
function cJSON_bool (line 1071) | static cJSON_bool print_string(const cJSON * const item, printbuffer * c...
function parse_buffer (line 1085) | static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer)
function parse_buffer (line 1111) | static parse_buffer *skip_utf8_bom(parse_buffer * const buffer)
function cJSON_ParseWithOpts (line 1126) | CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char ...
function cJSON_ParseWithLengthOpts (line 1142) | CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_...
function cJSON_Parse (line 1222) | CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value)
function cJSON_ParseWithLength (line 1227) | CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t bu...
function cJSON_Print (line 1302) | CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item)
function cJSON_PrintUnformatted (line 1307) | CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item)
function cJSON_PrintBuffered (line 1312) | CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffe...
function cJSON_PrintPreallocated (line 1343) | CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buff...
function cJSON_bool (line 1363) | static cJSON_bool parse_value(cJSON * const item, parse_buffer * const i...
function cJSON_bool (line 1418) | static cJSON_bool print_value(const cJSON * const item, printbuffer * co...
function cJSON_bool (line 1492) | static cJSON_bool parse_array(cJSON * const item, parse_buffer * const i...
function cJSON_bool (line 1590) | static cJSON_bool print_array(const cJSON * const item, printbuffer * co...
function cJSON_bool (line 1652) | static cJSON_bool parse_object(cJSON * const item, parse_buffer * const ...
function cJSON_bool (line 1770) | static cJSON_bool print_object(const cJSON * const item, printbuffer * c...
function cJSON_GetArraySize (line 1884) | CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array)
function cJSON (line 1907) | static cJSON* get_array_item(const cJSON *array, size_t index)
function cJSON_GetArrayItem (line 1926) | CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index)
function cJSON (line 1936) | static cJSON *get_object_item(const cJSON * const object, const char * c...
function cJSON_GetObjectItem (line 1968) | CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, co...
function cJSON_GetObjectItemCaseSensitive (line 1973) | CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * con...
function cJSON_HasObjectItem (line 1978) | CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const ...
function suffix_object (line 1984) | static void suffix_object(cJSON *prev, cJSON *item)
function cJSON (line 1991) | static cJSON *create_reference(const cJSON *item, const internal_hooks *...
function cJSON_bool (line 2012) | static cJSON_bool add_item_to_array(cJSON *array, cJSON *item)
function cJSON_AddItemToArray (line 2046) | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item)
function cJSON_bool (line 2067) | static cJSON_bool add_item_to_object(cJSON * const object, const char * ...
function cJSON_AddItemToObject (line 2104) | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char...
function cJSON_AddItemToObjectCS (line 2110) | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const ch...
function cJSON_AddItemReferenceToArray (line 2115) | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJS...
function cJSON_AddItemReferenceToObject (line 2125) | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, c...
function cJSON_AddNullToObject (line 2135) | CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const c...
function cJSON_AddTrueToObject (line 2147) | CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const c...
function cJSON_AddFalseToObject (line 2159) | CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const ...
function cJSON_AddBoolToObject (line 2171) | CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const c...
function cJSON_AddNumberToObject (line 2183) | CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const...
function cJSON_AddStringToObject (line 2195) | CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const...
function cJSON_AddRawToObject (line 2207) | CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const ch...
function cJSON_AddObjectToObject (line 2219) | CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const...
function cJSON_AddArrayToObject (line 2231) | CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const ...
function cJSON_DetachItemViaPointer (line 2243) | CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * ...
function cJSON_DetachItemFromArray (line 2279) | CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which)
function cJSON_DeleteItemFromArray (line 2289) | CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which)
function cJSON_DetachItemFromObject (line 2294) | CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const ch...
function cJSON_DetachItemFromObjectCaseSensitive (line 2301) | CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *obj...
function cJSON_DeleteItemFromObject (line 2308) | CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char ...
function cJSON_DeleteItemFromObjectCaseSensitive (line 2313) | CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object...
function cJSON_InsertItemInArray (line 2319) | CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which...
function cJSON_ReplaceItemViaPointer (line 2353) | CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const paren...
function cJSON_ReplaceItemInArray (line 2402) | CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int whic...
function cJSON_bool (line 2412) | static cJSON_bool replace_item_in_object(cJSON *object, const char *stri...
function cJSON_ReplaceItemInObject (line 2435) | CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object, const ...
function cJSON_ReplaceItemInObjectCaseSensitive (line 2440) | CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *o...
function cJSON_CreateNull (line 2446) | CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void)
function cJSON_CreateTrue (line 2457) | CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void)
function cJSON_CreateFalse (line 2468) | CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void)
function cJSON_CreateBool (line 2479) | CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean)
function cJSON_CreateNumber (line 2490) | CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num)
function cJSON_CreateString (line 2516) | CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string)
function cJSON_CreateStringReference (line 2533) | CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string)
function cJSON_CreateObjectReference (line 2545) | CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child)
function cJSON_CreateArrayReference (line 2556) | CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) {
function cJSON_CreateRaw (line 2566) | CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw)
function cJSON_CreateArray (line 2583) | CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void)
function cJSON_CreateObject (line 2594) | CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void)
function cJSON_CreateIntArray (line 2606) | CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count)
function cJSON_CreateFloatArray (line 2646) | CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int c...
function cJSON_CreateDoubleArray (line 2686) | CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int...
function cJSON_CreateStringArray (line 2726) | CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings...
function cJSON_Duplicate (line 2769) | CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recu...
function cJSON (line 2774) | cJSON * cJSON_Duplicate_rec(const cJSON *item, size_t depth, cJSON_bool ...
function skip_oneline_comment (line 2860) | static void skip_oneline_comment(char **input)
function skip_multiline_comment (line 2873) | static void skip_multiline_comment(char **input)
function minify_string (line 2887) | static void minify_string(char **input, char **output) {
function cJSON_Minify (line 2909) | CJSON_PUBLIC(void) cJSON_Minify(char *json)
function cJSON_IsInvalid (line 2957) | CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item)
function cJSON_IsFalse (line 2967) | CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item)
function cJSON_IsTrue (line 2977) | CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item)
function cJSON_IsBool (line 2988) | CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item)
function cJSON_IsNull (line 2997) | CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item)
function cJSON_IsNumber (line 3007) | CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item)
function cJSON_IsString (line 3017) | CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item)
function cJSON_IsArray (line 3027) | CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item)
function cJSON_IsObject (line 3037) | CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item)
function cJSON_IsRaw (line 3047) | CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item)
function cJSON_Compare (line 3057) | CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSO...
function cJSON_malloc (line 3182) | CJSON_PUBLIC(void *) cJSON_malloc(size_t size)
function cJSON_free (line 3187) | CJSON_PUBLIC(void) cJSON_free(void *object)
FILE: src/json/cJSON.h
type cJSON (line 103) | typedef struct cJSON
type cJSON_Hooks (line 125) | typedef struct cJSON_Hooks
type cJSON_bool (line 132) | typedef int cJSON_bool;
FILE: src/mimetable/mimetable.cpp
type mimetype (line 4) | namespace mimetype {
function String (line 37) | String getContentType(const String &path) {
FILE: src/mimetable/mimetable.h
function namespace (line 5) | namespace mimetype {
FILE: src/websocket/SocketIOclient.h
type engineIOmessageType_t (line 19) | typedef enum {
type socketIOmessageType_t (line 29) | typedef enum {
function class (line 39) | class SocketIOclient : protected WebSocketsClient {
FILE: src/websocket/WebSockets.cpp
function String (line 534) | String WebSockets::acceptKey(String & clientKey) {
function String (line 561) | String WebSockets::base64_encode(uint8_t * data, size_t length) {
FILE: src/websocket/WebSockets.h
type WSclientsStatus_t (line 81) | typedef enum {
type WStype_t (line 88) | typedef enum {
type WSopcode_t (line 102) | typedef enum {
type WSMessageHeader_t (line 113) | typedef struct {
function class (line 187) | class WebSockets {
FILE: src/websocket/WebSocketsClient.h
function class (line 30) | class WebSocketsClient : protected WebSockets {
FILE: src/websocket/WebSocketsServer.cpp
function IPAddress (line 396) | IPAddress WebSocketsServerCore::remoteIP(uint8_t num) {
function WSclient_t (line 416) | WSclient_t * WebSocketsServerCore::newClient(WEBSOCKETS_NETWORK_CLASS * ...
function WSclient_t (line 566) | WSclient_t * WebSocketsServerCore::handleNewClient(WEBSOCKETS_NETWORK_CL...
FILE: src/websocket/WebSocketsServer.h
function class (line 34) | class WebSocketsServerCore : protected WebSockets {
function class (line 209) | class WebSocketsServer : public WebSocketsServerCore {
FILE: src/websocket/libb64/cdecode.c
function base64_decode_value (line 19) | int base64_decode_value(char value_in)
function base64_init_decodestate (line 28) | void base64_init_decodestate(base64_decodestate* state_in)
function base64_decode_block (line 34) | int base64_decode_block(const char* code_in, const int length_in, char* ...
FILE: src/websocket/libb64/cdecode_inc.h
type base64_decodestep (line 11) | typedef enum
type base64_decodestate (line 16) | typedef struct
FILE: src/websocket/libb64/cencode.c
function base64_init_encodestate (line 21) | void base64_init_encodestate(base64_encodestate* state_in)
function base64_encode_value (line 28) | char base64_encode_value(char value_in)
function base64_encode_block (line 35) | int base64_encode_block(const char* plaintext_in, int length_in, char* c...
function base64_encode_blockend (line 96) | int base64_encode_blockend(char* code_out, base64_encodestate* state_in)
FILE: src/websocket/libb64/cencode_inc.h
type base64_encodestep (line 11) | typedef enum
type base64_encodestate (line 16) | typedef struct
FILE: src/websocket/libsha1/libsha1.c
function SHA1Transform (line 57) | void SHA1Transform(uint32_t state[5], const unsigned char buffer[64])
function SHA1Init (line 118) | void SHA1Init(SHA1_CTX* context)
function SHA1Update (line 132) | void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len)
function SHA1Final (line 156) | void SHA1Final(unsigned char digest[20], SHA1_CTX* context)
FILE: src/websocket/libsha1/libsha1.h
type SHA1_CTX (line 10) | typedef struct {
Condensed preview — 184 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,416K chars).
[
{
"path": ".github/workflows/clean_workflow.yml",
"chars": 652,
"preview": "name: Clean Workflow Logs\non:\n workflow_dispatch:\n inputs:\n keep_minimum_runs:\n description: \"Numero di "
},
{
"path": ".github/workflows/cli-build-esp32-dev.yml",
"chars": 5977,
"preview": "# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\n\nname: (ESP32) Build dev with Arduino "
},
{
"path": ".github/workflows/cli-build-esp8266.yml",
"chars": 5952,
"preview": "# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\n\nname: (ESP8266) Build with Arduino CL"
},
{
"path": ".github/workflows/pio-build-esp32-dev.yml",
"chars": 5328,
"preview": "# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\n\nname: (ESP32) Build dev with Platform"
},
{
"path": ".github/workflows/pio-build-esp8266.yml",
"chars": 5278,
"preview": "# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\n\nname: (ESP8266) Build with PlatformIO"
},
{
"path": ".gitignore",
"chars": 530,
"preview": "# Prerequisites\n*.d\n\n# Compiled Object files\n*.slo\n*.lo\n*.o\n*.obj\n\n# Precompiled Headers\n*.gch\n*.pch\n\n# Compiled Dynamic"
},
{
"path": "LICENSE",
"chars": 11357,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 4951,
"preview": "# ESP-FS-WebServer\n\nA library for ESP8266/ESP32 that provides a web server with an integrated file system browser, WiFi "
},
{
"path": "built-in-webpages/readme.md",
"chars": 869,
"preview": "The canonical built-in page sources are no longer maintained in this folder.\n\nUse the shared repository at `C:\\Cloud\\fs-"
},
{
"path": "docs/API.md",
"chars": 4546,
"preview": "# FSWebServer – API (overview)\n\nThis page summarizes the main methods exposed by `FSWebServer` and when to use them.\n\n> "
},
{
"path": "docs/FileEditorAndFS.md",
"chars": 1225,
"preview": "# Filesystem + Web File Editor (/edit)\n\nThe library serves static files from the filesystem and, optionally, includes a "
},
{
"path": "docs/SetupAndWiFi.md",
"chars": 3162,
"preview": "# Setup + WiFi (startWiFi / captive portal / config)\n\nThis library can:\n- try to connect to a previously saved WiFi netw"
},
{
"path": "docs/WebSocket.md",
"chars": 3036,
"preview": "# WebSocket Support\n\nThe library includes and uses [WebSockets](https://github.com/Links2004/arduino-WebSockets) by Mark"
},
{
"path": "docs/pwd_encrypt.md",
"chars": 5614,
"preview": "# FSWebServer CredentialManager Integration \n\n✅ **Encrypted Password Storage** - AES-256-CBC encryption\n✅ **Automatic Pe"
},
{
"path": "docs/readme.md",
"chars": 299,
"preview": "# Documentation\n\n- [API](API.md) – available methods and what they do\n- [Setup + WiFi](SetupAndWiFi.md) – `startWiFi()`,"
},
{
"path": "examples/csvLogger/.gitignore",
"chars": 94,
"preview": ".pio\n.vscode/.browse.c_cpp.db*\n.vscode/c_cpp_properties.json\n.vscode/launch.json\n.vscode/ipch\n"
},
{
"path": "examples/csvLogger/csvLogger.ino",
"chars": 3442,
"preview": "#include <Arduino.h>\n#include <FS.h>\n#include <FSWebServer.h>\n#include <LittleFS.h>\n\nFSWebServer server(LittleFS, 80, \"e"
},
{
"path": "examples/csvLogger/data/assets/css/index.css",
"chars": 3108,
"preview": "/* Misc */\nhtml, body {\n font-family: \"Helvetica\",\"Arial\",sans-serif;\n font-size: 14px;\n font-weight: 400;\n "
},
{
"path": "examples/csvLogger/data/assets/css/style.css",
"chars": 3106,
"preview": "/* Misc */\nhtml, body {\n font-family: \"Helvetica\",\"Arial\",sans-serif;\n font-size: 14px;\n font-weight: 400;\n "
},
{
"path": "examples/csvLogger/data/assets/js/csv.js",
"chars": 8031,
"preview": "\n// Default file to be loaded with no parameter in url\nvar filename = '';\n\n// JQuery-like selector\nvar $ = function(el) "
},
{
"path": "examples/csvLogger/data/assets/js/index.js",
"chars": 2891,
"preview": "var dataFolder = document.getElementById(\"csv-path\").value;\nvar fileList = document.getElementById('file-list');\nvar cur"
},
{
"path": "examples/csvLogger/data/csv/2024_01_10.csv",
"chars": 847,
"preview": "timestamp, free heap, largest free block, connected, wifi strength\nWed Jan 10 13:19:43 2024, 270472, 262132, true, -43\nW"
},
{
"path": "examples/csvLogger/data/index.htm",
"chars": 1144,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n <title>ESP32 CSV L"
},
{
"path": "examples/csvLogger/partitions.csv",
"chars": 305,
"preview": "# Name, Type, SubType, Offset, Size, Flags\nnvs, data, nvs, 0x9000, 0x5000,\notadata, data, ota, 0xe000,"
},
{
"path": "examples/csvLogger/platformio.ini",
"chars": 1241,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "examples/csvLogger/readme.md",
"chars": 241,
"preview": "An example for logging to a CSV file and viewing the content with the browser.\nIt is also possible to modify or download"
},
{
"path": "examples/csvLoggerSD/csvLoggerSD.ino",
"chars": 4066,
"preview": "#include <SD.h>\n#include <FSWebServer.h>\n\n// Timezone definition to get properly time from NTP server\n#define MYTZ \"CET-"
},
{
"path": "examples/csvLoggerSD/data/assets/css/index.css",
"chars": 3108,
"preview": "/* Misc */\nhtml, body {\n font-family: \"Helvetica\",\"Arial\",sans-serif;\n font-size: 14px;\n font-weight: 400;\n "
},
{
"path": "examples/csvLoggerSD/data/assets/css/style.css",
"chars": 3106,
"preview": "/* Misc */\nhtml, body {\n font-family: \"Helvetica\",\"Arial\",sans-serif;\n font-size: 14px;\n font-weight: 400;\n "
},
{
"path": "examples/csvLoggerSD/data/assets/js/csv.js",
"chars": 8031,
"preview": "\n// Default file to be loaded with no parameter in url\nvar filename = '';\n\n// JQuery-like selector\nvar $ = function(el) "
},
{
"path": "examples/csvLoggerSD/data/assets/js/index.js",
"chars": 2891,
"preview": "var dataFolder = document.getElementById(\"csv-path\").value;\nvar fileList = document.getElementById('file-list');\nvar cur"
},
{
"path": "examples/csvLoggerSD/data/index.htm",
"chars": 1144,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n <title>ESP32 CSV L"
},
{
"path": "examples/csvLoggerSD/partitions.csv",
"chars": 305,
"preview": "# Name, Type, SubType, Offset, Size, Flags\nnvs, data, nvs, 0x9000, 0x5000,\notadata, data, ota, 0xe000,"
},
{
"path": "examples/csvLoggerSD/platformio.ini",
"chars": 1172,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "examples/csvLoggerSD/readme.md",
"chars": 241,
"preview": "An example for logging to a CSV file and viewing the content with the browser.\nIt is also possible to modify or download"
},
{
"path": "examples/customHTML/customElements.h",
"chars": 5698,
"preview": "#pragma once\n#include <Arduino.h>\n\n\n/*\n* This HTML code will be injected in /setup webpage using a <div></div> element a"
},
{
"path": "examples/customHTML/customHTML.ino",
"chars": 8050,
"preview": "#include <FS.h>\r\n#include <LittleFS.h>\r\n#include <FSWebServer.h> // https://github.com/cotestatnt/esp-fs-webserver\r\n\r\n"
},
{
"path": "examples/customHTML/thingsboard.h",
"chars": 4435,
"preview": "#pragma once\n#include <Arduino.h>\n\n\ninline const char thingsboard_htm[] PROGMEM = R\"EOF(\n<div>\n <br>If you don't have a"
},
{
"path": "examples/customOptions/.gitignore",
"chars": 94,
"preview": ".pio\n.vscode/.browse.c_cpp.db*\n.vscode/c_cpp_properties.json\n.vscode/launch.json\n.vscode/ipch\n"
},
{
"path": "examples/customOptions/customOptions.ino",
"chars": 7687,
"preview": "#include <FS.h>\r\n#include <LittleFS.h>\r\n#include <FSWebServer.h> //https://github.com/cotestatnt/esp-fs-webserver\r\n\r\n//"
},
{
"path": "examples/customOptions/partitions.csv",
"chars": 305,
"preview": "# Name, Type, SubType, Offset, Size, Flags\nnvs, data, nvs, 0x9000, 0x5000,\notadata, data, ota, 0xe000,"
},
{
"path": "examples/customOptions/platformio.ini",
"chars": 1196,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "examples/esp32-cam/.gitignore",
"chars": 94,
"preview": ".pio\n.vscode/.browse.c_cpp.db*\n.vscode/c_cpp_properties.json\n.vscode/launch.json\n.vscode/ipch\n"
},
{
"path": "examples/esp32-cam/camera_pins.h",
"chars": 9690,
"preview": "#pragma once\n#include <Arduino.h>\n#include \"esp_camera.h\"\n#include <esp_err.h>\n\n// Select camera model\n//#define CAMERA_"
},
{
"path": "examples/esp32-cam/data/index.htm",
"chars": 530,
"preview": "<!DOCTYPE HTML>\n<html lang=\"en-US\">\n <head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"refresh\" content"
},
{
"path": "examples/esp32-cam/data/www/app.js",
"chars": 4252,
"preview": "const imgFolder = '/img/';\nvar fileList = document.getElementById('file-list');\nvar intervalID;\n\n// Load list of files e"
},
{
"path": "examples/esp32-cam/data/www/index.htm",
"chars": 1568,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n <title>ESP32-CAM-W"
},
{
"path": "examples/esp32-cam/data/www/styles.css",
"chars": 3516,
"preview": "*, *:before, *:after {\n -moz-box-sizing: border-box;\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n}\n\nhtm"
},
{
"path": "examples/esp32-cam/esp32-cam.ino",
"chars": 6375,
"preview": "\n#include <FS.h>\n#include <SD_MMC.h>\n#include <LittleFS.h>\n#include <FSWebServer.h> // https://github.com/cotestatnt/e"
},
{
"path": "examples/esp32-cam/partitions.csv",
"chars": 305,
"preview": "# Name, Type, SubType, Offset, Size, Flags\nnvs, data, nvs, 0x9000, 0x5000,\notadata, data, ota, 0xe000,"
},
{
"path": "examples/esp32-cam/platformio.ini",
"chars": 1076,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "examples/gpio_list/.gitignore",
"chars": 94,
"preview": ".pio\n.vscode/.browse.c_cpp.db*\n.vscode/c_cpp_properties.json\n.vscode/launch.json\n.vscode/ipch\n"
},
{
"path": "examples/gpio_list/data/index.htm",
"chars": 2897,
"preview": "<!DOCTYPE html>\r\n<html>\r\n <head>\r\n <meta charset=\"UTF-8\">\r\n <title>ESP GPIO dinamic list</title>\r\n <meta name="
},
{
"path": "examples/gpio_list/data/script.js",
"chars": 4328,
"preview": "const svgLightOn = '<svg style=\"width:24px;height:24px\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M20,11H23V13H20"
},
{
"path": "examples/gpio_list/gpio_list.ino",
"chars": 6044,
"preview": "#include <FS.h>\r\n#include <LittleFS.h>\r\n#include <FSWebServer.h> // https://github.com/cotestatnt/esp-fs-webserver/\r\n\r"
},
{
"path": "examples/gpio_list/partitions.csv",
"chars": 305,
"preview": "# Name, Type, SubType, Offset, Size, Flags\nnvs, data, nvs, 0x9000, 0x5000,\notadata, data, ota, 0xe000,"
},
{
"path": "examples/gpio_list/platformio.ini",
"chars": 1172,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "examples/gpio_list/readme.md",
"chars": 301,
"preview": "In this example the content of webpage will be created at runtime with Javascript depending from a JSON list of gpios (p"
},
{
"path": "examples/handleFormData/.gitignore",
"chars": 94,
"preview": ".pio\n.vscode/.browse.c_cpp.db*\n.vscode/c_cpp_properties.json\n.vscode/launch.json\n.vscode/ipch\n"
},
{
"path": "examples/handleFormData/data/index.htm",
"chars": 6100,
"preview": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n <style>\r\n .container {\r\n box-shadow:\r\n 0 0.125rem 1rem rgba(27, 40, 50"
},
{
"path": "examples/handleFormData/data/myScript.js",
"chars": 2178,
"preview": "// Expand or collapse the content according to current state\r\nfunction expandCollapse() {\r\n // Get the HTML element imm"
},
{
"path": "examples/handleFormData/data/myStyle.css",
"chars": 1430,
"preview": "\r\n.container {\r\n box-shadow: \r\n 0 0.125rem 1rem rgba(27, 40, 50, 0.04),\r\n 0 0.125rem 2rem rgba(27, 40, 50, 0.08),"
},
{
"path": "examples/handleFormData/handleFormData.ino",
"chars": 2786,
"preview": "#include <FS.h>\r\n#include <LittleFS.h>\r\n#include <FSWebServer.h> // https://github.com/cotestatnt/esp-fs-webserver/\r\n\r"
},
{
"path": "examples/handleFormData/partitions.csv",
"chars": 305,
"preview": "# Name, Type, SubType, Offset, Size, Flags\nnvs, data, nvs, 0x9000, 0x5000,\notadata, data, ota, 0xe000,"
},
{
"path": "examples/handleFormData/platformio.ini",
"chars": 1248,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "examples/leanWebserver/.gitignore",
"chars": 94,
"preview": ".pio\n.vscode/.browse.c_cpp.db*\n.vscode/c_cpp_properties.json\n.vscode/launch.json\n.vscode/ipch\n"
},
{
"path": "examples/leanWebserver/data/index.htm",
"chars": 2028,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n <title>ESP simpleS"
},
{
"path": "examples/leanWebserver/leanWebserver.ino",
"chars": 4504,
"preview": "/**\n * @file leanWebserver.ino\n * @brief Example of a lean ESP webserver using FSWebServer and LittleFS.\n *\n * This sket"
},
{
"path": "examples/leanWebserver/partitions.csv",
"chars": 305,
"preview": "# Name, Type, SubType, Offset, Size, Flags\nnvs, data, nvs, 0x9000, 0x5000,\notadata, data, ota, 0xe000,"
},
{
"path": "examples/leanWebserver/platformio.ini",
"chars": 1166,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "examples/localRFID/.gitignore",
"chars": 94,
"preview": ".pio\n.vscode/.browse.c_cpp.db*\n.vscode/c_cpp_properties.json\n.vscode/launch.json\n.vscode/ipch\n"
},
{
"path": "examples/localRFID/JsonDB.hpp",
"chars": 3629,
"preview": "#pragma once\n#include <Arduino.h>\n#include <ArduinoJson.h>\n#include <FS.h>\n#include <LittleFS.h>\n\nclass TableManager {\np"
},
{
"path": "examples/localRFID/data/html_login.h",
"chars": 14639,
"preview": "/* Generated with https://cotestatnt.github.io/fsdata.html */\n/* File: login */\n/* Content-Type: application/octet-strea"
},
{
"path": "examples/localRFID/data/html_rfid.h",
"chars": 44397,
"preview": "/* Generated with https://cotestatnt.github.io/fsdata.html */\n/* File: rfid */\n/* Content-Type: application/octet-stream"
},
{
"path": "examples/localRFID/data/login",
"chars": 5712,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width"
},
{
"path": "examples/localRFID/data/rfid",
"chars": 27841,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, in"
},
{
"path": "examples/localRFID/localRFID.ino",
"chars": 5782,
"preview": "#include <Arduino.h>\n#include <FS.h>\n#include <LittleFS.h>\n#include <ArduinoJson.h>\n#include <FSWebServer.h> // ht"
},
{
"path": "examples/localRFID/partitions.csv",
"chars": 305,
"preview": "# Name, Type, SubType, Offset, Size, Flags\nnvs, data, nvs, 0x9000, 0x5000,\notadata, data, ota, 0xe000,"
},
{
"path": "examples/localRFID/platformio.ini",
"chars": 1240,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "examples/localRFID/readme.md",
"chars": 1912,
"preview": "In this example, a simple access control system has been implemented using RFID tags and the [Arduino_MFRC522v2](https:/"
},
{
"path": "examples/localRFID/webserver.hpp",
"chars": 7466,
"preview": "#pragma once\n#include <Arduino.h>\n\n#include <LittleFS.h>\n#include <FSWebServer.h> // https://github.com/cotestatnt/esp-"
},
{
"path": "examples/mqtt_webserver/.gitignore",
"chars": 94,
"preview": ".pio\n.vscode/.browse.c_cpp.db*\n.vscode/c_cpp_properties.json\n.vscode/launch.json\n.vscode/ipch\n"
},
{
"path": "examples/mqtt_webserver/mqtt_webserver.ino",
"chars": 5670,
"preview": "/*\n Basic ESP32 MQTT example:\n It connects to an MQTT server then:\n - publishes \"hello world\" to the topic \"output\" eve"
},
{
"path": "examples/mqtt_webserver/partitions.csv",
"chars": 305,
"preview": "# Name, Type, SubType, Offset, Size, Flags\nnvs, data, nvs, 0x9000, 0x5000,\notadata, data, ota, 0xe000,"
},
{
"path": "examples/mqtt_webserver/platformio.ini",
"chars": 1174,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "examples/mqtt_webserver/readme.md",
"chars": 94,
"preview": "\n"
},
{
"path": "examples/mysqlRFID/.gitignore",
"chars": 94,
"preview": ".pio\n.vscode/.browse.c_cpp.db*\n.vscode/c_cpp_properties.json\n.vscode/launch.json\n.vscode/ipch\n"
},
{
"path": "examples/mysqlRFID/html_flash_files.h",
"chars": 27847,
"preview": "#pragma once\n#include <Arduino.h>\n\ninline const char login_htm[] PROGMEM = R\"string_literal(\n<!DOCTYPE html>\n<html lang="
},
{
"path": "examples/mysqlRFID/mysqlRFID.ino",
"chars": 4012,
"preview": "#include <FS.h>\n#include <LittleFS.h>\n\n#include <ArduinoJson.h> // https://github.com/bblanchon/ArduinoJson\n#inclu"
},
{
"path": "examples/mysqlRFID/mysql_impl.h",
"chars": 2844,
"preview": "#pragma once\n#include <Arduino.h>\n#include <MySQL.h> // https://github.com/cotestatnt/Arduino-MySQL\n#includ"
},
{
"path": "examples/mysqlRFID/partitions.csv",
"chars": 305,
"preview": "# Name, Type, SubType, Offset, Size, Flags\nnvs, data, nvs, 0x9000, 0x5000,\notadata, data, ota, 0xe000,"
},
{
"path": "examples/mysqlRFID/platformio.ini",
"chars": 1307,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "examples/mysqlRFID/webserver_impl.h",
"chars": 10555,
"preview": "#pragma once\n#include <Arduino.h>\n#include <LittleFS.h>\n\n#include <ArduinoJson.h> // https://github.com/bblanchon/"
},
{
"path": "examples/remoteOTA/data/index.htm",
"chars": 4752,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n <title>ESP Index</"
},
{
"path": "examples/remoteOTA/fw-esp32/readme.md",
"chars": 1,
"preview": "\n"
},
{
"path": "examples/remoteOTA/fw-esp8266/readme.md",
"chars": 1,
"preview": "\n"
},
{
"path": "examples/remoteOTA/partitions.csv",
"chars": 305,
"preview": "# Name, Type, SubType, Offset, Size, Flags\nnvs, data, nvs, 0x9000, 0x5000,\notadata, data, ota, 0xe000,"
},
{
"path": "examples/remoteOTA/platformio.ini",
"chars": 1172,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "examples/remoteOTA/remoteOTA.ino",
"chars": 8051,
"preview": "#ifdef ESP8266\r\n #include <ESP8266HTTPClient.h>\r\n #include <ESP8266httpUpdate.h>\r\n#elif defined(ESP32)\r\n #include <Wi"
},
{
"path": "examples/remoteOTA/version-esp32.json",
"chars": 149,
"preview": "{\n \"version\": \"1.0.1\",\n \"raw_url\": \"https://github.com/cotestatnt/async-esp-fs-webserver/raw/master/examples/remot"
},
{
"path": "examples/remoteOTA/version-esp8266.json",
"chars": 151,
"preview": "{\n \"version\": \"1.0.1\",\n \"raw_url\": \"https://github.com/cotestatnt/async-esp-fs-webserver/raw/master/examples/remot"
},
{
"path": "examples/simpleServer/data/index.htm",
"chars": 2028,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n <title>ESP simpleS"
},
{
"path": "examples/simpleServer/simpleServer.ino",
"chars": 3417,
"preview": "#include <FS.h>\r\n#include <LittleFS.h>\r\n#include <FSWebServer.h>\r\n\r\nFSWebServer server(LittleFS, 80, \"myServer\");\r\nuint1"
},
{
"path": "examples/websocketEcharts/README.md",
"chars": 867,
"preview": "# WebSocket Chart Example\n\nReal-time telemetry dashboard demonstrating WebSocket communication with live charting.\n\nThe "
},
{
"path": "examples/websocketEcharts/data/index.htm",
"chars": 17856,
"preview": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-w"
},
{
"path": "examples/websocketEcharts/websocketEcharts.ino",
"chars": 3730,
"preview": "#include <Arduino.h>\n#include <FS.h>\n#include <LittleFS.h>\n#include <FSWebServer.h> // https://github.com/cotestatnt/e"
},
{
"path": "examples/withWebSocket/.gitignore",
"chars": 94,
"preview": ".pio\n.vscode/.browse.c_cpp.db*\n.vscode/c_cpp_properties.json\n.vscode/launch.json\n.vscode/ipch\n"
},
{
"path": "examples/withWebSocket/index_htm.h",
"chars": 4970,
"preview": "#pragma once\n#include <Arduino.h>\n\ninline const char homepage[] PROGMEM = R\"EOF(\n<!DOCTYPE html>\n<html>\n <head>\n <me"
},
{
"path": "examples/withWebSocket/partitions.csv",
"chars": 305,
"preview": "# Name, Type, SubType, Offset, Size, Flags\nnvs, data, nvs, 0x9000, 0x5000,\notadata, data, ota, 0xe000,"
},
{
"path": "examples/withWebSocket/platformio.ini",
"chars": 1172,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "examples/withWebSocket/readme.md",
"chars": 873,
"preview": "## esp-fs-webserver\nThis example is a little bit more advanced respect to the [simpleServer](https://github.com/cotestat"
},
{
"path": "examples/withWebSocket/withWebSocket.ino",
"chars": 6351,
"preview": "#include <FS.h>\r\n#include <LittleFS.h>\r\n#include \"FSWebServer.h\"\r\n\r\n#include \"index_htm.h\"\r\n\r\n#define FILESYSTEM LittleF"
},
{
"path": "keywords.txt",
"chars": 1293,
"preview": "AsyncFsWebServer\tKEYWORD1\nWebServer\t KEYWORD1\nFSWebServer KEYWORD1\n\ninit\t KEYWORD2\nbegin\t "
},
{
"path": "library.properties",
"chars": 500,
"preview": "name=esp-fs-webserver\nversion=3.3.0\nauthor=Tolentino Cotesta <cotestatnt@yahoo.com>\nmaintainer=Tolentino Cotesta <cotest"
},
{
"path": "partitions.csv",
"chars": 301,
"preview": "# Name ,Type ,SubType ,Offset ,Size ,Flags\nnvs ,data ,nvs ,36K ,20K ,\notadata ,data ,ota ,56K "
},
{
"path": "pio_examples/customOptions/.gitignore",
"chars": 94,
"preview": ".pio\n.vscode/.browse.c_cpp.db*\n.vscode/c_cpp_properties.json\n.vscode/launch.json\n.vscode/ipch\n"
},
{
"path": "pio_examples/customOptions/customOptions.code-workspace",
"chars": 88,
"preview": "{\n\t\"folders\": [\n\t\t{\n\t\t\t\"path\": \".\"\n\t\t},\n\t\t{\n\t\t\t\"path\": \"../..\"\n\t\t}\n\t],\n\t\"settings\": {}\n}"
},
{
"path": "pio_examples/customOptions/platformio.ini",
"chars": 896,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "pio_examples/customOptions/src/customOptions.ino",
"chars": 22539,
"preview": "#include <Arduino.h>\n#include <FS.h>\n#include <LittleFS.h>\n#include <FSWebServer.h> //https://github.com/cotestatnt/asy"
},
{
"path": "pio_examples/esp32-p4/.gitignore",
"chars": 94,
"preview": ".pio\n.vscode/.browse.c_cpp.db*\n.vscode/c_cpp_properties.json\n.vscode/launch.json\n.vscode/ipch\n"
},
{
"path": "pio_examples/esp32-p4/app3M_spiffs9M_16MB.csv",
"chars": 305,
"preview": "# Name, Type, SubType, Offset, Size, Flags\nnvs, data, nvs, 0x9000, 0x5000,\notadata, data, ota, 0xe000,"
},
{
"path": "pio_examples/esp32-p4/platformio.ini",
"chars": 1348,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "pio_examples/esp32-p4/src/index_htm.h",
"chars": 4970,
"preview": "#pragma once\n#include <Arduino.h>\n\ninline const char homepage[] PROGMEM = R\"EOF(\n<!DOCTYPE html>\n<html>\n <head>\n <me"
},
{
"path": "pio_examples/esp32-p4/src/withWebSocket.cpp",
"chars": 5749,
"preview": "#ifdef USE_M5UNIFIED\n #include <M5Unified.h>\n#endif\n#include \"ESP_HostedOTA.h\"\n#include <FS.h>\n#include <LittleFS.h>\n"
},
{
"path": "pio_examples/leanWebserver/.gitignore",
"chars": 94,
"preview": ".pio\n.vscode/.browse.c_cpp.db*\n.vscode/c_cpp_properties.json\n.vscode/launch.json\n.vscode/ipch\n"
},
{
"path": "pio_examples/leanWebserver/data/index.htm",
"chars": 2028,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n <title>ESP simpleS"
},
{
"path": "pio_examples/leanWebserver/partitions.csv",
"chars": 305,
"preview": "# Name, Type, SubType, Offset, Size, Flags\nnvs, data, nvs, 0x9000, 0x5000,\notadata, data, ota, 0xe000,"
},
{
"path": "pio_examples/leanWebserver/platformio.ini",
"chars": 1342,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "pio_examples/leanWebserver/src/leanWebserver.ino",
"chars": 2255,
"preview": "#include <Arduino.h>\n#include <FS.h>\n#include <LittleFS.h>\n#include <FSWebServer.h> //https://github.com/cotestatnt/asy"
},
{
"path": "pio_examples/simpleServer/.gitignore",
"chars": 94,
"preview": ".pio\n.vscode/.browse.c_cpp.db*\n.vscode/c_cpp_properties.json\n.vscode/launch.json\n.vscode/ipch\n"
},
{
"path": "pio_examples/simpleServer/compile_commands.json",
"chars": 236911,
"preview": "[\n {\n \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o "
},
{
"path": "pio_examples/simpleServer/partitions.csv",
"chars": 305,
"preview": "# Name, Type, SubType, Offset, Size, Flags\nnvs, data, nvs, 0x9000, 0x5000,\notadata, data, ota, 0xe000,"
},
{
"path": "pio_examples/simpleServer/platformio.ini",
"chars": 1139,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "pio_examples/simpleServer/src/simpleServer.ino",
"chars": 4437,
"preview": "#include <FS.h>\n#include <LittleFS.h>\n#include \"FSWebServer.h\"\n\nFSWebServer server(LittleFS, 80, \"myServer\");\nuint16_t t"
},
{
"path": "pio_examples/websocketEcharts/.gitignore",
"chars": 94,
"preview": ".pio\n.vscode/.browse.c_cpp.db*\n.vscode/c_cpp_properties.json\n.vscode/launch.json\n.vscode/ipch\n"
},
{
"path": "pio_examples/websocketEcharts/README.md",
"chars": 817,
"preview": "# WebSocket Chart Example\n\nReal-time telemetry dashboard demonstrating WebSocket communication with live charting.\n\nThe "
},
{
"path": "pio_examples/websocketEcharts/data/index.htm",
"chars": 17856,
"preview": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-w"
},
{
"path": "pio_examples/websocketEcharts/data/index.html",
"chars": 17384,
"preview": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-w"
},
{
"path": "pio_examples/websocketEcharts/platformio.ini",
"chars": 1063,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "pio_examples/websocketEcharts/src/websocketEcharts.ino",
"chars": 3730,
"preview": "#include <Arduino.h>\n#include <FS.h>\n#include <LittleFS.h>\n#include <FSWebServer.h> // https://github.com/cotestatnt/e"
},
{
"path": "pio_examples/withWebSocket/.gitignore",
"chars": 94,
"preview": ".pio\n.vscode/.browse.c_cpp.db*\n.vscode/c_cpp_properties.json\n.vscode/launch.json\n.vscode/ipch\n"
},
{
"path": "pio_examples/withWebSocket/partitions.csv",
"chars": 305,
"preview": "# Name, Type, SubType, Offset, Size, Flags\nnvs, data, nvs, 0x9000, 0x5000,\notadata, data, ota, 0xe000,"
},
{
"path": "pio_examples/withWebSocket/platformio.ini",
"chars": 996,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "pio_examples/withWebSocket/readme.md",
"chars": 1532,
"preview": "## AsyncFsWebServer – withWebSocket example\n\nThis folder contains a PlatformIO example and a small documentation set for"
},
{
"path": "pio_examples/withWebSocket/src/index_htm.h",
"chars": 4970,
"preview": "#pragma once\n#include <Arduino.h>\n\ninline const char homepage[] PROGMEM = R\"EOF(\n<!DOCTYPE html>\n<html>\n <head>\n <me"
},
{
"path": "pio_examples/withWebSocket/src/withWebSocket.ino",
"chars": 5835,
"preview": "#include <FS.h>\n#include <LittleFS.h>\n#include \"FSWebServer.h\"\n\n#include \"index_htm.h\"\n\n#define FILESYSTEM LittleFS\ncons"
},
{
"path": "platformio.ini",
"chars": 2362,
"preview": "\n[platformio]\ndefault_envs = ci-arduino-3-latest, ci-esp8266\nlib_dir = .\n\n[env]\nframework = arduino\nupload_protocol = es"
},
{
"path": "src/ConfigUpgrader.hpp",
"chars": 21832,
"preview": "#ifndef CONFIG_UPGRADER_HPP\n#define CONFIG_UPGRADER_HPP\n\n#include <FS.h>\n#include \"SerialLog.h\"\n\nextern \"C\" {\n#include \""
},
{
"path": "src/CredentialManager.cpp",
"chars": 25058,
"preview": "#include \"CredentialManager.h\"\n#include \"SerialLog.h\"\n\n\nCredentialManager::CredentialManager() : m_efuse_initialized(fal"
},
{
"path": "src/CredentialManager.h",
"chars": 9883,
"preview": "#ifndef CREDENTIAL_MANAGER_H\n#define CREDENTIAL_MANAGER_H\n\n#include <Arduino.h>\n#include <IPAddress.h>\n#include <vector>"
},
{
"path": "src/FSWebServer.cpp",
"chars": 44745,
"preview": "#include \"FSWebServer.h\"\n\n#if defined(ESP32)\n #include \"mimetable/mimetable.h\"\n#endif\n\nnamespace {\nString serializeJs"
},
{
"path": "src/FSWebServer.h",
"chars": 18959,
"preview": "#ifndef FS_WEBSERVER_H\n#define FS_WEBSERVER_H\n\n\n#include \"WiFiService.h\"\n#include \"Json.h\"\n#include \"SerialLog.h\"\n#inclu"
},
{
"path": "src/Json.cpp",
"chars": 11586,
"preview": "#include \"Json.h\"\n#include <cstring>\n#include <stdlib.h>\n\nusing namespace CJSON;\n\nJson::Json() : root(nullptr) {}\nJson::"
},
{
"path": "src/Json.h",
"chars": 2129,
"preview": "#pragma once\n#include <Arduino.h>\n#include <vector>\n#include <stdint.h>\nextern \"C\" {\n#include \"json/cJSON.h\"\n}\n\nnamespac"
},
{
"path": "src/SerialLog.h",
"chars": 1309,
"preview": "#ifndef __SERIALLOG_H__\n#define __SERIALLOG_H__\n\n#include <stdio.h>\n#include <string.h>\n\n#ifdef __cplusplus\nextern \"C\"\n{"
},
{
"path": "src/SetupConfig.hpp",
"chars": 54013,
"preview": "#ifndef CONFIGURATOR_HPP\n#define CONFIGURATOR_HPP\n#include <type_traits>\n#include <FS.h>\n#if defined(ESP8266)\n#include <"
},
{
"path": "src/Version.h",
"chars": 2196,
"preview": "\n#ifndef VERSION_H\n#define VERSION_H\t\n\n// Year YY\n#define BUILD_YEAR_CH0 (__DATE__[9])\n#define BUILD_YEAR_CH1 (__DATE__["
},
{
"path": "src/WiFiService.cpp",
"chars": 18422,
"preview": "#include \"WiFiService.h\"\n#include \"Json.h\"\n\n#if defined(ESP32) || defined(ESP8266)\nWiFiConnectedCallbackF WiFiService::m"
},
{
"path": "src/WiFiService.h",
"chars": 3749,
"preview": "#pragma once\n\n#include <Arduino.h>\n#include <DNSServer.h>\n#include <FS.h>\n#include \"SerialLog.h\"\n#include \"CredentialMan"
},
{
"path": "src/assets/edit_htm.h",
"chars": 41943,
"preview": "// Auto Generated file (shared build script)\n#pragma once\n#include <pgmspace.h>\n\nconst uint8_t _acedit_htm[6758] PROGMEM"
},
{
"path": "src/assets/logo_svg.h",
"chars": 9780,
"preview": "// Auto Generated file (shared build script)\n#pragma once\n#include <pgmspace.h>\n\nconst uint8_t _aclogo_svg[1560] PROGMEM"
},
{
"path": "src/assets/setup_htm.h",
"chars": 66717,
"preview": "// Auto Generated file (shared build script)\n#pragma once\n#include <pgmspace.h>\n\nconst uint8_t _acsetup_min_htm[10761] P"
},
{
"path": "src/compat/mbedtls_aes.h",
"chars": 2010,
"preview": "#pragma once\n\n#if defined(ESP8266)\n#include <bearssl/bearssl.h>\n#include <stdint.h>\n#include <stddef.h>\n#include <string"
},
{
"path": "src/esp-fs-webserver.h",
"chars": 87,
"preview": "\n#ifndef ESP_FS_WEBSERVER_H\n#define ESP_FS_WEBSERVER_H\n\n#include \"FSWebServer.h\"\n#endif"
},
{
"path": "src/json/cJSON.c",
"chars": 80398,
"preview": "/*\n Copyright (c) 2009-2017 Dave Gamble and cJSON contributors\n\n Permission is hereby granted, free of charge, to any "
},
{
"path": "src/json/cJSON.h",
"chars": 16393,
"preview": "/*\n Copyright (c) 2009-2017 Dave Gamble and cJSON contributors\n\n Permission is hereby granted, free of charge, to any "
},
{
"path": "src/mimetable/mimetable.cpp",
"chars": 1425,
"preview": "#include \"mimetable.h\"\n#include <pgmspace.h>\n\nnamespace mimetype {\n\n// MIME type lookup table stored in PROGMEM\nstatic c"
},
{
"path": "src/mimetable/mimetable.h",
"chars": 329,
"preview": "#ifndef __MIMETABLE_H__\n#define __MIMETABLE_H__\n#include <Arduino.h>\n\nnamespace mimetype {\n\n// Minimal MIME type helper "
},
{
"path": "src/websocket/SocketIOclient.cpp",
"chars": 9955,
"preview": "/*\n * SocketIOclient.cpp\n *\n * Created on: May 12, 2018\n * Author: links\n */\n\n#include \"WebSockets.h\"\n#include \"We"
},
{
"path": "src/websocket/SocketIOclient.h",
"chars": 5384,
"preview": "/**\n * SocketIOclient.h\n *\n * Created on: May 12, 2018\n * Author: links\n */\n\n#ifndef ESP_SOCKETIOCLIENT_H_\n#define"
},
{
"path": "src/websocket/WebSockets.cpp",
"chars": 22916,
"preview": "/**\n * @file WebSockets.cpp\n * @date 20.05.2015\n * @author Markus Sattler\n *\n * Copyright (c) 2015 Markus Sattler. All r"
},
{
"path": "src/websocket/WebSockets.h",
"chars": 7687,
"preview": "/**\n * @file WebSockets.h\n * @date 20.05.2015\n * @author Markus Sattler\n *\n * Copyright (c) 2015 Markus Sattler. All rig"
},
{
"path": "src/websocket/WebSocketsClient.cpp",
"chars": 29553,
"preview": "/**\n * @file WebSocketsClient.cpp\n * @date 20.05.2015\n * @author Markus Sattler\n *\n * Copyright (c) 2015 Markus Sattler."
},
{
"path": "src/websocket/WebSocketsClient.h",
"chars": 6026,
"preview": "/**\n * @file WebSocketsClient.h\n * @date 20.05.2015\n * @author Markus Sattler\n *\n * Copyright (c) 2015 Markus Sattler. A"
},
{
"path": "src/websocket/WebSocketsServer.cpp",
"chars": 28161,
"preview": "/**\n * @file WebSocketsServer.cpp\n * @date 20.05.2015\n * @author Markus Sattler\n *\n * Copyright (c) 2015 Markus Sattler."
},
{
"path": "src/websocket/WebSocketsServer.h",
"chars": 8271,
"preview": "/**\n * @file WebSocketsServer.h\n * @date 20.05.2015\n * @author Markus Sattler\n *\n * Copyright (c) 2015 Markus Sattler. A"
},
{
"path": "src/websocket/libb64/AUTHORS",
"chars": 166,
"preview": "libb64: Base64 Encoding/Decoding Routines\n======================================\n\nAuthors:\n-------\n\nChris Venter\tchris.v"
},
{
"path": "src/websocket/libb64/LICENSE",
"chars": 1679,
"preview": "Copyright-Only Dedication (based on United States law) \nor Public Domain Certification\n\nThe person or persons who have a"
},
{
"path": "src/websocket/libb64/cdecode.c",
"chars": 2672,
"preview": "/*\ncdecoder.c - c source to a base64 decoding algorithm implementation\n\nThis is part of the libb64 project, and has been"
},
{
"path": "src/websocket/libb64/cdecode_inc.h",
"chars": 648,
"preview": "/*\ncdecode.h - c header for a base64 decoding algorithm\n\nThis is part of the libb64 project, and has been placed in the "
},
{
"path": "src/websocket/libb64/cencode.c",
"chars": 2722,
"preview": "/*\ncencoder.c - c source to a base64 encoding algorithm implementation\n\nThis is part of the libb64 project, and has been"
},
{
"path": "src/websocket/libb64/cencode_inc.h",
"chars": 723,
"preview": "/*\ncencode.h - c header for a base64 encoding algorithm\n\nThis is part of the libb64 project, and has been placed in the "
},
{
"path": "src/websocket/libsha1/libsha1.c",
"chars": 6768,
"preview": "/* from valgrind tests */\n\n/* ================ sha1.c ================ */\n/*\nSHA-1 in C\nBy Steve Reid <steve@edmweb.com>"
},
{
"path": "src/websocket/libsha1/libsha1.h",
"chars": 516,
"preview": "/* ================ sha1.h ================ */\n/*\nSHA-1 in C\nBy Steve Reid <steve@edmweb.com>\n100% Public Domain\n*/\n\n#if"
}
]
About this extraction
This page contains the full source code of the cotestatnt/esp-fs-webserver GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 184 files (1.3 MB), approximately 481.6k tokens, and a symbol index with 357 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.